From ffba8b572dd28afd96c1f3d51b180033f16dbbb6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 06:39:40 +0000 Subject: [PATCH] chore(spec): sync API spec 2026-10-29 --- openapi/SOURCE.json | 8 +- openapi/account.json | 177 +- openapi/verification.json | 16819 +++++++++------- .../me/didit/resources/AccountResource.java | 4 +- .../resources/AddressValidationResource.java | 151 + .../resources/AgeEstimationResource.java | 2 +- .../me/didit/resources/BillingResource.java | 2 +- .../didit/resources/BusinessesResource.java | 2 + .../me/didit/resources/CasesResource.java | 4 +- .../me/didit/resources/DiditResources.java | 11 + .../didit/resources/DocumentAiResource.java | 2 +- .../me/didit/resources/FaceMatchResource.java | 2 +- .../didit/resources/FaceSearchResource.java | 2 +- .../resources/IdVerificationResource.java | 6 +- .../resources/PassiveLivenessResource.java | 2 +- .../resources/ProofOfAddressResource.java | 2 +- .../resources/RegulatoryReportsResource.java | 129 +- .../me/didit/resources/SessionsResource.java | 4 + .../didit/resources/TransactionsResource.java | 176 + .../TransactionsTravelRuleResource.java | 2 +- .../resources/TravelRuleNetworksResource.java | 498 + .../didit/resources/TravelRuleResource.java | 22 + .../TravelRuleWalletProofsResource.java | 324 + .../me/didit/resources/UsersResource.java | 2 + .../didit/types/AddressValidationRequest.java | 223 + .../types/AddressValidationResponse.java | 142 + .../didit/types/AddressValidationResult.java | 499 + src/main/java/me/didit/types/Application.java | 4 +- .../types/BillingRetrieveBalanceResponse.java | 4 +- .../me/didit/types/BillingTopUpParams.java | 4 +- .../didit/types/EmailUndeliverableError.java | 261 + .../me/didit/types/FiledRegulatoryReport.java | 225 + .../didit/types/FiledReportTransaction.java | 142 + .../types/IdVerificationVerifyParams.java | 4 +- .../types/IdVerificationVerifyResponse.java | 25 +- .../me/didit/types/InFiledReportError.java | 205 + .../me/didit/types/KYBRegistryOwnership.java | 11 +- .../types/KybListShareholdersResponse.java | 11 +- .../me/didit/types/KybListUbosResponse.java | 11 +- .../me/didit/types/KybSelectResponse.java | 11 +- .../me/didit/types/NetworkDetailResponse.java | 10 +- .../me/didit/types/NetworksListResponse.java | 10 +- .../me/didit/types/RecoveryHoldError.java | 185 + .../types/RecoverySetupRequiredError.java | 205 + .../types/RegulatoryReportsDeleteParams.java | 99 + .../java/me/didit/types/SessionDecision.java | 865 +- .../me/didit/types/SessionsCreateParams.java | 174 +- .../didit/types/SessionsCreateResponse.java | 22 +- .../me/didit/types/SessionsListParams.java | 4 +- .../types/TransactionsBatchDeleteParams.java | 77 + .../types/TravelRuleNetworkParticipant.java | 640 + .../TravelRuleNetworkParticipantSummary.java | 300 + .../types/TravelRuleNetworkQuestionnaire.java | 418 + ...TravelRuleNetworkQuestionnaireAnswers.java | 571 + .../didit/types/TravelRuleNetworkState.java | 518 + .../types/TravelRuleNetworksOverview.java | 306 + .../me/didit/types/TravelRuleNextAction.java | 6 +- .../didit/types/TravelRuleSettingsDetail.java | 986 +- .../types/TravelRuleTransferPatchRequest.java | 35 +- .../types/TravelRuleWalletProofDecision.java | 163 + .../TravelRuleWalletProofFailureReason.java | 87 + .../types/TravelRuleWalletProofReview.java | 528 + .../TravelRuleWalletProofsListParams.java | 188 + .../TravelRuleWalletProofsListResponse.java | 159 + .../didit/types/TravelRuleWidgetContext.java | 79 +- .../types/TravelRuleWidgetProofCreate.java | 190 + .../types/TravelRuleWidgetProofError.java | 337 + .../types/TravelRuleWidgetProofSummary.java | 56 +- .../TravelRuleWidgetProofVerifyResult.java | 316 + .../types/TravelRuleWidgetSessionCreate.java | 4 +- .../me/didit/types/WalletAddressEntry.java | 4 +- .../didit/types/WalletAddressEntryCreate.java | 4 +- .../java/me/didit/types/WebhookConfig.java | 68 +- .../types/WebhooksUpdateConfigParams.java | 68 +- .../java/me/didit/types/WorkflowDetail.java | 16 +- .../AddressValidationResourceTest.java | 42 + .../RegulatoryReportsResourceTest.java | 6 +- .../didit/resources/SessionsResourceTest.java | 4 +- .../resources/TransactionsResourceTest.java | 15 + .../TransactionsTravelRuleResourceTest.java | 4 +- .../TravelRuleNetworksResourceTest.java | 106 + .../TravelRuleWalletProofsResourceTest.java | 61 + .../didit/resources/WebhooksResourceTest.java | 4 +- 83 files changed, 19939 insertions(+), 8136 deletions(-) create mode 100644 src/main/java/me/didit/resources/AddressValidationResource.java create mode 100644 src/main/java/me/didit/resources/TravelRuleNetworksResource.java create mode 100644 src/main/java/me/didit/resources/TravelRuleWalletProofsResource.java create mode 100644 src/main/java/me/didit/types/AddressValidationRequest.java create mode 100644 src/main/java/me/didit/types/AddressValidationResponse.java create mode 100644 src/main/java/me/didit/types/AddressValidationResult.java create mode 100644 src/main/java/me/didit/types/EmailUndeliverableError.java create mode 100644 src/main/java/me/didit/types/FiledRegulatoryReport.java create mode 100644 src/main/java/me/didit/types/FiledReportTransaction.java create mode 100644 src/main/java/me/didit/types/InFiledReportError.java create mode 100644 src/main/java/me/didit/types/RecoveryHoldError.java create mode 100644 src/main/java/me/didit/types/RecoverySetupRequiredError.java create mode 100644 src/main/java/me/didit/types/RegulatoryReportsDeleteParams.java create mode 100644 src/main/java/me/didit/types/TransactionsBatchDeleteParams.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworkParticipant.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworkParticipantSummary.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworkQuestionnaire.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworkQuestionnaireAnswers.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworkState.java create mode 100644 src/main/java/me/didit/types/TravelRuleNetworksOverview.java create mode 100644 src/main/java/me/didit/types/TravelRuleWalletProofDecision.java create mode 100644 src/main/java/me/didit/types/TravelRuleWalletProofFailureReason.java create mode 100644 src/main/java/me/didit/types/TravelRuleWalletProofReview.java create mode 100644 src/main/java/me/didit/types/TravelRuleWalletProofsListParams.java create mode 100644 src/main/java/me/didit/types/TravelRuleWalletProofsListResponse.java create mode 100644 src/main/java/me/didit/types/TravelRuleWidgetProofCreate.java create mode 100644 src/main/java/me/didit/types/TravelRuleWidgetProofError.java create mode 100644 src/main/java/me/didit/types/TravelRuleWidgetProofVerifyResult.java create mode 100644 src/test/java/me/didit/resources/AddressValidationResourceTest.java create mode 100644 src/test/java/me/didit/resources/TravelRuleNetworksResourceTest.java create mode 100644 src/test/java/me/didit/resources/TravelRuleWalletProofsResourceTest.java diff --git a/openapi/SOURCE.json b/openapi/SOURCE.json index 5561124..9780330 100644 --- a/openapi/SOURCE.json +++ b/openapi/SOURCE.json @@ -1,13 +1,13 @@ { "verification.json": { "url": "https://docs.didit.me/openapi-25.json", - "sha256": "b2658eb22e23f868d2df5fe513c8dcdadce3abf2e35d1bbebce0382c911038db", - "fetched_at": "2026-10-27T06:50:05Z" + "sha256": "12b2fdb05572755d61b93791f6964b7d883ba7b20f7ea9b31543903b6edab8a9", + "fetched_at": "2026-10-29T06:37:04Z" }, "account.json": { "url": "https://docs.didit.me/openapi-auth.json", - "sha256": "5fafb88b306ab60c7c105b0bf00fa2a6a49b5d2ca8115eb003b332f073844b39", - "fetched_at": "2026-10-23T06:48:14Z" + "sha256": "ae1b5e07df822fe93cae27a9eaa0790948069352c9d98c87eecf7efd644c92cb", + "fetched_at": "2026-10-29T06:37:04Z" }, "webhook-vectors.json": { "generator": "product-automations scripts/server-sdks/make_vectors.py (seeded 2026-10-17)", diff --git a/openapi/account.json b/openapi/account.json index ce6a041..b832f39 100644 --- a/openapi/account.json +++ b/openapi/account.json @@ -25,7 +25,7 @@ "/programmatic/register/": { "post": { "summary": "Register Account", - "description": "Step 1 of programmatic onboarding. Creates a pending account and emails a 6-char code (10 min TTL). Rate limit: 5/IP/hour.", + "description": "Step 1 of programmatic onboarding. Creates a pending account and emails a 6-char code (10 min TTL). The address is checked before any account is created: one that cannot receive the code returns 400 `email_undeliverable`. Rate limits: 5/IP/hour, plus a shared hourly budget per network (/24 for IPv4, /64 for IPv6).", "operationId": "programmatic_register", "x-didit-sdk": { "resource": "account", @@ -82,11 +82,18 @@ } }, "400": { - "description": "Validation error. The intended envelope is `{\"field\": [\"...\"]}` for field-level errors and `{\"detail\": \"...\"}` for business-rule errors (e.g. duplicate email). Password-strength rules currently respond with a bare array `[\"...\"]`, one rule at a time -- known server-side inconsistency; treat it as `{\"password\": [\"...\"]}` until fixed.", + "description": "Validation error. The intended envelope is `{\"field\": [\"...\"]}` for field-level errors and `{\"detail\": \"...\"}` for business-rule errors (e.g. duplicate email). Password-strength rules currently respond with a bare array `[\"...\"]`, one rule at a time -- known server-side inconsistency; treat it as `{\"password\": [\"...\"]}` until fixed. When the address cannot receive the code (disposable, reserved or test domain, no mail server, a likely typo, or a previously bounced address), the body is `EmailUndeliverableError` with `code: \"email_undeliverable\"`; no account is created or changed.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ValidationError" + "oneOf": [ + { + "$ref": "#/components/schemas/ValidationError" + }, + { + "$ref": "#/components/schemas/EmailUndeliverableError" + } + ] }, "examples": { "MissingFields": { @@ -145,13 +152,30 @@ "value": { "detail": "An account with this email already exists." } + }, + "EmailUndeliverable": { + "summary": "Address cannot receive email (disposable domain)", + "value": { + "detail": "This email can't receive messages. Please use a different email address.", + "code": "email_undeliverable", + "reason": "disposable_domain" + } + }, + "EmailLikelyTypo": { + "summary": "Likely typo in the domain", + "value": { + "detail": "This email can't receive messages. Please use a different email address.", + "code": "email_undeliverable", + "reason": "possible_typo", + "suggestion": "gmail.com" + } } } } } }, "429": { - "description": "Rate limited. More than 5 registration attempts from the same IP in the last hour. Also returned, with a `Retry-After: 5` header, when the service is momentarily checking as many passwords or codes as it can at once; retry after the indicated number of seconds.", + "description": "Rate limited. More than 5 registration attempts from the same IP in the last hour, or the hourly budget for the caller's network (/24 for IPv4, /64 for IPv6, shared by registration and code resends) is used up. Both carry `Retry-After: 3600`. Also returned, with a `Retry-After: 5` header, when the service is momentarily checking as many passwords or codes as it can at once; retry after the indicated number of seconds.", "content": { "application/json": { "schema": { @@ -472,6 +496,27 @@ } } }, + "403": { + "description": "The account was recovered and must set up a new sign-in method before it can log in. A link to do that was emailed to the account's address.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverySetupRequiredError" + }, + "examples": { + "RecoverySetupRequired": { + "summary": "Recovered account with no sign-in method yet", + "value": { + "recovery_setup_required": true, + "setup_link_sent": true, + "message": "You recovered your account. Open the link we emailed you to set up a new sign-in method.", + "code": "recovery_setup_required" + } + } + } + } + } + }, "429": { "description": "Account locked (too many recent failures on this email) or IP rate-limited (too many attempts from this address). Also returned, with a `Retry-After: 5` header, when the service is momentarily checking as many passwords or codes as it can at once; retry after the indicated number of seconds.", "content": { @@ -835,11 +880,18 @@ } }, "403": { - "description": "You cannot create applications in this organization. A member of the organization who is not an owner or admin gets `Only organization owners and admins can manage applications.` An owner or admin whose access to the organization is suspended gets `Your access to this organization is suspended.` A request without an `Authorization` header gets `You do not have permission to perform this action.`", + "description": "You cannot create applications in this organization. A member of the organization who is not an owner or admin gets `Only organization owners and admins can manage applications.` An owner or admin whose access to the organization is suspended gets `Your access to this organization is suspended.` A request without an `Authorization` header gets `You do not have permission to perform this action.` For 72 hours after you recovered your account the request is refused with `code` `recovery_hold` and `until`, the time the hold ends.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AuthError" + "oneOf": [ + { + "$ref": "#/components/schemas/AuthError" + }, + { + "$ref": "#/components/schemas/RecoveryHoldError" + } + ] }, "examples": { "NotOwnerOrAdmin": { @@ -859,6 +911,14 @@ "value": { "detail": "You do not have permission to perform this action." } + }, + "RecoveryHold": { + "summary": "Account recovered less than 72 hours ago", + "value": { + "detail": "This action is on hold because you recovered your account.", + "code": "recovery_hold", + "until": "2026-11-04T10:15:00+00:00" + } } } } @@ -1596,7 +1656,7 @@ "tags": [ "Programmatic Auth" ], - "description": "Resend the 6-character verification code for an unverified programmatic account. Requires the account's email and password so only the owner can trigger a new code (no console Basic Auth). A fresh code replaces the previous one and is valid for 10 minutes.\n\nRate limits: a per-email cooldown of 60 seconds between resends (HTTP 429 with `Retry-After`), and the same 5-per-IP-per-hour budget as registration.", + "description": "Resend the 6-character verification code for an unverified programmatic account. Requires the account's email and password so only the owner can trigger a new code (no console Basic Auth). A fresh code replaces the previous one and is valid for 10 minutes.\n\nRate limits: a per-email cooldown of 60 seconds between resends (HTTP 429 with `Retry-After`), and the same 5-per-IP-per-hour and per-network hourly budgets as registration.", "requestBody": { "required": true, "content": { @@ -1678,7 +1738,7 @@ } }, "429": { - "description": "Cooldown active (60s between resends) or IP budget exhausted (5/hour, shared with registration). Also returned, with a `Retry-After: 5` header, when the service is momentarily checking as many passwords or codes as it can at once; retry after the indicated number of seconds.", + "description": "Cooldown active (60s between resends) or IP or network budget exhausted (5/IP/hour and an hourly per-network budget, both shared with registration). Also returned, with a `Retry-After: 5` header, when the service is momentarily checking as many passwords or codes as it can at once; retry after the indicated number of seconds.", "content": { "application/json": { "examples": { @@ -1925,7 +1985,7 @@ }, "api_key": { "type": "string", - "description": "Long-lived secret (also called `client_secret`). Use as the `x-api-key` header for every call to `https://verification.didit.me/v3/...` (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with `sk_live_` (live application) or `sk_test_` (sandbox application), followed by 43 URL-safe characters (letters, digits, `-` and `_`). Keys created before this format have no prefix and keep working unchanged.", + "description": "Long-lived secret (also called `client_secret`). Use as the `x-api-key` header for every call to `https://verification.didit.me/v3/...` (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with `sk_live_` (live application) or `sk_test_` (sandbox application), followed by 43 URL-safe characters (letters, digits, `-` and `_`). Keys created before this format have no prefix and keep working unchanged. For 72 hours after you recovered your account, list, retrieve and update return an empty string here instead of the key.", "example": "sk_live_example_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, "website_url": { @@ -2245,6 +2305,105 @@ "status", "modules" ] + }, + "EmailUndeliverableError": { + "type": "object", + "description": "Returned by `POST /programmatic/register/` when the address cannot receive the verification code. The check runs before any account is created or changed, so nothing is left behind.", + "required": [ + "detail", + "code", + "reason" + ], + "properties": { + "detail": { + "type": "string", + "description": "Human-readable message.", + "example": "This email can't receive messages. Please use a different email address." + }, + "code": { + "type": "string", + "enum": [ + "email_undeliverable" + ], + "description": "Always `email_undeliverable`." + }, + "reason": { + "type": "string", + "enum": [ + "invalid_address", + "reserved_domain", + "no_mail_records", + "disposable_domain", + "possible_typo", + "suppressed" + ], + "description": "Why the address was refused: `invalid_address` (not a valid address), `reserved_domain` (a reserved or test domain such as `example.com`), `no_mail_records` (the domain has no mail server), `disposable_domain` (a throwaway mailbox provider), `possible_typo` (a likely misspelling of a common provider; see `suggestion`), `suppressed` (the address previously bounced or complained)." + }, + "suggestion": { + "type": "string", + "description": "The likely intended domain, present only when the domain looks like a typo (for example `gmail.com` for `gmial.com`). The API does not accept a confirmation: register again with the corrected address.", + "example": "gmail.com" + } + } + }, + "RecoveryHoldError": { + "type": "object", + "description": "Returned for 72 hours after the account was recovered (lost sign-in methods were reset). Actions that create organizations, applications or secrets are refused until `until`; read operations keep working.", + "required": [ + "detail", + "code", + "until" + ], + "properties": { + "detail": { + "type": "string", + "example": "This action is on hold because you recovered your account." + }, + "code": { + "type": "string", + "enum": [ + "recovery_hold" + ], + "example": "recovery_hold" + }, + "until": { + "type": "string", + "format": "date-time", + "description": "When the hold ends (ISO 8601). Retry the request after this time.", + "example": "2026-11-04T10:15:00+00:00" + } + } + }, + "RecoverySetupRequiredError": { + "type": "object", + "description": "The account was recovered and has no sign-in method set up yet. No tokens are issued; a one-time link to set up a new method was emailed to the account's address. Open it, set up the method, then log in again.", + "required": [ + "recovery_setup_required", + "setup_link_sent", + "message", + "code" + ], + "properties": { + "recovery_setup_required": { + "type": "boolean", + "example": true + }, + "setup_link_sent": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "You recovered your account. Open the link we emailed you to set up a new sign-in method." + }, + "code": { + "type": "string", + "enum": [ + "recovery_setup_required" + ], + "example": "recovery_setup_required" + } + } } } } diff --git a/openapi/verification.json b/openapi/verification.json index 1125811..7736883 100644 --- a/openapi/verification.json +++ b/openapi/verification.json @@ -754,6 +754,17 @@ "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": [ @@ -1078,6 +1089,19 @@ }, "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).", @@ -1412,6 +1436,32 @@ } } } + }, + "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": { @@ -2822,7 +2872,7 @@ "status": "Success", "reason": null }, - "fee": 0.03 + "fee": 0.02 }, { "type": "VALID_CODE_ENTERED", @@ -3912,7 +3962,36 @@ } ], "created_at": "2024-07-24T08:54:25.443172Z", - "expires_at": "2025-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": { @@ -4528,7 +4607,7 @@ "/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.", + "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", @@ -4770,6 +4849,16 @@ } } } + }, + "409": { + "description": "The session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } } }, "security": [ @@ -5639,7 +5728,7 @@ "/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` `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. 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 (standalone APIs have no free tier). 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`.", + "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", @@ -5684,7 +5773,7 @@ "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.", + "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": { @@ -5785,7 +5874,7 @@ }, "responses": { "200": { - "description": "Document processed. `id_verification.status` is `Approved` or `Declined`; every detected issue is itemized in `id_verification.warnings`. 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.", + "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": { @@ -6031,9 +6120,10 @@ "type": "string", "enum": [ "Approved", - "Declined" + "Declined", + "In Review" ], - "description": "`Approved` when no warning resolves to a decline; `Declined` otherwise — the `warnings` list explains why." + "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", @@ -6450,13 +6540,13 @@ }, "warnings": { "type": "array", - "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`.", + "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.", + "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": { @@ -6479,7 +6569,7 @@ "warning", "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`.)" + "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", @@ -6684,7 +6774,7 @@ "/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 (standalone APIs have no free tier). 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`.", + "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", @@ -7212,7 +7302,7 @@ "/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 (standalone APIs have no free tier). 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`.", + "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", @@ -7701,7 +7791,7 @@ "/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 (standalone APIs have no free tier). 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.", + "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", @@ -8506,7 +8596,7 @@ "/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, no free tier** (standalone APIs never have one). 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/`.", + "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", @@ -10093,7 +10183,7 @@ "/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 (standalone APIs have no free tier). 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`.", + "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", @@ -10771,7 +10861,7 @@ "name": "search", "in": "query", "required": false, - "description": "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.", + "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" }, @@ -12605,9 +12695,10 @@ "enum": [ "not_requested", "delivered", + "not_held", "unavailable" ], - "description": "`delivered` when the ownership data came back, `unavailable` when an ownership tier was requested but the registry had none for this company (see `registry_data.ownership_error`), `not_requested` on the Lite tier." + "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" @@ -12867,7 +12958,7 @@ "/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 (standalone APIs have no free tier). 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`.", + "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", @@ -15340,7 +15431,7 @@ "channel": "whatsapp", "actual_channel": "whatsapp" }, - "fee": 0.0709 + "fee": 0.0509 }, { "type": "PHONE_DELIVERY_DELIVERED", @@ -15428,7 +15519,7 @@ "channel": "whatsapp", "actual_channel": "whatsapp" }, - "fee": 0.0709 + "fee": 0.0509 }, { "type": "INVALID_CODE_ENTERED", @@ -15515,7 +15606,7 @@ "channel": "whatsapp", "actual_channel": "sms" }, - "fee": 0.0709 + "fee": 0.0509 }, { "type": "VALID_CODE_ENTERED", @@ -16510,7 +16601,7 @@ "status": "Success", "reason": null }, - "fee": 0.03 + "fee": 0.02 }, { "type": "VALID_CODE_ENTERED", @@ -16582,7 +16673,7 @@ "status": "Success", "reason": null }, - "fee": 0.03 + "fee": 0.02 }, { "type": "VALID_CODE_ENTERED", @@ -16652,7 +16743,7 @@ "status": "Success", "reason": null }, - "fee": 0.03 + "fee": 0.02 }, { "type": "VALID_CODE_ENTERED", @@ -19313,7 +19404,7 @@ "/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.", + "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", @@ -19513,6 +19604,16 @@ } } } + }, + "409": { + "description": "The selection includes a person in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } } }, "security": [ @@ -19525,7 +19626,7 @@ "/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).", + "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", @@ -20081,6 +20182,16 @@ } } } + }, + "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": [ @@ -20863,7 +20974,7 @@ "auto_refill_amount": { "type": "string", "nullable": true, - "description": "USD amount that will be charged on each auto-refill. `null` when auto-refill is disabled or unset." + "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", @@ -20932,7 +21043,7 @@ "/v3/billing/top-up/": { "post": { "summary": "Top up credits", - "description": "Add credits via a hosted checkout session (**$50 minimum**); not available on annual plans. Redirect the customer to the returned `checkout_session_url`.", + "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", @@ -20956,7 +21067,8 @@ "amount_in_dollars": { "type": "number", "minimum": 50, - "description": "Amount to charge, in USD. **Must be at least 50.** Tax is added automatically at checkout based on the customer's billing address." + "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", @@ -21039,7 +21151,7 @@ } }, "400": { - "description": "Invalid payload — the amount is missing, not a number, below the $50 minimum, or the organization is on an annual plan. The error body is a JSON array.", + "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": { @@ -21058,6 +21170,11 @@ "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." @@ -26422,7 +26539,7 @@ "/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`.", + "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", @@ -26579,6 +26696,16 @@ } } } + }, + "409": { + "description": "The selection includes a company in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } } }, "security": [ @@ -31276,26 +31403,46 @@ "gtr": { "rail": "GTR", "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", "platform_available": false, - "connected": 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 + "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 + "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 + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 } }, "didit_network": { @@ -32882,7 +33029,7 @@ "/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.", + "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", @@ -32944,6 +33091,12 @@ "action": "update_originator_wallet", "wallet_address": "bc1qOriginatorInputAddress01" } + }, + "Name the counterparty": { + "value": { + "action": "beneficiary_vasp", + "beneficiary_vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } } } } @@ -32968,7 +33121,7 @@ } }, "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.", + "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": { @@ -32991,10 +33144,10 @@ } }, "404": { - "description": "Unknown transaction, or the transaction has no Travel Rule transfer." + "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.", + "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": { @@ -34188,43 +34341,32 @@ "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", + }, + "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": "users", - "method": "set_ongoing_monitoring", - "auth": "api_key", - "server": true, - "scope": "application" + "resource": "travel_rule", + "method": "select_widget_vasp", + "auth": "public", + "server": false }, "tags": [ - "Users" + "Travel Rule" + ], + "security": [ + {} ], "parameters": [ { - "name": "organization_id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Organization UUID." - }, - { - "name": "application_id", + "name": "token", "in": "path", "required": true, + "description": "Opaque session token returned when the widget session was minted.", "schema": { - "type": "string", - "format": "uuid" - }, - "description": "Application UUID." + "type": "string" + } } ], "requestBody": { @@ -34232,126 +34374,107 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + "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": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "description": "The pick was recorded and the transfer is being sent again.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + "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" } } } }, - "202": { - "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "400": { + "description": "`vasp` is missing, or no live directory entry has that id.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OngoingMonitoringJob" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "vasp": [ + "No directory entry has this id." + ] } } } }, - "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." + "404": { + "description": "Unknown token." }, - { - "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" - } - } - } + "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." }, - "404": { - "description": "Unknown job for this application." - } - }, - "security": [ - { - "ApiKeyAuth": [] + "410": { + "description": "The session expired." } - ] + } } }, - "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/": { + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/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", + "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": "businesses", + "resource": "users", "method": "set_ongoing_monitoring", "auth": "api_key", "server": true, "scope": "application" }, "tags": [ - "Businesses" + "Users" ], "parameters": [ { @@ -34417,20 +34540,168 @@ ] } }, - "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/{job_id}/": { + "/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_businesses_ongoing_monitoring_job", + "operationId": "get_vendor_users_ongoing_monitoring_job", "x-didit-sdk": { - "resource": "businesses", + "resource": "users", "method": "retrieve_ongoing_monitoring_job", "auth": "api_key", "server": true, "scope": "application" }, "tags": [ - "Businesses" + "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": [ { @@ -35380,7 +35651,7 @@ }, "delete": { "summary": "Delete case", - "description": "Delete a case. There is no undelete endpoint.", + "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", @@ -35435,107 +35706,43 @@ "responses": { "204": { "description": "Case deleted." - } - }, - "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." + "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" + } + } + } + } + } }, - { - "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 + "404": { + "description": "The case does not exist in this application or was already deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "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/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.", + }, + "409": { + "description": "The case has a finalized regulatory report. Nothing is deleted.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CaseDetail" + "$ref": "#/components/schemas/InFiledReportError" } } } @@ -35548,14 +35755,118 @@ ] } }, - "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/resolve/": { + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/assign/": { "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", + "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": "resolve", + "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" @@ -40494,7 +40805,7 @@ }, "delete": { "summary": "Delete report", - "description": "Delete a draft or validated report. Finalized reports cannot be deleted (409).", + "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", @@ -40542,16 +40853,44 @@ "x-codeSamples": [ { "lang": "curl", - "label": "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." + "description": "Report deleted, including its generated files." }, - "409": { - "description": "Report is already finalized.", + "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": { @@ -40564,105 +40903,62 @@ } } } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ] - } - }, - "/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." + "404": { + "description": "Report not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } }, - { - "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.", + "409": { + "description": "The report is already being deleted. Repeat the request to finish erasing its files.", "content": { "application/json": { "schema": { "type": "object", "properties": { - "status": { - "type": "string", - "enum": [ - "DRAFT", - "VALIDATED", - "FINALIZED" - ] + "detail": { + "type": "string" }, - "is_valid": { - "type": "boolean" + "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" }, - "errors": { - "type": "array", - "description": "Empty when valid.", - "items": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } + "code": { + "type": "string" } } + }, + "example": { + "detail": "The report files could not be erased. Please retry deleting the report.", + "code": "report_storage_unavailable" } } } @@ -40672,17 +40968,139 @@ { "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}/finalize/": { + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/validate/": { "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", + "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": "finalize", + "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" @@ -41992,7 +42410,7 @@ }, "cross_org_available": { "type": "boolean", - "description": "Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the `global` sharing mode.", + "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": { @@ -42002,7 +42420,7 @@ "sharing_mode" ], "nullable": true, - "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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`.", + "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 } }, @@ -45442,11 +45860,12 @@ "enum": [ "not_requested", "delivered", + "not_held", "unavailable", "pending", null ], - "description": "`delivered` when the ownership data came back, `unavailable` when an ownership tier was requested but the registry had none for this company (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." + "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" @@ -46315,11 +46734,12 @@ "enum": [ "not_requested", "delivered", + "not_held", "unavailable", "pending", null ], - "description": "`delivered` when the ownership data came back, `unavailable` when an ownership tier was requested but the registry had none for this company (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." + "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" @@ -58605,6 +59025,7 @@ "data_retention_months": 24, "face_retention_policy": "delete_with_session", "face_retention_days": null, + "biometric_consent_mode": "auto", "available_events": [ { "value": "status.updated", @@ -58873,6 +59294,15 @@ "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, @@ -58888,7 +59318,8 @@ "Enable biometric template retention": { "value": { "face_retention_policy": "retain_until_user_deleted", - "face_retention_days": 365 + "face_retention_days": 365, + "biometric_consent_mode": "auto" } }, "Rotate the legacy secret": { @@ -58948,6 +59379,7 @@ "data_retention_months": 24, "face_retention_policy": "retain_until_user_deleted", "face_retention_days": 365, + "biometric_consent_mode": "auto", "available_events": [ { "value": "status.updated", @@ -59038,7 +59470,8 @@ "value": { "face_retention_days": [ "A finite retention duration is required when face retention is enabled." - ] + ], + "biometric_consent_mode": "auto" } }, "Retention window out of range": { @@ -73627,522 +74060,1696 @@ } ] } - } - }, - "components": { - "schemas": { - "DocumentAIDetectedCode": { - "type": "object", - "required": [ - "type", - "page_number", - "position", - "x", - "y", - "width", - "height", - "page_width", - "page_height", - "raw_payload", - "parsed_payload" + }, + "/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" ], - "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." + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." }, - "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" + { + "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." }, - "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." + "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." + ] + } + } + } }, - "page_width": { - "type": "integer", - "description": "Rendered page width in pixels." + "403": { + "description": "The API key cannot delete transactions. The endpoint requires `write:transactions`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } }, - "page_height": { - "type": "integer", - "description": "Rendered page height in pixels." + "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 + } + } + } + } + } + } + } }, - "raw_payload": { - "type": "string", - "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." + "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" + } + } + } }, - "parsed_payload": { - "nullable": true, - "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." + "401": { + "description": "Missing or invalid API key." } } }, - "SessionImportJob": { - "type": "object", - "required": [ - "uuid", - "status", - "import_type", - "source_format", - "source_filename", - "total_rows", - "processed_rows", - "created_count", - "updated_count", - "skipped_count", - "failed_count" + "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" ], - "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" - ] + "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" + } + } + } }, - "import_type": { - "type": "string", - "enum": [ - "user_verification", - "business_verification", - "status_rules", - "transactions" - ] + "400": { + "description": "Validation error, keyed by field." }, - "source_format": { - "type": "string", - "enum": [ - "csv", - "ndjson" - ] + "401": { + "description": "Missing or invalid API key." }, - "source_filename": { - "type": "string" + "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" + ] + } + } + } + } + } }, - "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" + "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" + } + } } }, - "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" + "400": { + "description": "The saved answers are incomplete or invalid, keyed by field." }, - "same_person_count": { - "type": "integer", - "format": "int64" + "401": { + "description": "Missing or invalid API key." }, - "face_templates_count": { - "type": "integer", - "format": "int64" + "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" + ] + } + } + } + } + } }, - "face_duplicates_count": { - "type": "integer", - "format": "int64" + "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" + } + } + } }, - "face_blocklist_hits_count": { - "type": "integer", - "format": "int64" + "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" + ] + } + } + } + } + } }, - "created_by_name": { - "type": "string", - "nullable": true + "401": { + "description": "Missing or invalid API key." }, - "created_by_email": { - "type": "string", - "nullable": true + "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" + ] + } + } + } + } + } } } - }, - "SessionImportError": { - "type": "object", - "required": [ - "uuid", - "row_number", - "error", - "raw_row" + } + }, + "/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" ], - "properties": { - "uuid": { - "type": "string", - "format": "uuid" + "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 }'" }, - "row_number": { - "type": "integer", - "format": "int64" + { + "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\"])" }, - "external_id": { - "type": "string", - "nullable": true + { + "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" + } + } + } + } + } }, - "error": { - "type": "string" + "400": { + "$ref": "#/components/responses/AddressValidationValidationError" }, - "raw_row": { - "type": "object", - "additionalProperties": true + "403": { + "$ref": "#/components/responses/AddressValidationPermissionDenied" }, - "created_at": { - "type": "string", - "format": "date-time" + "429": { + "$ref": "#/components/responses/RateLimitedWrite" } } - }, - "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." + } + }, + "/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" + } }, - "color_background": { - "type": "string", - "description": "Page background color." + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } }, - "color_panel": { - "type": "string", - "description": "Panel / accent color." + { + "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" + } + } + } + } + } + } }, - "color_panel_10": { - "type": "string", - "description": "Panel color at 10% opacity." + "400": { + "description": "Unknown status." }, - "color_on_panel_1": { - "type": "string", - "description": "Support text color on panels." + "401": { + "description": "Missing or invalid x-api-key." }, - "color_on_panel_2": { - "type": "string", - "description": "Pill / badge color on panels." + "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" + } + } + } }, - "color_on_background": { - "type": "string", - "description": "Text color on background." + "400": { + "description": "Invalid decision, or a note longer than 512 characters." }, - "color_button_1": { - "type": "string", - "description": "Primary button color." + "401": { + "description": "Missing or invalid x-api-key." }, - "color_button_2": { - "type": "string", - "description": "Secondary button color." + "403": { + "description": "The caller lacks the transactions permission." }, - "color_button_text_1": { - "type": "string", - "description": "Primary button text color." + "404": { + "description": "No submitted screenshot proof with this id in your application." }, - "color_button_text_2": { - "type": "string", - "description": "Secondary button text color." + "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" + } + } + } }, - "color_pill_text": { - "type": "string", - "description": "Text color for pills, inputs and selectors." + "200": { + "description": "The proof already open for this method, resumed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } }, - "border_radius_panel": { - "type": "integer", - "minimum": 0, - "description": "Panel corner radius in pixels." + "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" + } + } + } }, - "border_radius_buttons": { - "type": "integer", - "minimum": 0, - "description": "Button corner radius in pixels." + "403": { + "description": "SELF_DECLARATION is not allowed for this application." }, - "font_family": { - "type": "string", - "description": "Google Font family name (e.g. `Inter`, `AR One Sans`)." + "404": { + "description": "Unknown token." }, - "font_weight": { - "type": "string", - "description": "Font weight available for the selected family (e.g. `regular`, `500`, `700`)." + "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" + } + } + } }, - "app_public_name": { - "type": "string", - "nullable": true, - "description": "Public name shown in the verification flow." + "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" + } }, - "privacy_policy_url": { + { + "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": "Privacy policy URL shown to the end user." + "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" + } + } }, - "skip_welcome_screen": { - "type": "boolean", - "description": "Skip the initial welcome screen." + "x": { + "type": "integer", + "nullable": true, + "description": "Left edge of the bounding box in rendered-page pixels." }, - "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)." + "y": { + "type": "integer", + "nullable": true, + "description": "Top edge of the bounding box in rendered-page pixels." }, - "hide_progress_bar": { - "type": "boolean", - "description": "Hide the progress bar during the flow." + "width": { + "type": "integer", + "nullable": true, + "description": "Bounding-box width in rendered-page pixels." }, - "callback_seconds": { + "height": { "type": "integer", - "minimum": 0, - "description": "Delay (seconds) before redirecting to the callback URL." + "nullable": true, + "description": "Bounding-box height in rendered-page pixels." }, - "delete_logo_square": { - "type": "boolean", - "description": "When `true`, removes the current square logo." + "page_width": { + "type": "integer", + "description": "Rendered page width in pixels." }, - "delete_logo_rectangular": { - "type": "boolean", - "description": "When `true`, removes the current rectangular logo." + "page_height": { + "type": "integer", + "description": "Rendered page height in pixels." }, - "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." + "raw_payload": { + "type": "string", + "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." }, - "delete_favicon": { - "type": "boolean", - "description": "When `true`, removes the current favicon." + "parsed_payload": { + "nullable": true, + "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." } } }, - "BrandingCustomization": { + "SessionImportJob": { "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.", + "required": [ + "uuid", + "status", + "import_type", + "source_format", + "source_filename", + "total_rows", + "processed_rows", + "created_count", + "updated_count", + "skipped_count", + "failed_count" + ], "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" + "uuid": { + "type": "string", + "format": "uuid", + "description": "Import job id. Use this value to poll progress and fetch row errors." }, - "color_on_panel_2": { - "type": "string" + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED", + "PAUSED", + "CANCELLED", + "READY" + ] }, - "color_on_background": { - "type": "string" + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ] }, - "color_button_1": { - "type": "string" + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ] }, - "color_button_2": { + "source_filename": { "type": "string" }, - "color_button_text_1": { - "type": "string" + "total_rows": { + "type": "integer", + "format": "int64" }, - "color_button_text_2": { - "type": "string" + "processed_rows": { + "type": "integer", + "format": "int64" }, - "color_pill_text": { - "type": "string" + "checkpoint_row": { + "type": "integer", + "format": "int64" }, - "border_radius_panel": { - "type": "integer" + "created_count": { + "type": "integer", + "format": "int64" }, - "border_radius_buttons": { - "type": "integer" + "updated_count": { + "type": "integer", + "format": "int64" }, - "font_family": { - "type": "string" + "skipped_count": { + "type": "integer", + "format": "int64" }, - "font_weight": { - "type": "string" + "failed_count": { + "type": "integer", + "format": "int64" }, - "font_url": { - "type": "string", - "nullable": true, - "description": "Read-only. Resolved Google Fonts stylesheet URL for the selected family." + "total_media_bytes": { + "type": "integer", + "format": "int64" }, - "logo_square": { + "last_error": { "type": "string", - "nullable": true, - "description": "Read-only URL of the square logo. Upload via `image_square`." + "nullable": true }, - "logo_rectangular": { + "created_at": { "type": "string", - "nullable": true, - "description": "Read-only URL of the rectangular logo. Upload via `image_rectangular`." + "format": "date-time" }, - "logo_rectangular_dark": { + "updated_at": { "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`." + "format": "date-time" }, - "favicon": { + "started_at": { "type": "string", - "nullable": true, - "description": "Read-only URL of the favicon. Upload via `image_favicon`." + "format": "date-time", + "nullable": true }, - "app_public_name": { + "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 }, @@ -77026,123 +78633,19 @@ "networks": { "type": "object", "readOnly": true, - "description": "Effective membership configuration for each network. No network, including GTR, is included by default. This is not a live health or delivery check.", + "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": { - "type": "object", - "properties": { - "rail": { - "type": "string", - "enum": [ - "GTR" - ] - }, - "mode": { - "type": "string", - "enum": [ - "own", - "didit", - "off", - "unavailable" - ], - "description": "own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available." - }, - "platform_available": { - "type": "boolean", - "description": "Whether platform membership 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." - } - } + "$ref": "#/components/schemas/TravelRuleNetworkState" }, "trust": { - "type": "object", - "properties": { - "rail": { - "type": "string", - "enum": [ - "TRUST" - ] - }, - "mode": { - "type": "string", - "enum": [ - "own", - "didit", - "off", - "unavailable" - ], - "description": "own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available." - }, - "platform_available": { - "type": "boolean", - "description": "Whether platform membership 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." - } - } + "$ref": "#/components/schemas/TravelRuleNetworkState" }, "verifyvasp": { - "type": "object", - "properties": { - "rail": { - "type": "string", - "enum": [ - "VERIFYVASP" - ] - }, - "mode": { - "type": "string", - "enum": [ - "own", - "didit", - "off", - "unavailable" - ], - "description": "own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available." - }, - "platform_available": { - "type": "boolean", - "description": "Whether platform membership 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." - } - } + "$ref": "#/components/schemas/TravelRuleNetworkState" }, "sygna": { - "type": "object", - "properties": { - "rail": { - "type": "string", - "enum": [ - "SYGNA" - ] - }, - "mode": { - "type": "string", - "enum": [ - "own", - "didit", - "off", - "unavailable" - ], - "description": "own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available." - }, - "platform_available": { - "type": "boolean", - "description": "Whether platform membership 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." - } - } + "$ref": "#/components/schemas/TravelRuleNetworkState" } } }, @@ -77332,7 +78835,7 @@ }, "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." + "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", @@ -77387,7 +78890,7 @@ "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." + "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", @@ -77677,7 +79180,7 @@ }, "TravelRuleTransferPatchRequest": { "type": "object", - "description": "Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, or action=update_originator_wallet with wallet_address.", + "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", @@ -77689,9 +79192,10 @@ "cancel", "resend", "update_compliance_email", - "update_originator_wallet" + "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`)." + "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", @@ -77708,6 +79212,11 @@ "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." } } }, @@ -78912,7 +80421,7 @@ }, "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.", + "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": { @@ -78982,7 +80491,20 @@ "type": "string" }, "chain": { - "type": "string" + "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" @@ -78998,7 +80520,7 @@ "SELF_DECLARATION" ] }, - "description": "Proof methods available for this session. MESSAGE_SIGNING requires a supported chain family, SATOSHI_TEST requires a satoshi_deposit_address on the session, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting and an `ownership_validation_mode` that accepts a declaration for the linked transfer (never in `FULL`; in `SIMPLIFIED` only below `simplified_validation_threshold`). Initiating a SELF_DECLARATION the mode does not accept answers 403." + "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", @@ -79068,13 +80590,14 @@ "REJECTED", "EXPIRED" ], - "description": "PENDING on a SATOSHI_TEST proof means the deposit has been seen on-chain but has not yet reached enough confirmations; a background job automatically rechecks it and moves it to VERIFIED once it confirms, with no resubmission needed. REJECTED means a genuinely wrong deposit was submitted (absent, wrong sender, or wrong amount). EXPIRED means the proof's own test window closed before a valid deposit was seen." + "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" + "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", @@ -79099,11 +80622,23 @@ }, "rejection_reason": { "type": "string", - "description": "Why the proof was rejected. Present only when `status` is REJECTED." + "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." } } }, @@ -83559,10 +85094,11 @@ "not_requested", "pending", "delivered", + "not_held", "unavailable", null ], - "description": "`delivered` when the registry published ownership; `unavailable` when it did not (see `unavailable_reason`); `pending` while the registry is still fetching it; `not_requested` for the Basic tier." + "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", @@ -91500,6 +93036,15 @@ } } } + }, + "address_validation": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressValidationResult" + } + ], + "nullable": true, + "description": "Address Validation of the address printed on this document: whether it exists and its normalised form, as the document read it (a later correction of `address` does not run it again). Present when this ID verification step enabled the Address Validation add-on; `null` otherwise." } } } @@ -93011,6 +94556,15 @@ "items": { "$ref": "#/components/schemas/DocumentAIDetectedCode" } + }, + "address_validation": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressValidationResult" + } + ], + "nullable": true, + "description": "Address Validation of the address on this proof of address: whether it exists and its normalised form, as the document read it (a later correction of `poa_address` does not run it again). Present when this Proof of Address step enabled the Address Validation add-on and the upload passed the document checks; `null` otherwise." } } } @@ -94478,7 +96032,7 @@ "registry_ownership_status": { "type": "string", "nullable": true, - "description": "`not_requested`, `pending`, `delivered` or `unavailable`. Also in `ownership.status`." + "description": "`not_requested`, `pending`, `delivered`, `not_held` or `unavailable`. Also in `ownership.status`." } } }, @@ -95648,6 +97202,173 @@ } } }, + "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" } @@ -95794,22 +97515,22 @@ }, "is_bank_verification_enabled": { "type": "boolean", - "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps and before database validation, AML and IP analysis. Set in the console." + "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`). 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).", + "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. Never on KYB workflows. Set in the console." + "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`). Every key is optional and an absent key keeps its default, so `{}` means all defaults.", + "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": {} } }, @@ -98368,3492 +100089,4956 @@ } } }, - "score": { - "type": "number", - "format": "float", - "nullable": true, + "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, - "maximum": 100, - "description": "Overall AML screening score — the highest risk score across all hits (float, 0-100). `null` until computed." + "description": "Number of entries currently in the list." }, - "screened_data": { + "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" + "type": "string", + "description": "Document entries taken from a session: holder's full name." }, - "nationality": { - "type": "string" + "issuing_state": { + "type": "string", + "description": "Document entries taken from a session: ISO 3166-1 alpha-3 issuing country." }, - "date_of_birth": { + "document_type": { "type": "string", - "format": "date" + "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", - "nullable": true + "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." } } }, - "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." + "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." }, - "next_ongoing_monitoring_bill_date": { + "session_id": { "type": "string", - "format": "date", + "format": "uuid", + "description": "Verification session the item belongs to." + }, + "session_number": { + "type": "integer", "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." + "description": "Application-scoped number of that session, as shown in the console." }, - "last_ongoing_screening_at": { + "blocklisted_at": { "type": "string", "format": "date-time", + "description": "When the item was last blocklisted." + }, + "face_image": { + "type": "string", "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." + "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." }, - "last_ongoing_screening_datasets_updated_at": { + "document_type": { "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." + "description": "Document items: document type code, for example `P` (passport), `ID` (national ID), `DL` (driver's license) or `RP` (residence permit)." }, - "warnings": { - "type": "array", - "items": { - "type": "object", - "properties": { - "feature": { + "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", - "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" + "format": "email" }, - "node_id": { - "type": "string", - "description": "The workflow graph node ID that generated this warning" - } + "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." } } }, - "MonitoringEvent": { + "TransactionSummary": { "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" - ], + "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": { - "event_uuid": { + "uuid": { "type": "string", "format": "uuid", - "description": "Didit identifier of the event. Use it as `{event_uuid}` in the retrieve and delete calls." + "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." }, - "event_id": { + "transaction_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped sequential number shown in the Console." + }, + "txn_id": { "type": "string", - "maxLength": 128, - "description": "Your own identifier, the idempotency key you sent." + "nullable": true, + "description": "Your own identifier from the create call. Unique per application." }, - "event_category": { + "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": [ - "auth", - "session", - "gameplay", - "bonus", - "affiliate", - "payment", - "dispute", - "security", - "responsible_gambling", - "custom" + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" ], - "description": "Event category." + "description": "Current monitoring verdict." }, - "action_type": { + "source": { "type": "string", - "maxLength": 128, - "description": "What happened within the category, as you sent it." + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." }, - "occurred_at": { + "direction": { "type": "string", - "format": "date-time", - "description": "When the event happened on your side." + "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." }, - "received_at": { + "txn_date": { "type": "string", "format": "date-time", - "description": "When Didit accepted the event." + "description": "When the transaction occurred." }, - "status": { + "created_at": { "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." + "format": "date-time", + "description": "When Didit recorded it." }, "score": { "type": "integer", - "description": "Accumulated rule score. Starts at 0; rule `add_score` actions move it (negative deltas are allowed)." + "description": "Risk score. Higher is riskier." }, - "subject_vendor_data": { + "severity": { "type": "string", - "maxLength": 255, - "description": "`subject.vendor_data` of the event: your id for the user or company." + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, `null` until something sets it." }, - "subject_entity_type": { + "decision_reason_code": { "type": "string", - "description": "`individual` or `company`: which kind of subject `subject_vendor_data` names.", - "example": "individual" + "nullable": true, + "description": "Machine-readable reason for the current `status`." }, - "subject_uuid": { + "decision_reason_label": { "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." + "description": "Human-readable label for `decision_reason_code`." }, - "counterparty_vendor_data": { + "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": "The other player of a P2P event (promoted from `payload.counterparty_vendor_data`).", - "maxLength": 255 + "description": "Full name of the `APPLICANT` party." }, - "session_id": { + "subject_vendor_data": { "type": "string", "nullable": true, - "description": "`session.session_id` sent with the event.", - "maxLength": 255 + "description": "Your `vendor_data` for the applicant." }, - "device_fingerprint": { + "subject_type": { "type": "string", "nullable": true, - "description": "`device_context.fingerprint` sent with the event.", - "maxLength": 255 + "enum": [ + "user", + "business", + null + ], + "description": "Whether the applicant is linked to a Didit user or business; `null` when it is linked to neither." }, - "ip_address": { + "subject_uuid": { "type": "string", + "format": "uuid", "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 + "description": "UUID of the Didit user or business the applicant is linked to." }, - "ip_country": { + "subject_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" + "description": "Country code of the applicant." }, - "amount": { + "counterparty_name": { "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" + "description": "Full name of the `COUNTERPARTY` party, if any." }, - "currency": { + "counterparty_vendor_data": { "type": "string", "nullable": true, - "description": "Currency from `transaction_context.currency` or `payload.currency`.", - "maxLength": 10 + "description": "Your `vendor_data` for the counterparty." }, - "game_id": { + "counterparty_country": { "type": "string", "nullable": true, - "description": "Promoted from `payload.game_id`.", - "maxLength": 128 + "description": "Country code of the counterparty." }, - "round_id": { + "assigned_to_name": { "type": "string", "nullable": true, - "description": "Promoted from `payload.round_id`.", - "maxLength": 128 + "description": "Name of the Console reviewer assigned to the transaction." }, - "bonus_id": { + "travel_rule_status": { "type": "string", "nullable": true, - "description": "Promoted from `payload.bonus_id`.", - "maxLength": 128 + "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." }, - "affiliate_code": { + "application_id": { "type": "string", - "nullable": true, - "description": "Promoted from `payload.affiliate_code`.", - "maxLength": 128 + "format": "uuid", + "description": "Application that owns the transaction." }, - "payment_reference_id": { + "application_name": { "type": "string", "nullable": true, - "description": "Payment reference from `transaction_context.payment_reference_id` or `payload.payment_reference_id`.", - "maxLength": 255 + "description": "Name of that application." }, - "retention_class": { + "organization_id": { "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." + "format": "uuid", + "description": "Organization that owns the application." }, - "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`." + "organization_name": { + "type": "string", + "nullable": true, + "description": "Name of that organization." } } }, - "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": { + "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": { - "rule_uuid": { + "rounded_date": { "type": "string", - "format": "uuid", - "description": "UUID of the transaction monitoring rule." + "format": "date", + "description": "The day, `YYYY-MM-DD`." }, - "rule_title": { - "type": "string", - "description": "Title of the rule." + "total": { + "type": "integer", + "description": "Sessions created that day." }, - "library_key": { - "type": "string", - "nullable": true, - "description": "Library key when the rule was installed from the rules library, otherwise null." + "approved": { + "type": "integer", + "description": "Of those, sessions `Approved` or `Kyc Expired`." }, - "matched": { - "type": "boolean", - "description": "Always `true`: only matching runs are recorded." + "declined": { + "type": "integer", + "description": "Sessions `Declined`." }, - "mode": { - "type": "string", - "enum": [ - "ACTIVE", - "TEST" - ], - "description": "Mode of the rule when it ran. A `TEST` run records evidence but applied no actions." + "canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." }, - "is_async": { - "type": "boolean", - "description": "`true` when the run came from the asynchronous correlation pass rather than the ingest request." + "pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review`." }, - "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." + "in_review": { + "type": "integer", + "description": "Sessions `In Review`." }, - "score_delta": { + "expired": { "type": "integer", - "description": "How much this run changed the event's score (0 for `TEST` runs)." + "description": "Sessions `Expired`." }, - "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." + "abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." }, - "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 - } + "in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the rule run was recorded." + "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." } - }, - "required": [ - "rule_uuid", - "rule_title", - "library_key", - "matched", - "mode", - "is_async", - "severity", - "score_delta", - "action_summary", - "matched_payload", - "created_at" - ] + } }, - "MonitoringEventPage": { + "ApplicationAnalyticsRequestBreakdown": { "type": "object", - "description": "One page of monitoring events, newest `occurred_at` first.", + "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", "required": [ - "count", - "next", - "previous", - "results" + "total_approved", + "total_in_review", + "total_declined", + "total_canceled", + "total_pending" ], "properties": { - "count": { + "total_approved": { "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." + "description": "Sessions `Approved` or `Kyc Expired`." }, - "next": { - "type": "string", - "format": "uri", - "nullable": true, - "description": "URL of the next page, or null on the last page." + "total_in_review": { + "type": "integer", + "description": "Sessions `In Review`." }, - "previous": { - "type": "string", - "format": "uri", - "nullable": true, - "description": "URL of the previous page, or null on the first page." + "total_declined": { + "type": "integer", + "description": "Sessions `Declined`." }, - "results": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MonitoringEvent" - } + "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." } } }, - "MonitoringEventCreateRequest": { + "ApplicationAnalyticsCountryCount": { "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": { + "country": { "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" + "description": "Country name." }, - "event_category": { + "country_code": { "type": "string", - "enum": [ - "auth", - "session", - "gameplay", - "bonus", - "affiliate", - "payment", - "dispute", - "security", - "responsible_gambling", - "custom" - ], - "description": "Event category.", - "example": "auth" + "description": "Two-letter country code." }, - "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" + "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." }, - "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" + "countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Same list as `ip_countries`." }, - "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." + "id_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Issuing countries of the verified ID documents, most sessions first." }, - "subject": { + "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": "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" - ], + "description": "Sessions per age band from the ID document. Empty when there is no data.", "properties": { - "vendor_data": { - "type": "string", - "minLength": 1, - "maxLength": 255, - "description": "Your id for the subject.", - "example": "user-42" + "under_18": { + "type": "integer" }, - "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" + "18_24": { + "type": "integer" }, - "full_name": { - "type": "string", - "nullable": true, - "maxLength": 512, - "description": "Display name, used only when this event creates the user or company." + "25_34": { + "type": "integer" }, - "email": { - "type": "string", - "format": "email", - "nullable": true, - "description": "Validated as an email address; not stored on the event." + "35_44": { + "type": "integer" }, - "phone": { - "type": "string", - "nullable": true, - "maxLength": 32, - "description": "Validated; not stored on the event." + "45_64": { + "type": "integer" }, - "country": { - "type": "string", - "nullable": true, - "maxLength": 3, - "description": "Validated; not stored on the event." + "65_plus": { + "type": "integer" } + }, + "additionalProperties": { + "type": "integer" } }, - "business": { + "gender_distribution": { "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" + "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" + } } } }, - "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." + "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" + } } } }, - "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" - } + "operating_systems": { + "type": "array", + "description": "Operating systems.", + "items": { + "type": "object", + "properties": { + "os": { + "type": "string" + }, + "count": { + "type": "integer" } } } - }, - "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`." + } + } + }, + "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." }, - "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." + "overall_count": { + "type": "integer", + "description": "Sessions measured." }, - "custom_values": { + "per_feature": { "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.", + "description": "Keyed by feature, for example `OCR` or `LIVENESS`.", "additionalProperties": { - "nullable": true, - "oneOf": [ - { - "type": "string" + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" }, - { + "total_seconds": { "type": "number" }, - { - "type": "boolean" + "count": { + "type": "integer" } - ] + } } - } - }, - "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" + "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" + } + } + } } - }, - "payload": { - "method": "password" - }, - "custom_values": { - "vip_tier": "gold" } } }, - "MonitoringEventIngestResult": { + "ApplicationAnalyticsResubmissionStats": { "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": { + "first_pass": { "type": "integer", - "description": "Score after the real-time rules ran." + "description": "Finished sessions with no resubmission." }, - "rules_evaluated_count": { + "second": { "type": "integer", - "minimum": 0, - "description": "Number of event rules evaluated in the request. 0 in a sandbox application, where no rules run." + "description": "Finished sessions resubmitted once." }, - "rules_matched_count": { + "third_plus": { "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." + "description": "Finished sessions resubmitted two or more times." }, - "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." + "abandoned": { + "type": "integer", + "description": "Abandoned sessions." + } + } + }, + "ApplicationAnalyticsOriginalStatusStats": { + "type": "object", + "description": "The workflow's automatic verdict, before any reviewer changed it.", + "properties": { + "approved": { + "type": "integer" }, - "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." + "in_review": { + "type": "integer" }, - "rule_matches": { - "type": "array", - "description": "Rules that matched the event.", - "items": { + "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", - "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." + "additionalProperties": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "information": { + "type": "integer" + } } } } } } }, - "ComplianceList": { + "ApplicationAnalyticsDemographicPoint": { "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": { + "date": { "type": "string", - "description": "Free-text description. Empty string when not set." + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." }, - "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\"`)." + "age_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } }, - "entry_type": { + "gender_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDevicePoint": { + "type": "object", + "properties": { + "date": { "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." + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." }, - "last_entry_added_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When the newest current entry was added. `null` for an empty list." + "devices": { + "type": "object", + "additionalProperties": { + "type": "integer" + } }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the list was created." + "browsers": { + "type": "object", + "additionalProperties": { + "type": "integer" + } }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "When the list was last updated." + "operating_systems": { + "type": "object", + "additionalProperties": { + "type": "integer" + } } } }, - "ComplianceListEntry": { + "ApplicationAnalyticsVerificationTimePoint": { "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": { + "date": { "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." + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." }, - "comment": { - "type": "string", - "description": "Free-text note. Empty string when not set." + "overall_avg": { + "type": "number", + "description": "Average active seconds per session." }, - "added_by": { + "per_feature": { + "type": "object", + "description": "Average active seconds per feature.", + "additionalProperties": { + "type": "number" + } + } + } + }, + "ApplicationAnalyticsResubmissionPoint": { + "type": "object", + "properties": { + "date": { "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)." + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." }, - "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." + "first_pass": { + "type": "integer" }, - "reference_session_number": { - "type": "integer", - "nullable": true, - "description": "Application-scoped number of that session, as shown in the console." + "second": { + "type": "integer" }, - "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." + "third_plus": { + "type": "integer" }, - "reference_object_uuid": { + "abandoned": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsOriginalStatusPoint": { + "type": "object", + "properties": { + "date": { "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`)." + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." }, - "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." - } - } + "approved": { + "type": "integer" }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the entry was added." + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" } } }, - "BlocklistedItem": { + "ApplicationAnalyticsSummary": { "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`.", + "description": "User verification (KYC) summary, returned when neither `scope` nor `widget_keys` is sent.", "required": [ - "type", - "session_id", - "session_number", - "blocklisted_at", - "face_image", - "document_type", - "document_number", - "front_image", - "phone_number", - "country_code", - "email" + "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": { - "type": { - "type": "string", - "enum": [ - "face", - "document", - "phone", - "email" - ], - "description": "What was blocklisted." + "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." }, - "session_id": { - "type": "string", - "format": "uuid", - "description": "Verification session the item belongs to." + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" }, - "session_number": { - "type": "integer", - "nullable": true, - "description": "Application-scoped number of that session, as shown in the console." + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "Daily points for the period of the same length right before `start_date`." }, - "blocklisted_at": { - "type": "string", - "format": "date-time", - "description": "When the item was last blocklisted." + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" }, - "face_image": { - "type": "string", + "conversion_rate": { + "type": "number", "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." + "description": "`total_approved / (total_approved + total_declined + total_in_review) x 100`, one decimal. `null` when nothing finished." }, - "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)." + "location_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsLocationStats" }, - "document_number": { - "type": "string", - "nullable": true, - "description": "Document items: the document number, or the personal number when the document has none." + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" }, - "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`." + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" }, - "phone_number": { - "type": "string", - "nullable": true, - "description": "Phone items: the phone number in E.164." + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" }, - "country_code": { - "type": "string", - "nullable": true, - "description": "Phone items: ISO 3166-1 alpha-2 country of the number." + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" }, - "email": { + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "grouping_mode": { "type": "string", - "nullable": true, - "description": "Email items: the email address." + "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" + } } } }, - "TransactionNote": { + "ApplicationAnalyticsWidget": { "type": "object", - "description": "One note on a monitored transaction's review timeline.", + "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": [ - "uuid", - "note", - "actor_email", - "actor_name", - "metadata", - "created_at" + "kind", + "title" ], "properties": { - "uuid": { + "kind": { "type": "string", - "format": "uuid", - "description": "Note identifier." + "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." }, - "note": { + "title": { "type": "string", - "description": "Plain-text note body." + "description": "Display title, for example `Verification volume`." }, - "actor_email": { + "grouping_mode": { "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." + "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." }, - "actor_name": { + "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": "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." + "description": "Transaction volume and amount widgets: the application's default transaction currency." }, - "metadata": { + "thresholds": { "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.", + "description": "`histogram` widget: the application's review and decline score thresholds.", "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." + "review": { + "type": "integer" }, - "rule_uuid": { - "type": "string", - "format": "uuid", - "description": "Rule whose `add_note` action wrote the note (with `source: rule_action`)." + "decline": { + "type": "integer" } } }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the note was stored." + "search": { + "type": "object", + "description": "`kyb_registry_coverage` widget: registry search totals.", + "properties": { + "searches_total": { + "type": "integer" + }, + "searches_with_no_candidates": { + "type": "integer" + } + } } } }, - "TransactionSummary": { + "ApplicationAnalyticsWidgetResponse": { "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.", + "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", + "required": [ + "scope", + "available_widget_keys", + "requested_widget_keys", + "widgets" + ], "properties": { - "uuid": { + "scope": { "type": "string", - "format": "uuid", - "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ], + "description": "Dashboard tab." }, - "transaction_number": { - "type": "integer", - "nullable": true, - "description": "Application-scoped sequential number shown in the Console." + "available_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every widget key this scope accepts." }, - "txn_id": { - "type": "string", - "nullable": true, - "description": "Your own identifier from the create call. Unique per application." + "requested_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." }, - "transaction_type": { + "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", - "description": "Category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange` or `auditTrailEvent`." + "format": "uuid", + "description": "Destination ID." }, - "action_type": { + "label": { "type": "string", - "description": "Sub-type within the category, for example `withdrawal`." + "maxLength": 255, + "description": "Human-readable name." }, - "status": { + "url": { "type": "string", - "enum": [ - "APPROVED", - "IN_REVIEW", - "DECLINED", - "AWAITING_USER" - ], - "description": "Current monitoring verdict." + "format": "uri", + "maxLength": 2000, + "description": "HTTPS endpoint Didit posts events to." }, - "source": { - "type": "string", - "enum": [ - "LIVE", - "IMPORTED" - ], - "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." + "enabled": { + "type": "boolean", + "description": "`false` pauses deliveries to this destination." }, - "direction": { + "webhook_version": { "type": "string", "enum": [ - "INBOUND", - "OUTBOUND" + "v1", + "v2", + "v3" ], - "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`." + "description": "Payload version sent to this destination." }, - "currency_type": { + "secret_shared_key": { "type": "string", - "nullable": true, - "description": "`fiat` or `crypto` when known." + "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." }, - "preferred_currency_amount": { + "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", - "nullable": true, - "description": "`amount` converted to the application's preferred currency, when a rate was available." + "format": "date-time" }, - "preferred_currency_code": { + "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": "Currency of `preferred_currency_amount`." + "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." }, - "payment_details": { + "webhook_version": { "type": "string", - "nullable": true, - "description": "Free-text payment reference." + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." }, - "txn_date": { + "secret_shared_key": { "type": "string", - "format": "date-time", - "description": "When the transaction occurred." + "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." }, - "created_at": { + "capture_method": { "type": "string", - "format": "date-time", - "description": "When Didit recorded it." + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." }, - "score": { + "data_retention_months": { "type": "integer", - "description": "Risk score. Higher is riskier." + "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)." }, - "severity": { + "face_retention_policy": { "type": "string", - "nullable": true, "enum": [ - "UNKNOWN", - "LOW", - "MEDIUM", - "HIGH", - "CRITICAL", - null + "delete_with_session", + "retain_until_user_deleted" ], - "description": "Risk severity, `null` until something sets it." + "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." }, - "decision_reason_code": { - "type": "string", + "face_retention_days": { + "type": "integer", "nullable": true, - "description": "Machine-readable reason for the current `status`." + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`. Required when that policy is set." }, - "decision_reason_label": { + "biometric_consent_mode": { "type": "string", - "nullable": true, - "description": "Human-readable label for `decision_reason_code`." + "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)." }, - "tags": { + "available_events": { "type": "array", - "description": "Tags on the transaction.", + "description": "Every event type a destination can subscribe to, with a display label.", "items": { "type": "object", + "required": [ + "value", + "label" + ], "properties": { - "uuid": { - "type": "string", - "format": "uuid" - }, - "name": { - "type": "string" - }, - "color": { - "type": "string", - "description": "Hex color, for example `#F59E0B`." - }, - "description": { - "type": "string", - "nullable": true + "value": { + "$ref": "#/components/schemas/WebhookEvent" }, - "source": { + "label": { "type": "string", - "enum": [ - "didit", - "custom" - ], - "description": "Didit preset or custom tag." + "description": "Display label, for example `Session status updated`." } } } }, - "subject_name": { + "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", - "nullable": true, - "description": "Full name of the `APPLICANT` party." + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." }, - "subject_vendor_data": { + "session_id": { "type": "string", + "format": "uuid", "nullable": true, - "description": "Your `vendor_data` for the applicant." + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." }, - "subject_type": { + "request_url": { "type": "string", + "format": "uri", "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." + "description": "URL the delivery was sent to: the destination's URL when the event was generated." }, - "subject_uuid": { - "type": "string", - "format": "uuid", + "response_status": { + "type": "integer", "nullable": true, - "description": "UUID of the Didit user or business the applicant is linked to." + "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." }, - "subject_country": { - "type": "string", + "response_time_ms": { + "type": "integer", "nullable": true, - "description": "Country code of the applicant." + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." }, - "counterparty_name": { + "status": { "type": "string", "nullable": true, - "description": "Full name of the `COUNTERPARTY` party, if any." + "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." }, - "counterparty_vendor_data": { + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { "type": "string", - "nullable": true, - "description": "Your `vendor_data` for the counterparty." + "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." }, - "counterparty_country": { + "session_id": { "type": "string", + "format": "uuid", "nullable": true, - "description": "Country code of the counterparty." + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." }, - "assigned_to_name": { + "request_url": { "type": "string", + "format": "uri", "nullable": true, - "description": "Name of the Console reviewer assigned to the transaction." + "description": "URL the delivery was sent to: the destination's URL when the event was generated." }, - "travel_rule_status": { - "type": "string", + "response_status": { + "type": "integer", "nullable": true, - "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." + "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." }, - "application_id": { - "type": "string", - "format": "uuid", - "description": "Application that owns the transaction." + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." }, - "application_name": { + "status": { "type": "string", "nullable": true, - "description": "Name of that application." + "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." }, - "organization_id": { + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { "type": "string", - "format": "uuid", - "description": "Organization that owns the application." + "format": "date-time", + "description": "When the delivery was created." }, - "organization_name": { + "request_header": { "type": "string", - "nullable": true, - "description": "Name of that organization." + "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." } } }, - "ApplicationAnalyticsDataPoint": { + "SessionWebhookDelivery": { "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.", + "description": "One webhook delivery attempt recorded for a verification session, with the exact request headers and body Didit sent.", "required": [ - "rounded_date", - "total", - "approved", - "declined", - "canceled", - "pending" + "uuid", + "session_id", + "request_url", + "response_status", + "status", + "webhook_type", + "request_header", + "request_body", + "created_at" ], "properties": { - "rounded_date": { + "uuid": { "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`." + "format": "uuid", + "description": "Delivery ID. Use it as `{webhook_uuid}` to get or resend this delivery." }, - "pending": { - "type": "integer", - "description": "Sessions `Not Started`, `In Progress` or `In Review`." + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the delivery belongs to (the session in the path)." }, - "in_review": { - "type": "integer", - "description": "Sessions `In Review`." + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to." }, - "expired": { + "response_status": { "type": "integer", - "description": "Sessions `Expired`." + "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." }, - "abandoned": { - "type": "integer", - "description": "Sessions `Abandoned`." + "status": { + "type": "string", + "nullable": true, + "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." }, - "in_progress": { - "type": "integer", - "description": "Sessions `In Progress`." + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" }, - "not_started": { - "type": "integer", - "description": "Sessions `Not Started`." + "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." }, - "manually_approved": { - "type": "integer", - "description": "Sessions a reviewer approved after the workflow gave another verdict." + "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." }, - "manually_declined": { - "type": "integer", - "description": "Sessions a reviewer declined after the workflow gave another verdict." + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." } } }, - "ApplicationAnalyticsRequestBreakdown": { + "SessionEvent": { "type": "object", - "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", + "description": "One entry of a verification session's event timeline.", "required": [ - "total_approved", - "total_in_review", - "total_declined", - "total_canceled", - "total_pending" + "uuid", + "event_type", + "created_at", + "component", + "feature_step", + "step", + "details", + "ip_address", + "user_agent", + "device_info", + "file_url", + "sent_at" ], "properties": { - "total_approved": { - "type": "integer", - "description": "Sessions `Approved` or `Kyc Expired`." + "uuid": { + "type": "string", + "format": "uuid", + "description": "Event identifier." }, - "total_in_review": { - "type": "integer", - "description": "Sessions `In Review`." + "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." }, - "total_declined": { - "type": "integer", - "description": "Sessions `Declined`." + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." }, - "total_canceled": { - "type": "integer", - "description": "Sessions `Expired` or `Abandoned`." + "component": { + "type": "string", + "nullable": true, + "description": "UI component that reported the event (device events only)." }, - "total_pending": { - "type": "integer", - "description": "Sessions `Not Started`, `In Progress` or `In Review` (includes `total_in_review`)." + "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." }, - "total_expired": { - "type": "integer", - "description": "Sessions `Expired`." + "step": { + "type": "string", + "nullable": true, + "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." }, - "total_abandoned": { - "type": "integer", - "description": "Sessions `Abandoned`." + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific data. The keys depend on `event_type`." }, - "total_in_progress": { - "type": "integer", - "description": "Sessions `In Progress`." + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address the device reported the event from. `null` for server-recorded events." }, - "total_not_started": { - "type": "integer", - "description": "Sessions `Not Started`." + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the device. `null` for server-recorded events." }, - "total_manually_approved": { - "type": "integer", - "description": "Approved by a reviewer." + "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`." }, - "total_manually_declined": { + "sent_at": { "type": "integer", - "description": "Declined by a reviewer." + "format": "int64", + "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." } } }, - "ApplicationAnalyticsCountryCount": { + "AMLDatasetSourceFreshness": { "type": "object", + "description": "Freshness of one family of AML datasets.", + "required": [ + "last_updated_at", + "oldest_updated_at", + "dataset_count", + "dated_dataset_count" + ], "properties": { - "country": { + "last_updated_at": { "type": "string", - "description": "Country name." + "format": "date-time", + "description": "Most recent update of any dataset in the family." }, - "country_code": { + "oldest_updated_at": { "type": "string", - "description": "Two-letter country code." + "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." }, - "session_count": { + "dataset_count": { "type": "integer", - "description": "Sessions from that country." + "description": "Number of datasets in the family." + }, + "dated_dataset_count": { + "type": "integer", + "description": "Number of those datasets that reported an update date." } } }, - "ApplicationAnalyticsLocationStats": { + "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": { - "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`." + "captured_at": { + "type": "string", + "format": "date-time", + "description": "When Didit read the dataset catalog this freshness comes from." }, - "id_countries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" - }, - "description": "Issuing countries of the verified ID documents, most sessions first." + "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" }, - "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" - } - } - } + "catalog_version": { + "type": "string", + "nullable": true, + "description": "Version identifier of the dataset catalog, when the source reports one." }, - "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": { + "sources": { "type": "object", - "description": "Sessions per age band from the ID document. Empty when there is no data.", + "description": "Freshness per dataset family. A family whose datasets reported no update date is absent rather than filled with `null`.", "properties": { - "under_18": { - "type": "integer" - }, - "18_24": { - "type": "integer" - }, - "25_34": { - "type": "integer" + "sanctions": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" }, - "35_44": { - "type": "integer" + "pep": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" }, - "45_64": { - "type": "integer" + "warnings": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" }, - "65_plus": { - "type": "integer" + "adverse_media": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" } - }, - "additionalProperties": { - "type": "integer" - } - }, - "gender_distribution": { - "type": "object", - "description": "Sessions per document gender code: `M`, `F` or `U` (unknown).", - "additionalProperties": { - "type": "integer" } } } }, - "ApplicationAnalyticsDeviceDetails": { + "AMLScreeningRun": { "type": "object", - "description": "Top five of each, most sessions first.", + "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": { - "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" - } - } - } + "uuid": { + "type": "string", + "format": "uuid", + "description": "Identifier of this screening run." }, - "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" - } - } - } + "completed_at": { + "type": "string", + "format": "date-time", + "description": "When this subject's re-screen finished. Results are ordered by it, newest first." }, - "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." + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the monitoring batch this re-screen belongs to started." }, - "overall_count": { - "type": "integer", - "description": "Sessions measured." + "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." }, - "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" - } - } - } + "trigger": { + "type": "string", + "enum": [ + "ONGOING_MONITORING" + ], + "description": "What started the screening. Always `ONGOING_MONITORING` today." }, - "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." + "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" }, - "second": { - "type": "integer", - "description": "Finished sessions resubmitted once." + "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." }, - "third_plus": { - "type": "integer", - "description": "Finished sessions resubmitted two or more times." + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "AML score this run left the check in." }, - "abandoned": { + "total_hits": { "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" + "nullable": true, + "description": "Number of hits this run left the check in." }, - "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" - } - } + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" } - } + ], + "nullable": true, + "description": "Dataset freshness behind this run, or `null` when no dates were available." } } }, - "ApplicationAnalyticsDemographicPoint": { + "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": { - "date": { + "uuid": { "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" - } + "format": "uuid", + "description": "Party identifier." }, - "gender_distribution": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - } - } - }, - "ApplicationAnalyticsDevicePoint": { - "type": "object", - "properties": { - "date": { + "entity_type": { "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" - } + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." }, - "browsers": { - "type": "object", - "additionalProperties": { - "type": "integer" - } + "source": { + "type": "string", + "enum": [ + "REGISTRY", + "USER" + ], + "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." }, - "operating_systems": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - } - } - }, - "ApplicationAnalyticsVerificationTimePoint": { - "type": "object", - "properties": { - "date": { + "name": { "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`." + "description": "Display name of the party." }, - "overall_avg": { - "type": "number", - "description": "Average active seconds per session." + "first_name": { + "type": "string", + "nullable": true, + "description": "First name (persons)." }, - "per_feature": { - "type": "object", - "description": "Average active seconds per feature.", - "additionalProperties": { - "type": "number" - } - } - } - }, - "ApplicationAnalyticsResubmissionPoint": { - "type": "object", - "properties": { - "date": { + "last_name": { + "type": "string", + "nullable": true, + "description": "Last name (persons)." + }, + "company_name": { "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`." + "nullable": true, + "description": "Company name (company parties)." }, - "first_pass": { - "type": "integer" + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number (company parties)." }, - "second": { - "type": "integer" + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email the verification link is sent to." }, - "third_plus": { - "type": "integer" + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons, country of incorporation for companies." }, - "abandoned": { - "type": "integer" - } - } - }, - "ApplicationAnalyticsOriginalStatusPoint": { - "type": "object", - "properties": { - "date": { + "phone_number": { "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`." + "nullable": true, + "description": "Phone number collected for the party." }, - "approved": { - "type": "integer" + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth (persons)." }, - "in_review": { - "type": "integer" + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title (persons)." }, - "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." + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined custom fields collected for the party, keyed by field key." }, - "request_breakdown": { - "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + "is_active": { + "type": "boolean", + "description": "Whether the party is active." }, - "previous_data_points": { + "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": { - "$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" + "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." + } + } + } }, - "conversion_rate": { - "type": "number", + "kyc_session_id": { + "type": "string", + "format": "uuid", "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" + "description": "Child individual KYC session (persons)." }, - "demographic_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + "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." }, - "device_details": { - "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + "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`." }, - "workflow_funnel_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + "kyc_session_deleted": { + "type": "boolean", + "description": "The child KYC session was deleted, so it can never settle." }, - "verification_time_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB session (company parties)." }, - "resubmission_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + "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." }, - "warning_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + "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`." }, - "original_status_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Company-party counterpart of `kyc_session_deleted`." }, - "grouping_mode": { + "verification_workflow_type": { "type": "string", + "nullable": true, "enum": [ - "day", - "week", - "month" + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null ], - "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" - } + "description": "Type of the workflow the child session runs." }, - "device_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" - } + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow the child session runs." }, - "verification_time_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" - } + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Label of that workflow." }, - "resubmission_time_series": { + "verification_workflow_features": { "type": "array", + "nullable": true, + "description": "Features of the child session's workflow with their current status.", "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + "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." + } + } } }, - "original_status_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" - } + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's identifier of the verified person, once known." } } }, - "ApplicationAnalyticsWidget": { + "TravelRuleDelivery": { "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`.", + "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", - "title" + "status", + "rail", + "attempts", + "last_error", + "next_attempt_at", + "delivered_at" ], "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" + "SEND", + "ANSWER", + "TXID", + "CANCEL", + "POLL" ], - "description": "Widget type." + "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." }, - "title": { + "status": { "type": "string", - "description": "Display title, for example `Verification volume`." + "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." }, - "grouping_mode": { + "rail": { "type": "string", "enum": [ - "day", - "week", - "month" + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "" ], - "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." + "description": "The rail that carries the message. Empty until Didit has chosen one for a first message." }, - "data_points": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" - } + "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." }, - "request_breakdown": { - "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + "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" }, - "previous_data_points": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" - } + "next_attempt_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." }, - "previous_request_breakdown": { - "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + "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." }, - "conversion_rate": { - "type": "number", + "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 }, - "workflow_funnel_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + "required": { + "type": "boolean" }, - "verification_time_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + "obligations_count": { + "type": "integer" }, - "verification_time_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" - } + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." }, - "warning_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + "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." }, - "resubmission_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" }, - "resubmission_time_series": { + "mismatched_fields": { "type": "array", "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" - } + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." }, - "original_status_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" }, - "original_status_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" - } + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." }, - "demographic_stats": { - "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + "metadata": { + "type": "object" }, - "demographic_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" - } + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Which side of the exchange this transfer is." }, - "device_details": { - "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + "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." }, - "device_time_series": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" - } + "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." }, - "items": { + "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": "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`.", + "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": { - "type": "object", - "additionalProperties": {} + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" } }, - "previous_items": { + "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": "Transaction time series for the period of the same length right before `start_date`.", + "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": { - "type": "object", - "additionalProperties": {} + "$ref": "#/components/schemas/TravelRuleNextAction" } }, - "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": { + "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": "`histogram` widget: the application's review and decline score thresholds.", + "description": "Visible networks by product.", "properties": { - "review": { + "all": { "type": "integer" }, - "decline": { - "type": "integer" + "identity": { + "type": "integer", + "description": "Every other network." + }, + "transactions": { + "type": "integer", + "description": "Networks whose members are all transaction parties." } } }, - "search": { + "views": { "type": "object", - "description": "`kyb_registry_coverage` widget: registry search totals.", + "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", "properties": { - "searches_total": { + "all": { "type": "integer" }, - "searches_with_no_candidates": { + "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" + } + } } } } } }, - "ApplicationAnalyticsWidgetResponse": { + "NetworkTag": { "type": "object", - "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", - "required": [ - "scope", - "available_widget_keys", - "requested_widget_keys", - "widgets" - ], + "description": "One of your application's tags on the network.", "properties": { - "scope": { + "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": [ - "home", - "kyc", - "kyb", - "transactions" + "didit", + "custom" ], - "description": "Dashboard tab." - }, - "available_widget_keys": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Every widget key this scope accepts." + "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." }, - "requested_widget_keys": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." + "added_by": { + "type": "string", + "nullable": true, + "description": "Email of the console member who added the tag. Null when an API key added it." }, - "widgets": { - "type": "object", - "description": "Keyed by widget key, in the order of `requested_widget_keys`.", - "additionalProperties": { - "$ref": "#/components/schemas/ApplicationAnalyticsWidget" - } + "added_at": { + "type": "string", + "format": "date-time" } } }, - "WebhookConfigDestination": { + "NetworkOutcomes": { "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" - ], + "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": { - "uuid": { - "type": "string", - "format": "uuid", - "description": "Destination ID." + "approved": { + "type": "integer", + "description": "Members whose most recent decided verification was approved." }, - "label": { - "type": "string", - "maxLength": 255, - "description": "Human-readable name." + "declined": { + "type": "integer", + "description": "Members whose most recent decided verification was declined." }, - "url": { + "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", - "format": "uri", - "maxLength": 2000, - "description": "HTTPS endpoint Didit posts events to." + "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." }, - "enabled": { - "type": "boolean", - "description": "`false` pauses deliveries to this destination." + "score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." }, - "webhook_version": { + "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": [ - "v1", - "v2", - "v3" + "computing", + "not_enough_selfies" ], - "description": "Payload version sent to this destination." + "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." }, - "secret_shared_key": { + "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", - "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": { + "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", - "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" + "$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" } }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "updated_at": { + "next_cursor": { "type": "string", - "format": "date-time" + "nullable": true, + "description": "Pass as `cursor` to read the next page. Null on the last page." } } }, - "WebhookConfig": { + "NetworkTypedTimelineEvent": { "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" - ], + "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": { - "webhook_url": { + "id": { "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." + "description": "`:`, unique within the timeline.", + "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" }, - "webhook_version": { + "event_type": { "type": "string", "enum": [ - "v1", - "v2", - "v3" - ], - "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] }, - "secret_shared_key": { + "occurred_at": { "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." + "format": "date-time", + "description": "When it happened." }, - "capture_method": { + "ingested_at": { "type": "string", - "enum": [ - "MOBILE", - "MOBILE_AND_DESKTOP" + "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" + } ], - "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." + "nullable": true, + "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." }, - "data_retention_months": { - "type": "integer", + "source_ref": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineSourceRef" + } + ], "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)." + "description": "The source record. Null for the network's own events and `note_added`." }, - "face_retention_policy": { - "type": "string", - "enum": [ - "delete_with_session", - "retain_until_user_deleted" + "verification_attempt": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAttempt" + } ], - "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." + "nullable": true, + "description": "Set on `verification_attempt` events." }, - "face_retention_days": { - "type": "integer", + "transaction": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineTransaction" + } + ], "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." + "description": "Set on `transaction` events." }, - "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`." - } + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineLogin" } - } + ], + "nullable": true, + "description": "Set on `login` events." }, - "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 - } + "rule_hit": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRuleHit" + } + ], + "nullable": true, + "description": "Set on `rule_hit` events." }, - "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" - } + "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." } } }, - "WebhookDestinationDelivery": { + "NetworkTimelineAccount": { "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" - ], + "description": "The member account the event belongs to.", "properties": { - "uuid": { + "node_id": { "type": "string", - "format": "uuid", - "description": "Delivery ID, for the detail and resend calls." + "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", + "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" }, - "session_id": { + "node_type": { "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." + "enum": [ + "account", + "business_account", + "transaction_party" + ] }, - "request_url": { + "uuid": { "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." + "format": "uuid" }, - "status": { + "vendor_data": { "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." + "nullable": true }, - "webhook_type": { - "$ref": "#/components/schemas/WebhookEvent" + "display_name": { + "type": "string", + "nullable": true }, - "created_at": { + "ownership": { "type": "string", - "format": "date-time", - "description": "When the delivery was created." + "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" } } }, - "WebhookDestinationDeliveryDetail": { + "NetworkTimelineSourceRef": { "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" - ], + "description": "The record the event was read from.", "properties": { - "uuid": { - "type": "string", - "format": "uuid", - "description": "Delivery ID, for the detail and resend calls." - }, - "session_id": { + "kind": { "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." + "enum": [ + "session", + "business_session", + "transaction", + "monitoring_event", + "case" + ] }, - "request_url": { + "id": { "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." + "format": "uuid" }, - "status": { + "ownership": { "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" + "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." }, - "created_at": { + "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", - "format": "date-time", - "description": "When the delivery was created." + "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." }, - "request_header": { + "id": { "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." + "format": "uuid" }, - "request_body": { + "application_id": { "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." + "format": "uuid" } } }, - "SessionWebhookDelivery": { + "NetworkTimelineAttempt": { "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" - ], + "description": "One verification session of the account.", "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)." + "description": "The session UUID, or the business session UUID for a company." }, - "request_url": { - "type": "string", - "format": "uri", - "nullable": true, - "description": "URL the delivery was sent to." + "session_number": { + "type": "integer", + "nullable": true }, - "response_status": { + "attempt": { "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." + "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", - "nullable": true, - "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." - }, - "webhook_type": { - "$ref": "#/components/schemas/WebhookEvent" + "description": "The session status, for example `Approved` or `Declined`." }, - "request_header": { + "status_changed_at": { "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." + "format": "date-time", + "nullable": true }, - "request_body": { + "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": "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." + "description": "The warning code." }, - "created_at": { + "feature": { "type": "string", - "format": "date-time", - "description": "When the delivery was created." + "description": "The feature that raised the warning." + }, + "short_description": { + "type": "string" } } }, - "SessionEvent": { + "NetworkTimelineTransaction": { "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" - ], + "description": "A transaction a member account took part in, dated by its transaction date.", "properties": { - "uuid": { + "transaction_id": { "type": "string", - "format": "uuid", - "description": "Event identifier." + "format": "uuid" }, - "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" + "transaction_number": { + "type": "integer", + "nullable": true + }, + "transaction_type": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAmount" + } ], - "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." + "nullable": true, + "description": "Null when the transaction has no amount." }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." + "status": { + "type": "string" }, - "component": { + "decision_reason_code": { "type": "string", - "nullable": true, - "description": "UI component that reported the event (device events only)." + "nullable": true }, - "feature_step": { + "decision_reason_label": { "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." + "nullable": true }, - "step": { + "party_role": { "type": "string", - "nullable": true, - "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." + "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." }, - "details": { - "type": "object", - "nullable": true, - "additionalProperties": true, - "description": "Event-specific data. The keys depend on `event_type`." + "currency": { + "type": "string" }, - "ip_address": { + "direction": { "type": "string", - "nullable": true, - "description": "IP address the device reported the event from. `null` for server-recorded events." + "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." }, - "user_agent": { + "succeeded": { + "type": "boolean" + }, + "status": { "type": "string", - "nullable": true, - "description": "User agent of the device. `null` for server-recorded events." + "description": "The event's status." }, - "device_info": { - "type": "object", + "ip": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineIP" + } + ], "nullable": true, - "additionalProperties": true, - "description": "Device details the SDK or browser reported." + "description": "Null when the event has no IP address." }, - "file_url": { - "type": "string", - "format": "uri", + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineDevice" + } + ], "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`." + "description": "Null when the event has no device fingerprint or context." }, - "sent_at": { - "type": "integer", - "format": "int64", - "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." + "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." } } }, - "AMLDatasetSourceFreshness": { + "NetworkTimelineIP": { "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": { + "address": { + "type": "string" + }, + "country_code": { "type": "string", - "format": "date-time", - "description": "Most recent update of any dataset in the family." + "nullable": true }, - "oldest_updated_at": { + "city": { + "type": "string", + "nullable": true + }, + "connection_type": { "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." + "description": "The connection type label for the address, as free text." }, - "dataset_count": { - "type": "integer", - "description": "Number of datasets in the family." + "is_vpn_or_tor": { + "type": "boolean" }, - "dated_dataset_count": { - "type": "integer", - "description": "Number of those datasets that reported an update date." + "is_data_center": { + "type": "boolean" } } }, - "AMLDatasetFreshness": { + "NetworkTimelineDevice": { "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": { + "fingerprint": { "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" + "nullable": true }, - "catalog_version": { + "label": { "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" - } - } + "description": "A readable device label built from the event's device context." } } }, - "AMLScreeningRun": { + "NetworkTimelineRuleHit": { "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" - ], + "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", "properties": { - "uuid": { + "rule_id": { "type": "string", - "format": "uuid", - "description": "Identifier of this screening run." + "format": "uuid" }, - "completed_at": { - "type": "string", - "format": "date-time", - "description": "When this subject's re-screen finished. Results are ordered by it, newest first." + "rule_title": { + "type": "string" }, - "started_at": { - "type": "string", - "format": "date-time", - "description": "When the monitoring batch this re-screen belongs to started." + "rule_category": { + "type": "string" }, - "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." + "rule_source": { + "type": "string" }, - "trigger": { + "severity": { "type": "string", - "enum": [ - "ONGOING_MONITORING" - ], - "description": "What started the screening. Always `ONGOING_MONITORING` today." + "nullable": true }, - "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" + "score_delta": { + "type": "integer" }, - "status": { + "status_target": { "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." + "nullable": true }, - "dataset_freshness": { - "allOf": [ - { - "$ref": "#/components/schemas/AMLDatasetFreshness" - } - ], - "nullable": true, - "description": "Dataset freshness behind this run, or `null` when no dates were available." + "action_summary": { + "type": "string", + "nullable": true } } }, - "KYBKeyPeopleParty": { + "NetworkTimelineCaseLinked": { "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" - ], + "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", "properties": { - "uuid": { + "case_id": { "type": "string", - "format": "uuid", - "description": "Party identifier." + "format": "uuid" }, - "entity_type": { + "case_number": { "type": "string", - "enum": [ - "person", - "company" - ], - "description": "Whether the party is an individual or a company." + "nullable": true }, - "source": { - "type": "string", - "enum": [ - "REGISTRY", - "USER" - ], - "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." + "status": { + "type": "string" }, - "name": { + "author_email": { "type": "string", - "description": "Display name of the party." + "nullable": true }, - "first_name": { + "author_name": { "type": "string", - "nullable": true, - "description": "First name (persons)." - }, - "last_name": { + "nullable": true + } + } + }, + "NetworkTimelineNoteAdded": { + "type": "object", + "description": "A note left on the network. The note text is not included.", + "properties": { + "note_id": { "type": "string", - "nullable": true, - "description": "Last name (persons)." + "format": "uuid" }, - "company_name": { + "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", - "nullable": true, - "description": "Company name (company parties)." + "format": "uuid", + "description": "Identifier of the override. Pass it to the cancel endpoint.", + "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }, - "registration_number": { + "node_id": { "type": "string", - "nullable": true, - "description": "Company registration number (company parties)." + "description": "The workflow node the request named (`default` for a step without a node).", + "example": "feature_ocr_1" }, - "email": { + "feature_type": { "type": "string", - "format": "email", - "nullable": true, - "description": "Email the verification link is sent to." + "description": "The `feature_type` the request passed, or an empty string when it passed none.", + "example": "" }, - "nationality": { + "feature_model": { "type": "string", - "nullable": true, - "description": "Nationality for persons, country of incorporation for companies." + "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", + "example": "KYC" }, - "phone_number": { + "previous_status": { "type": "string", - "nullable": true, - "description": "Phone number collected for the party." + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "The check's status when the override was requested.", + "example": "In Review" }, - "date_of_birth": { + "new_status": { "type": "string", - "format": "date", - "nullable": true, - "description": "Date of birth (persons)." + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "The status the override sets.", + "example": "Approved" }, - "position": { + "state": { "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." + "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" }, - "is_skipped": { + "forced": { "type": "boolean", - "description": "Whether the party is exempt from verification. Regenerating clears it." + "description": "`true` when the override applied at once while the person was still in the flow.", + "example": false }, - "requires_verification": { - "type": "boolean", - "description": "Whether the role and ownership rules require a verification for this party." + "reason": { + "type": "string", + "nullable": true, + "description": "The `comment` sent with the request, or `null`.", + "example": "Document manually verified by compliance." }, - "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." - } + "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" } } }, - "kyc_session_id": { + "requested_at": { "type": "string", - "format": "uuid", + "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": "Child individual KYC session (persons)." + "description": "When the check took the new status, or `null` while it waits or once it is cancelled.", + "example": "2026-10-13T09:15:00Z" }, - "kyc_session_status": { + "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": [ - "Not Started", - "In Progress", + "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", - "Kyc Expired", - "Resubmitted", - "Awaiting User", - null + "Abandoned" ], - "description": "Status of the child KYC session. A deleted session keeps the status it had when it was deleted." + "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" }, - "kyc_session_url": { + "comment": { "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`." + "maxLength": 500, + "description": "Note added to the activity entry the status change records.", + "example": "Testing the decline path" }, - "kyc_session_deleted": { - "type": "boolean", - "description": "The child KYC session was deleted, so it can never settle." + "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" }, - "kyb_sub_session_id": { + "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", - "nullable": true, - "description": "Child KYB session (company parties)." + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, - "kyb_sub_session_status": { + "previous_status": { "type": "string", - "nullable": true, "enum": [ "Not Started", "In Progress", @@ -101864,4315 +105049,3833 @@ "Abandoned", "Kyc Expired", "Resubmitted", - "Awaiting User", - null + "Awaiting User" ], - "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`." + "description": "The session's status before the simulation.", + "example": "In Progress" }, - "verification_workflow_type": { + "status": { "type": "string", - "nullable": true, "enum": [ - "kyc", - "kyb", - "adaptive_age_verification", - "biometric_authentication", - "address_verification", - "questionnaire_verification", - "email_verification", - "phone_verification", - null + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" ], - "description": "Type of the workflow the child session runs." + "description": "The status the simulation set.", + "example": "Declined" }, - "verification_workflow_id": { + "scenario": { "type": "string", - "format": "uuid", "nullable": true, - "description": "Workflow the child session runs." + "description": "The scenario slug you passed, or `null`.", + "example": "decline_aml_hit" }, - "verification_workflow_label": { + "risk": { "type": "string", "nullable": true, - "description": "Label of that workflow." + "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", + "example": "POSSIBLE_MATCH_FOUND" }, - "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." - } - } - } + "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" }, - "didit_internal_id": { + "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": "Didit's identifier of the verified person, once known." + "description": "The member's full name, or null when it is not known." + }, + "email": { + "type": "string", + "format": "email" } } }, - "TravelRuleDelivery": { + "ServiceStatusFeed": { "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.", + "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": [ - "kind", "status", - "rail", - "attempts", - "last_error", - "next_attempt_at", - "delivered_at" + "updated_at", + "services", + "incidents" ], "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", - "" + "operational", + "degraded", + "down" ], - "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" + "description": "The worst state of any service right now, decided by the ongoing incidents only.", + "example": "degraded" }, - "next_attempt_at": { + "updated_at": { "type": "string", "format": "date-time", - "nullable": true, - "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." + "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" }, - "delivered_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When the other side took the message." + "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" + } } } }, - "TravelRuleDiscoveryStep": { + "ServiceStatusService": { "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.", + "required": [ + "key", + "name", + "status" + ], "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": { + "key": { "type": "string", "enum": [ - "match", - "miss", - "skipped" + "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": "`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." + "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" }, - "rail": { + "name": { "type": "string", - "nullable": true, - "description": "The rail that reaches that counterparty, when the step found one." + "description": "English display name of the service.", + "example": "Database validation" }, - "at": { + "status": { "type": "string", - "format": "date-time" + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "`operational` when no incident is ongoing for the service, otherwise the worst state of its ongoing incidents.", + "example": "degraded" } } }, - "TravelRuleNextAction": { + "ServiceStatusIncident": { "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).", + "required": [ + "id", + "service", + "service_name", + "state", + "status", + "title", + "body", + "affected", + "country_name", + "started_at", + "resolved_at", + "duration_minutes" + ], "properties": { - "action": { + "id": { + "type": "string", + "format": "uuid", + "description": "Incident ID.", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "service": { "type": "string", "enum": [ - "select_counterparty_vasp", - "declare_self_hosted", - "add_vasp_hint", - "update_compliance_email", - "resend" + "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": "`select_counterparty_vasp`: the end user picks the destination VASP in the wallet-ownership widget. `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." + "description": "Key of the affected service (see `services[].key`).", + "example": "service_database_validation" }, - "url": { + "service_name": { "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": { + "description": "English display name of the affected service.", + "example": "Database validation" + }, + "state": { "type": "string", - "format": "uuid" + "enum": [ + "degraded", + "down" + ], + "description": "How the service is affected. A resolved incident keeps the last state it had.", + "example": "degraded" }, "status": { - "type": "string" + "type": "string", + "enum": [ + "ongoing", + "resolved" + ], + "description": "`ongoing` until the service recovers, then `resolved`.", + "example": "ongoing" }, - "protocol": { + "title": { "type": "string", - "nullable": true + "description": "English headline. To translate it, build your own text from `service`, `state` and `country_name`.", + "example": "Database validation is degraded in Brazil." }, - "required": { - "type": "boolean" + "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." }, - "obligations_count": { - "type": "integer" + "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)" }, - "originator_data": { - "type": "object", - "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + "country_name": { + "type": "string", + "description": "Country affected, in English. Empty when the incident is not tied to a country.", + "example": "Brazil" }, - "beneficiary_data": { - "type": "object", - "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the incident started (ISO 8601, UTC).", + "example": "2026-10-13T08:55:00Z" }, - "name_match": { - "type": "object", + "resolved_at": { + "type": "string", + "format": "date-time", "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" + "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." }, - "data_masked": { - "type": "boolean", - "description": "True while the counterparty's personal data is masked in this response." + "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." }, - "metadata": { - "type": "object" + "vasp_name": { + "type": "string" }, - "direction": { + "source": { "type": "string", - "enum": [ - "INBOUND", - "OUTBOUND" - ], - "description": "Which side of the exchange this transfer is." + "description": "Where the invitation came from, for example `pickup`." }, - "rail": { + "expires_at": { "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." + "format": "date-time", + "nullable": true }, - "counterparty_vasp": { + "prefill": { "type": "object", - "nullable": true, "properties": { - "name": { + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_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." + "jurisdiction": { + "type": "string" }, - "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." - } - } + "domains": { + "type": "array", + "items": { + "type": "string" } - }, - "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": { + } + } + }, + "TravelRuleJoinRegisterRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { "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)." + "format": "email", + "maxLength": 254, + "description": "Must be the address the invitation went to." }, - "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" - } + "password": { + "type": "string", + "minLength": 8, + "maxLength": 128 + } + } + }, + "TravelRuleJoinVerifyRequest": { + "type": "object", + "required": [ + "email", + "code" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254 }, - "chain_details": { - "$ref": "#/components/schemas/TravelRuleChainDetails" + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "description": "The 6-character code emailed after registering." } } }, - "NetworkListCounts": { + "TravelRuleJoinCredentials": { "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.", + "description": "The new free organization and the credentials its application calls the API with.", "properties": { - "scopes": { + "organization": { "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." + "uuid": { + "type": "string", + "format": "uuid" } } }, - "views": { + "application": { "type": "object", - "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", "properties": { - "all": { - "type": "integer" + "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" + ] }, - "active": { - "type": "integer" + "proved_by_domain": { + "type": "boolean" }, - "in_review": { - "type": "integer" + "claimed_at": { + "type": "string", + "format": "date-time", + "nullable": true }, - "resolved": { - "type": "integer" + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true }, - "dismissed": { - "type": "integer" + "review_note": { + "type": "string" }, - "mine": { - "type": "integer", - "description": "Networks assigned to the calling console member. Always 0 for an API key." + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true }, - "high_risk": { - "type": "integer", - "description": "Networks with a risk score of 80 or more that are not dismissed." + "vasp_name": { + "type": "string" } } }, - "facets": { + "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", - "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" - } - } + "domain": { + "type": "string" }, - "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" - } - } - } - } - } + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] }, - "has_case": { - "type": "integer", - "description": "Networks this application opened a case for." + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true }, - "min_size": { - "type": "object", - "description": "Networks with at least 10 members, keyed by the minimum size.", - "properties": { - "10": { - "type": "integer" - } - } + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" } } - } - } - }, - "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`." + "billing_enabled": { + "type": "boolean", + "description": "Receiving Travel Rule data is free. Sending is billed only after billing is enabled." }, - "source": { + "billing_enabled_at": { "type": "string", - "enum": [ - "didit", - "custom" - ], - "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." + "format": "date-time", + "nullable": true }, - "added_by": { - "type": "string", - "nullable": true, - "description": "Email of the console member who added the tag. Null when an API key added it." + "didit_network": { + "type": "object", + "description": "Whether the Didit network lists this VASP and, when not, each condition it still misses." }, - "added_at": { - "type": "string", - "format": "date-time" + "settings": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" } } }, - "NetworkOutcomes": { + "InvestigatorDecision": { "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.", + "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": { - "approved": { - "type": "integer", - "description": "Members whose most recent decided verification was approved." + "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" + ] }, - "declined": { - "type": "integer", - "description": "Members whose most recent decided verification was declined." + "actor_email": { + "type": "string", + "nullable": true }, - "in_review": { - "type": "integer", - "description": "Members whose most recent decided verification is in review." + "actor_name": { + "type": "string", + "nullable": true }, - "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": { + "override_category": { "type": "string", "enum": [ - "background" + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other", + "" ], - "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." + "nullable": true }, - "score": { - "type": "number", - "nullable": true, - "minimum": 0, - "maximum": 1, - "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." + "override_reason": { + "type": "string", + "nullable": true }, - "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`." + "final_status": { + "type": "string", + "description": "The item's status after the decision." }, - "reason": { + "agreed": { + "type": "boolean", + "description": "Whether the decision matched the recommendation.", + "nullable": true + }, + "decided_at": { "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." + "format": "date-time" }, - "computed_at": { + "billed": { + "type": "boolean", + "description": "The review was charged." + }, + "decided_by": { "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" + "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": { - "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" + "action": { + "type": "string" }, - "cross_org_available": { - "type": "boolean", - "description": "Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the `global` sharing mode.", - "example": true + "final_status": { + "type": "string" }, - "cross_org_unavailable_reason": { + "decided_at": { "type": "string", - "enum": [ - "plan", - "sharing_mode" - ], - "nullable": true, - "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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`." + "format": "date-time" } - } - } - ] - }, - "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": { + }, + "nullable": true + }, + "overrides": { "type": "array", + "description": "Every override of a decision the Investigator took on its own, oldest first.", "items": { - "$ref": "#/components/schemas/NetworkTypedTimelineEvent" + "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" + } + } } - }, - "next_cursor": { - "type": "string", - "nullable": true, - "description": "Pass as `cursor` to read the next page. Null on the last page." } } }, - "NetworkTypedTimelineEvent": { + "InvestigatorBlock": { "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.", + "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": { - "id": { + "run_id": { "type": "string", - "description": "`:`, unique within the timeline.", - "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" + "format": "uuid" }, - "event_type": { + "status": { "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" + "running", + "completed", + "failed" ] }, - "occurred_at": { + "stage": { "type": "string", - "format": "date-time", - "description": "When it happened." + "enum": [ + "shadow", + "live" + ] }, - "ingested_at": { + "queue": { "type": "string", - "format": "date-time", - "description": "When Didit stored it." + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] }, - "label": { + "is_open": { + "type": "boolean" + }, + "recommendation": { "type": "string", - "description": "A short English label, for example `Verification attempt 2 of 3`." + "enum": [ + "approve", + "decline", + "escalate", + "request" + ], + "nullable": true }, - "metadata": { - "type": "object", - "description": "For the network's own events, the same metadata as the default timeline. Empty otherwise." + "recommendation_detail": { + "type": "string", + "nullable": true }, - "account": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineAccount" - } + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" ], - "nullable": true, - "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." + "nullable": true }, - "source_ref": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineSourceRef" - } - ], - "nullable": true, - "description": "The source record. Null for the network's own events and `note_added`." + "summary": { + "type": "string", + "nullable": true }, - "verification_attempt": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineAttempt" - } - ], - "nullable": true, - "description": "Set on `verification_attempt` events." + "ready_to_accept": { + "type": "boolean" }, - "transaction": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineTransaction" - } - ], - "nullable": true, - "description": "Set on `transaction` events." + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true }, - "login": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineLogin" - } - ], - "nullable": true, - "description": "Set on `login` events." + "automated": { + "type": "boolean", + "description": "The Investigator decided on its own, even if the decision was later overridden." }, - "rule_hit": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineRuleHit" - } + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" ], - "nullable": true, - "description": "Set on `rule_hit` events." + "nullable": true }, - "case_linked": { - "allOf": [ - { - "$ref": "#/components/schemas/NetworkTimelineCaseLinked" - } - ], + "decision": { "nullable": true, - "description": "Set on `case_linked` events." - }, - "note_added": { "allOf": [ { - "$ref": "#/components/schemas/NetworkTimelineNoteAdded" + "$ref": "#/components/schemas/InvestigatorDecision" } ], - "nullable": true, - "description": "Set on `note_added` events. Never returned to an API key." + "type": "object" } } }, - "NetworkTimelineAccount": { + "InvestigatorQueueRow": { "type": "object", - "description": "The member account the event belongs to.", "properties": { - "node_id": { + "run_id": { "type": "string", - "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", - "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" + "format": "uuid" }, - "node_type": { + "kind": { "type": "string", "enum": [ - "account", - "business_account", - "transaction_party" + "session", + "business_session", + "transaction", + "case" ] }, - "uuid": { + "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", - "format": "uuid" + "maxLength": 4000, + "description": "A rewritten narrative. Sending one records the decision as edited and accepted." }, - "vendor_data": { - "type": "string", - "nullable": true + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." }, - "display_name": { + "email_address": { "type": "string", - "nullable": true + "format": "email", + "description": "Required when `send_email` is true." }, - "ownership": { + "email_language": { "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" + "default": "en", + "maxLength": 16 } } }, - "NetworkTimelineSourceRef": { + "InvestigatorOverrideRequest": { "type": "object", - "description": "The record the event was read from.", + "required": [ + "action", + "category", + "reason" + ], "properties": { - "kind": { + "action": { "type": "string", "enum": [ - "session", - "business_session", - "transaction", - "monitoring_event", - "case" + "approve", + "decline", + "escalate", + "request" ] }, - "id": { - "type": "string", - "format": "uuid" - }, - "ownership": { + "category": { "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." + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other" + ] }, - "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": { + "reason": { "type": "string", - "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." + "minLength": 10, + "maxLength": 500 }, - "id": { + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { "type": "string", - "format": "uuid" + "format": "email", + "description": "Required when `send_email` is true." }, - "application_id": { + "email_language": { "type": "string", - "format": "uuid" + "default": "en", + "maxLength": 16 } } }, - "NetworkTimelineAttempt": { + "InvestigatorBatchAcceptRequest": { "type": "object", - "description": "One verification session of the account.", + "required": [ + "run_ids" + ], "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": { + "run_ids": { "type": "array", + "minItems": 1, + "maxItems": 50, "items": { - "$ref": "#/components/schemas/NetworkTimelineDeclineReason" - }, - "description": "For a declined session only: its warnings, oldest first, once per warning. Empty otherwise." + "type": "string", + "format": "uuid" + } } } }, - "NetworkTimelineDeclineReason": { + "InvestigatorBatchAcceptResponse": { "type": "object", "properties": { - "risk": { - "type": "string", - "description": "The warning code." + "accepted": { + "type": "integer" }, - "feature": { - "type": "string", - "description": "The feature that raised the warning." + "skipped": { + "type": "integer" }, - "short_description": { - "type": "string" + "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" + } + } + } } } }, - "NetworkTimelineTransaction": { + "InvestigatorSettings": { "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": { + "stage": { "type": "string", - "nullable": true + "enum": [ + "none", + "shadow", + "live" + ] }, - "decision_reason_label": { + "stage_changed_at": { "type": "string", + "format": "date-time", + "readOnly": true, "nullable": true }, - "party_role": { - "type": "string", - "description": "The account's role in the transaction: `APPLICANT`, `REMITTER`, `BENEFICIARY` or `COUNTERPARTY`." - } - } - }, - "NetworkTimelineAmount": { - "type": "object", - "properties": { - "value": { + "stage_changed_by": { "type": "string", - "description": "Decimal amount as a string." - }, - "currency": { - "type": "string" + "readOnly": true }, - "direction": { + "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 - } - } - }, - "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" + "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." } - ], - "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": { + "TravelRuleUtxoEntry": { "type": "object", + "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "required": [ + "address" + ], "properties": { "address": { - "type": "string" - }, - "country_code": { - "type": "string", - "nullable": true - }, - "city": { "type": "string", - "nullable": true + "maxLength": 128 }, - "connection_type": { + "amount": { "type": "string", "nullable": true, - "description": "The connection type label for the address, as free text." + "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", + "example": "0.0125" }, - "is_vpn_or_tor": { - "type": "boolean" + "txid": { + "type": "string", + "maxLength": 128, + "description": "Optional. The transaction an input spends." }, - "is_data_center": { - "type": "boolean" + "vout": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Optional. The output index an input spends." } } }, - "NetworkTimelineDevice": { + "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": { - "fingerprint": { - "type": "string", - "nullable": true + "inputs": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + }, + "description": "Optional. Without inputs no originator is inferred and no change output is recognised." }, - "label": { - "type": "string", - "nullable": true, - "description": "A readable device label built from the event's device context." + "outputs": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } } } }, - "NetworkTimelineRuleHit": { + "TravelRuleChainSide": { "type": "object", - "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", + "description": "One side's wallet as Didit resolved it from the chain data.", "properties": { - "rule_id": { - "type": "string", - "format": "uuid" - }, - "rule_title": { - "type": "string" - }, - "rule_category": { - "type": "string" - }, - "rule_source": { - "type": "string" - }, - "severity": { + "address": { "type": "string", - "nullable": true - }, - "score_delta": { - "type": "integer" + "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." }, - "status_target": { + "memo": { "type": "string", - "nullable": true + "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." }, - "action_summary": { + "source": { "type": "string", - "nullable": true + "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`)." } } }, - "NetworkTimelineCaseLinked": { + "TravelRuleChainDetails": { "type": "object", - "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", + "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": { - "case_id": { + "chain": { "type": "string", - "format": "uuid" + "description": "Canonical chain, e.g. `BTC`, `XRP`, or `LIGHTNING` for a Lightning transfer." }, - "case_number": { + "shape": { "type": "string", - "nullable": true + "enum": [ + "utxo", + "memo", + "lightning" + ] }, - "status": { - "type": "string" + "originator": { + "$ref": "#/components/schemas/TravelRuleChainSide" }, - "author_email": { - "type": "string", - "nullable": true + "beneficiary": { + "$ref": "#/components/schemas/TravelRuleChainSide" }, - "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" + "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." + } + } }, - "author_email": { - "type": "string" + "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." } } }, - "FeatureStatusOverride": { + "WalletAddressImportRow": { "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" - ], + "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": { - "id": { + "address": { "type": "string", - "format": "uuid", - "description": "Identifier of the override. Pass it to the cancel endpoint.", - "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + "maxLength": 128, + "description": "The wallet address. Alias: `walletAddress`." }, - "node_id": { + "address_hash": { "type": "string", - "description": "The workflow node the request named (`default` for a step without a node).", - "example": "feature_ocr_1" + "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`." }, - "feature_type": { + "chain": { "type": "string", - "description": "The `feature_type` the request passed, or an empty string when it passed none.", - "example": "" + "maxLength": 32 }, - "feature_model": { + "memo": { "type": "string", - "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", - "example": "KYC" + "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`." }, - "previous_status": { + "holder_name": { "type": "string", - "enum": [ - "Not Finished", - "Approved", - "Declined", - "In Review", - "Resub Requested" - ], - "description": "The check's status when the override was requested.", - "example": "In Review" + "maxLength": 255 }, - "new_status": { + "holder_vendor_data": { "type": "string", - "enum": [ - "Approved", - "Declined", - "In Review" - ], - "description": "The status the override sets.", - "example": "Approved" + "maxLength": 255, + "description": "Your internal identifier for the holder. Alias: `externalUserId`." }, - "state": { + "entity_type": { "type": "string", "enum": [ - "applied", - "pending_until_flow_finishes", - "cancelled" + "individual", + "company" ], - "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" - } - } + "default": "individual" }, - "requested_at": { + "label": { "type": "string", - "format": "date-time", - "description": "When the override was requested.", - "example": "2026-10-13T09:15:00Z" + "maxLength": 255 }, - "applied_at": { + "owner_type": { "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" + "enum": [ + "customer", + "internal", + "vasp" + ], + "default": "customer" }, - "cancelled_at": { + "travel_address": { "type": "string", - "format": "date-time", - "nullable": true, - "description": "When a waiting override was cancelled, or `null`.", - "example": null + "maxLength": 512 }, - "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 + "self_declared": { + "type": "boolean", + "default": false, + "description": "Create the entry already verified through a self-declaration, when your settings allow it." } } }, - "SandboxSimulateRequest": { + "WalletAddressImportReport": { "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.", + "description": "What happened to each row of an import, in the order sent.", "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" + "total": { + "type": "integer" }, - "comment": { - "type": "string", - "maxLength": 500, - "description": "Note added to the activity entry the status change records.", - "example": "Testing the decline path" + "created": { + "type": "integer" }, - "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" + "existing": { + "type": "integer" }, - "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" + "duplicate": { + "type": "integer" }, - "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" + "failed": { + "type": "integer" }, - "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 + "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." + } + } + } } } }, - "SandboxSimulateResponse": { + "TravelRuleCounterpartyContact": { "type": "object", - "required": [ - "session_id", - "previous_status", - "status", - "scenario", - "risk", - "send_webhooks" - ], + "description": "A compliance address the EMAIL rail writes to for one directory entry.", "properties": { - "session_id": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "counterparty": { "type": "string", "format": "uuid", - "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + "description": "The directory entry's `id`, as GET /v3/travel-rule/vasps/ shows it." }, - "previous_status": { + "counterparty_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string", + "description": "Optional label, for example the team name." + }, + "source": { "type": "string", "enum": [ - "Not Started", - "In Progress", - "Approved", - "Declined", - "In Review", - "Expired", - "Abandoned", - "Kyc Expired", - "Resubmitted", - "Awaiting User" + "staff", + "application", + "learned" ], - "description": "The session's status before the simulation.", - "example": "In Progress" + "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." }, - "status": { + "scope": { "type": "string", "enum": [ - "Approved", - "Declined", - "In Review", - "Expired", - "Abandoned" + "platform", + "application" ], - "description": "The status the simulation set.", - "example": "Declined" + "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." }, - "scenario": { + "created_by": { "type": "string", - "nullable": true, - "description": "The scenario slug you passed, or `null`.", - "example": "decline_aml_hit" + "description": "Empty for platform contacts and for contacts an API key added." }, - "risk": { + "verified_at": { "type": "string", + "format": "date-time", "nullable": true, - "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", - "example": "POSSIBLE_MATCH_FOUND" + "description": "When this address last answered a request through its response link." }, - "send_webhooks": { + "is_invalid": { "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": { + "description": "true once the address bounced or complained; the EMAIL rail skips it." + }, + "invalid_reason": { "type": "string", - "format": "uuid" + "enum": [ + "", + "hard_bounce", + "soft_bounce", + "spam_complaint", + "suppressed" + ] }, - "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": { + "invalid_detail": { "type": "string", - "format": "uuid", - "description": "The member's user uuid: the value you pass as `checker` when you resolve the case." + "description": "The receiving mail server's reason, when it gave one." }, - "name": { + "invalid_at": { "type": "string", - "nullable": true, - "description": "The member's full name, or null when it is not known." + "format": "date-time", + "nullable": true }, - "email": { + "created_at": { "type": "string", - "format": "email" + "format": "date-time" } } }, - "ServiceStatusFeed": { + "TravelRuleCounterpartyContactCreate": { "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" + "counterparty", + "email" ], "properties": { - "status": { + "counterparty": { "type": "string", - "enum": [ - "operational", - "degraded", - "down" - ], - "description": "The worst state of any service right now, decided by the ongoing incidents only.", - "example": "degraded" + "format": "uuid", + "description": "The directory entry's `id`." }, - "updated_at": { + "email": { "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" - } + "format": "email", + "maxLength": 254 }, - "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" - } + "name": { + "type": "string", + "description": "Optional label.", + "maxLength": 255 } } }, - "ServiceStatusService": { + "TravelRuleNameMatch": { "type": "object", - "required": [ - "key", - "name", - "status" - ], + "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": { - "key": { + "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": [ - "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" + "none", + "exact_token", + "token_subset", + "token_containment", + "sequence_ratio" ], - "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" + "description": "How `matched` was decided: none (`NONE`), exact_token (`STRICT`), token_subset (`DEFAULT`), token_containment (`CONTAINS`) or sequence_ratio (`FUZZY`)." }, - "name": { + "strictness": { "type": "string", - "description": "English display name of the service.", - "example": "Database validation" + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] }, - "status": { + "compared_with": { "type": "string", "enum": [ - "operational", - "degraded", - "down" + "wallet_holder", + "counterparty_vasp" ], - "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" - ], + "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": "Incident ID.", - "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + "description": "The transfer's id." }, - "service": { + "transaction_id": { "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" + "format": "uuid", + "description": "The Didit transaction the transfer belongs to." }, - "service_name": { + "txn_id": { "type": "string", - "description": "English display name of the affected service.", - "example": "Database validation" + "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." }, - "state": { + "txn_date": { + "type": "string", + "format": "date-time" + }, + "kind": { "type": "string", "enum": [ - "degraded", - "down" + "withdrawal", + "deposit", + "message" ], - "description": "How the service is affected. A resolved incident keeps the last state it had.", - "example": "degraded" + "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": [ - "ongoing", - "resolved" - ], - "description": "`ongoing` until the service recovers, then `resolved`.", - "example": "ongoing" + "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" + ] }, - "title": { + "reason": { "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." + "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)." }, - "body": { + "rail": { "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." + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ] }, - "affected": { + "asset": { "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)" + "description": "The transaction's currency." }, - "country_name": { + "amount": { "type": "string", - "description": "Country affected, in English. Empty when the incident is not tied to a country.", - "example": "Brazil" + "description": "The amount as a plain decimal, for example \"1.25\"." }, - "started_at": { + "originator_vasp": { "type": "string", - "format": "date-time", - "description": "When the incident started (ISO 8601, UTC).", - "example": "2026-10-13T08:55:00Z" + "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." }, - "resolved_at": { + "beneficiary_vasp": { "type": "string", - "format": "date-time", - "nullable": true, - "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." + "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." }, - "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": { + "originator_wallet": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "originator_memo": { "type": "string", - "format": "email", - "description": "The address the invitation went to." + "description": "Memo or destination tag, empty on chains without one." }, - "vasp_name": { + "beneficiary_memo": { "type": "string" }, - "source": { + "originator_wallet_type": { "type": "string", - "description": "Where the invitation came from, for example `pickup`." + "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." }, - "expires_at": { + "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 + "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)." }, - "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" - } - } - } + "created_at": { + "type": "string", + "format": "date-time" } } }, - "TravelRuleJoinRegisterRequest": { - "type": "object", - "required": [ - "email", - "password" - ], - "properties": { - "email": { - "type": "string", - "format": "email", - "maxLength": 254, - "description": "Must be the address the invitation went to." + "BankAccountCheckHolder": { + "description": "The name the account is checked against. Send exactly one of `full_name` and `business_name`.", + "oneOf": [ + { + "$ref": "#/components/schemas/BankAccountCheckPersonHolder" }, - "password": { - "type": "string", - "minLength": 8, - "maxLength": 128 + { + "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" } - } + ] }, - "TravelRuleJoinVerifyRequest": { + "BankAccountCheckRequest": { "type": "object", "required": [ - "email", - "code" + "country", + "account", + "holder" ], "properties": { - "email": { + "country": { "type": "string", - "format": "email", - "maxLength": 254 + "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" }, - "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": { + "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": { - "uuid": { + "iban": { "type": "string", - "format": "uuid" - } - } - }, - "application": { - "type": "object", - "properties": { - "uuid": { + "maxLength": 64, + "description": "The IBAN, in a country that uses IBANs. It must belong to `country`." + }, + "account_number": { "type": "string", - "format": "uuid" + "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" } }, - "client_id": { - "type": "string" + "holder": { + "$ref": "#/components/schemas/BankAccountCheckHolder" }, - "api_key": { + "vendor_data": { "type": "string", - "description": "Send as the `x-api-key` header on the workspace endpoints." + "nullable": true, + "description": "Your own reference for this check, returned unchanged.", + "example": "payout_20481" } } }, - "TravelRuleWorkspaceClaimRequest": { + "BankAccountCheckResponse": { "type": "object", "required": [ - "vasp_id" + "request_id", + "result", + "holder_name", + "account", + "vendor_data", + "created_at" ], "properties": { - "vasp_id": { + "request_id": { "type": "string", "format": "uuid", - "description": "The directory entry this workspace is. Read ids from `GET /v3/travel-rule/vasps/`." + "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." } } }, - "TravelRuleWorkspaceDomainConfirmRequest": { + "BankAccountCheckError": { "type": "object", "required": [ - "code" + "detail" ], "properties": { + "detail": { + "type": "string", + "description": "What went wrong, in English." + }, "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." + "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." } } }, - "TravelRuleWorkspaceDomainCodeResponse": { + "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": { - "domain": { - "type": "string" + "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": [ - "unverified", - "code_sent", - "verified" - ] + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "The step's verdict, after its actions and status rules." }, - "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": { + "original_status": { "type": "string", - "example": "didit-tr-verify.example.com" + "nullable": true, + "description": "The first verdict the step reached, before any later change such as a manual review." }, - "value": { - "type": "string" - } - } - }, - "TravelRuleWorkspace": { - "type": "object", - "description": "The free Travel Rule workspace of an application that joined free.", - "properties": { - "joined_free": { - "type": "boolean" + "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`." }, - "work_email": { + "provider": { "type": "string", - "format": "email" + "nullable": true, + "description": "The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified." }, - "joined_at": { + "credential_source": { "type": "string", - "format": "date-time", - "nullable": true + "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`." + } + } }, - "claim": { + "connection": { "type": "object", + "nullable": true, + "description": "The latest attempt's connection. `null` before the first attempt.", "properties": { "status": { "type": "string", "enum": [ - "unclaimed", - "pending_review", - "claimed", - "rejected" + "pending", + "connected", + "cancelled", + "timeout", + "failed", + "unsupported", + "revoked", + "expired" ] }, - "proved_by_domain": { - "type": "boolean" + "institution_id": { + "type": "string", + "nullable": true, + "description": "The bank's ID. `null` for an account check, where no bank is picked." }, - "claimed_at": { + "institution_name": { "type": "string", - "format": "date-time", - "nullable": true + "nullable": true, + "description": "The bank's name. `null` for an account check." }, - "reviewed_at": { + "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 + "nullable": true, + "description": "When the connection succeeded." }, - "review_note": { - "type": "string" + "consent_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank consent ends, for bank sign-in." }, - "vasp_id": { + "last_refreshed_at": { "type": "string", - "format": "uuid", - "nullable": true + "format": "date-time", + "nullable": true, + "description": "When the financial data was last read again." }, - "vasp_name": { - "type": "string" + "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`." } } }, - "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": { + "account": { "type": "object", + "nullable": true, + "description": "The evaluated account. `null` until an account is evaluated.", "properties": { - "domain": { - "type": "string" + "account_id_masked": { + "type": "string", + "nullable": true, + "description": "The account number, masked." }, - "status": { + "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": [ - "unverified", - "code_sent", - "verified" + "checking", + "current", + "savings", + "business", + "other" ] }, - "verified_at": { + "subtype": { "type": "string", - "format": "date-time", + "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 }, - "txt": { - "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + "is_joint": { + "type": "boolean", + "nullable": true + }, + "is_business": { + "type": "boolean", + "nullable": true } } }, - "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": { + "ownership": { "type": "object", - "description": "The decision the Investigator took on its own, unchanged by later overrides. Null for a decision the team took.", + "nullable": true, + "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", "properties": { - "action": { - "type": "string" + "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." }, - "final_status": { - "type": "string" + "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`." }, - "decided_at": { + "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", - "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 + "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" }, - "overridden_at": { - "type": "string", - "format": "date-time" - } + "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." } } - } - } - }, - "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 + "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." + } + } }, - "decision": { + "billing": { + "type": "object", "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/InvestigatorDecision" + "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." } - ], - "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": { + "reference_identity": { "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" + "nullable": true, + "description": "The identity the holder was compared against, with its source." }, - "guardrails": { - "description": "The locked guardrails that hold this item, if any." + "signals": { + "type": "object", + "nullable": true, + "description": "Account signals read from the transaction history, when the step read one." }, - "waiting_since": { - "type": "string", - "format": "date-time", - "nullable": true + "connection_abandoned": { + "type": "boolean", + "nullable": true, + "description": "Whether the step ended without a match after the person left at their bank." }, - "due_at": { - "type": "string", - "format": "date-time", - "nullable": true + "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." }, - "prepared_at": { - "type": "string", - "format": "date-time", - "nullable": true - } - } - }, - "InvestigatorRunDetail": { - "allOf": [ - { - "$ref": "#/components/schemas/InvestigatorQueueRow" + "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": { - "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" + "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." }, - "prepared_before_opened": { - "type": "boolean", - "nullable": true + "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." }, - "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." + "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." }, - "trail": { - "description": "What I checked: the overview, sections and records it read, in order." + "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." }, - "decision": { + "institution_name": { + "type": "string", "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/InvestigatorDecision" - } - ], - "type": "object" + "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." }, - "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 + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." } } - } - ] - }, - "InvestigatorAcceptRequest": { - "type": "object", - "properties": { - "narrative": { + }, + "evidence_purged_at": { "type": "string", - "maxLength": 4000, - "description": "A rewritten narrative. Sending one records the decision as edited and accepted." + "format": "date-time", + "nullable": true, + "description": "When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise." }, - "send_email": { - "type": "boolean", - "default": false, - "description": "Email the person about the decision." + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational codes, for example `holder_declared`. A warning never changes the status on its own." }, - "email_address": { + "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": "email", - "description": "Required when `send_email` is true." + "format": "date-time" }, - "email_language": { + "updated_at": { "type": "string", - "default": "en", - "maxLength": 16 + "format": "date-time" } } }, - "InvestigatorOverrideRequest": { + "BankAccountCheckPersonHolder": { "type": "object", + "description": "The person who should hold the account.", "required": [ - "action", - "category", - "reason" + "full_name" ], "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": { + "full_name": { "type": "string", - "default": "en", - "maxLength": 16 + "maxLength": 255, + "description": "The full name of the person who should hold the account.", + "example": "Maria Oliveira" } } }, - "InvestigatorBatchAcceptRequest": { + "BankAccountCheckBusinessHolder": { "type": "object", + "description": "The company that should hold the account. Checks a business account.", "required": [ - "run_ids" + "business_name" ], "properties": { - "run_ids": { - "type": "array", - "minItems": 1, - "maxItems": 50, - "items": { - "type": "string", - "format": "uuid" - } + "business_name": { + "type": "string", + "maxLength": 255, + "description": "The name of the company that should hold the account. Checks a business account." } } }, - "InvestigatorBatchAcceptResponse": { + "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": { - "accepted": { - "type": "integer" + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" }, - "skipped": { - "type": "integer" + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" }, - "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" - } - } + "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" } } } }, - "InvestigatorSettings": { + "BankStatementUploadPricing": { "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], "properties": { - "stage": { + "price_usd": { "type": "string", - "enum": [ - "none", - "shadow", - "live" - ] + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" }, - "stage_changed_at": { - "type": "string", - "format": "date-time", - "readOnly": true, - "nullable": true + "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 }, - "stage_changed_by": { + "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", - "readOnly": true + "description": "Country name in English.", + "example": "United Kingdom" }, - "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 + "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" }, - "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." - } - } + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" } } }, - "TravelRuleUtxoEntry": { + "BankSignInPricing": { "type": "object", - "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", "required": [ - "address" + "available", + "market", + "price_usd", + "add_ons" ], "properties": { - "address": { - "type": "string", - "maxLength": 128 + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true }, - "amount": { + "market": { "type": "string", "nullable": true, - "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", - "example": "0.0125" + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" }, - "txid": { + "price_usd": { "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." + "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" + } + } } } }, - "TravelRuleUtxo": { + "BankAccountCheckPricing": { "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`.", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", "required": [ - "outputs" + "available", + "price_usd" ], "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." + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true }, - "outputs": { - "type": "array", - "minItems": 1, - "maxItems": 1000, - "items": { - "$ref": "#/components/schemas/TravelRuleUtxoEntry" - } + "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" } } }, - "TravelRuleChainSide": { + "ImportProvider": { "type": "object", - "description": "One side's wallet as Didit resolved it from the chain data.", "properties": { - "address": { + "key": { "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." + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] }, - "memo": { - "type": "string", - "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." + "name": { + "type": "string" }, - "source": { + "mode": { "type": "string", "enum": [ - "declared", - "x_address", - "largest_input", - "only_non_change_output", - "amount_match", - "invoice_payee", - "updated" + "api_list", + "api_ids", + "export_file" ], - "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" + "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." }, - "beneficiary": { - "$ref": "#/components/schemas/TravelRuleChainSide" + "record_noun": { + "type": "string" }, - "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" + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "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." + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } } } }, - "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 + "regions": { + "type": "array", + "items": { + "type": "string" + } }, - "warnings": { + "required_permissions": { "type": "array", "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { "type": "string", "enum": [ - "declared_originator_not_an_input", - "declared_beneficiary_not_an_output", - "beneficiary_output_ambiguous", - "beneficiary_output_amount_differs" + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" ] - }, - "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": { + "ImportJobSummary": { "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`.", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", "properties": { - "address": { + "id": { "type": "string", - "maxLength": 128, - "description": "The wallet address. Alias: `walletAddress`." + "format": "uuid" }, - "address_hash": { + "kind": { "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`." + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] }, - "chain": { + "source": { "type": "string", - "maxLength": 32 + "enum": [ + "file", + "api", + "provider" + ] }, - "memo": { + "provider": { "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`." + "nullable": true }, - "holder_name": { + "provider_name": { "type": "string", - "maxLength": 255 + "nullable": true }, - "holder_vendor_data": { + "filename": { "type": "string", - "maxLength": 255, - "description": "Your internal identifier for the holder. Alias: `externalUserId`." + "nullable": true }, - "entity_type": { + "state": { "type": "string", "enum": [ - "individual", - "company" + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" ], - "default": "individual" + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." }, - "label": { + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { "type": "string", - "maxLength": 255 + "nullable": true }, - "owner_type": { + "resumes_at": { "type": "string", - "enum": [ - "customer", - "internal", - "vasp" - ], - "default": "customer" + "format": "date-time", + "nullable": true }, - "travel_address": { + "last_error": { "type": "string", - "maxLength": 512 + "nullable": true }, - "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" + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } }, - "created": { - "type": "integer" + "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" + } + } }, - "existing": { - "type": "integer" + "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" + } + } }, - "duplicate": { - "type": "integer" + "eta_seconds": { + "type": "integer", + "nullable": true }, - "failed": { - "type": "integer" + "created_at": { + "type": "string", + "format": "date-time" }, - "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." - } - } - } + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true } } }, - "TravelRuleCounterpartyContact": { + "FiledRegulatoryReport": { "type": "object", - "description": "A compliance address the EMAIL rail writes to for one directory entry.", + "description": "A finalized regulatory report that prevents the requested deletion.", + "required": [ + "uuid", + "report_number", + "report_type", + "case_uuid" + ], "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." + "description": "Report UUID." }, - "verified_at": { + "report_number": { "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." + "description": "Report number shown in the Console (for example, SAR-0001)." }, - "invalid_reason": { + "report_type": { "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 + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Regulatory report type." }, - "created_at": { + "case_uuid": { "type": "string", - "format": "date-time" + "format": "uuid", + "description": "Case the report belongs to." } } }, - "TravelRuleCounterpartyContactCreate": { + "FiledReportTransaction": { "type": "object", + "description": "A transaction that cannot be deleted because a finalized regulatory report lists it.", "required": [ - "counterparty", - "email" + "uuid", + "transaction_id", + "reports" ], "properties": { - "counterparty": { + "uuid": { "type": "string", "format": "uuid", - "description": "The directory entry's `id`." + "description": "Didit transaction UUID." }, - "email": { + "transaction_id": { "type": "string", - "format": "email", - "maxLength": 254 + "description": "The transaction identifier supplied when the transaction was created." }, - "name": { - "type": "string", - "description": "Optional label.", - "maxLength": 255 + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Finalized reports that list this transaction." } } }, - "TravelRuleNameMatch": { + "InFiledReportError": { "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.", + "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": { - "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" - ] + "detail": { + "type": "string" }, - "compared_with": { + "code": { "type": "string", "enum": [ - "wallet_holder", - "counterparty_vasp" + "in_filed_report" ], - "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." + "description": "Stable error code for a deletion blocked by a finalized regulatory report." }, - "matched_names": { + "reports": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/FiledRegulatoryReport" }, - "description": "Words of `transfer_name` found in `compared_name`." + "description": "Reports that block a person, company, case or session deletion." }, - "mismatched_names": { + "transactions": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/FiledReportTransaction" }, - "description": "Words of `transfer_name` missing from `compared_name`, then words of `compared_name` missing from `transfer_name`." + "description": "Transactions that block a transaction deletion, each with the reports that list it." } } }, - "TravelRuleTransferRow": { + "TravelRuleNetworkParticipantSummary": { "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.", + "nullable": true, + "description": "This application's own participant on the network, or null when it has none.", "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": { + "mode": { "type": "string", - "format": "date-time" + "enum": [ + "platform", + "own" + ], + "description": "platform: carried by Didit's membership; own: onboarded under the application's own membership." }, - "kind": { + "status": { "type": "string", "enum": [ - "withdrawal", - "deposit", - "message" + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" ], - "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." + "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." }, - "direction": { + "participant_code": { "type": "string", - "enum": [ - "INBOUND", - "OUTBOUND" - ] + "description": "The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own." }, - "status": { + "requested_at": { "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" - ] + "format": "date-time", + "nullable": true }, - "reason": { + "activated_at": { "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)." + "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", - "nullable": true, "enum": [ - "INTERNAL", - "TRP", "GTR", "TRUST", "VERIFYVASP", - "SYGNA", - "EMAIL", - "TEST_VASP", - null + "SYGNA" ] }, - "asset": { - "type": "string", - "description": "The transaction's currency." - }, - "amount": { + "mode": { "type": "string", - "description": "The amount as a plain decimal, for example \"1.25\"." + "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)." }, - "originator_vasp": { + "state": { "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." + "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." }, - "beneficiary_vasp": { + "reason": { "type": "string", - "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." + "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." }, - "originator_wallet": { - "type": "string" + "platform_available": { + "type": "boolean", + "description": "Whether Didit's own membership of this network is configured; not a live health check." }, - "beneficiary_wallet": { - "type": "string" + "connected": { + "type": "boolean", + "description": "Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange." }, - "originator_memo": { - "type": "string", - "description": "Memo or destination tag, empty on chains without one." + "participant": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipantSummary" }, - "beneficiary_memo": { - "type": "string" + "vasps_reachable": { + "type": "integer", + "description": "Directory VASPs reachable on this network." }, - "originator_wallet_type": { + "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", - "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." + "maxLength": 255 }, - "beneficiary_wallet_type": { + "registration_number": { "type": "string", - "enum": [ - "vasp", - "self_hosted", - "own" - ] + "maxLength": 255 }, - "matched_transaction_id": { + "incorporation_country": { "type": "string", - "format": "uuid", - "nullable": true, - "description": "The other side of a deposit you matched to an announced transfer by hand." + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." }, - "deadline_at": { + "registered_address": { "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)." + "maxLength": 1000 }, - "created_at": { + "website": { "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" + "format": "uri", + "maxLength": 500 }, - { - "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" - } - ] - }, - "BankAccountCheckRequest": { - "type": "object", - "required": [ - "country", - "account", - "holder" - ], - "properties": { - "country": { + "lei": { "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" + "maxLength": 20, + "description": "Optional. A valid ISO 17442 LEI when given." }, - "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." + "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 + } } }, - "additionalProperties": { + "description": "Every licence or registration the VASP holds." + }, + "operating_jurisdictions": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "items": { "type": "string", - "maxLength": 64 - }, - "example": { - "sort_code": "20-45-67", - "account_number": "31926819" + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." } }, - "holder": { - "$ref": "#/components/schemas/BankAccountCheckHolder" + "compliance_officer_name": { + "type": "string", + "maxLength": 255 }, - "vendor_data": { + "compliance_officer_email": { "type": "string", - "nullable": true, - "description": "Your own reference for this check, returned unchanged.", - "example": "payout_20481" + "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." } } }, - "BankAccountCheckResponse": { + "TravelRuleNetworkQuestionnaire": { "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": { + "status": { "type": "string", "enum": [ - "match", - "close_match", - "no_match", - "cannot_verify" + "draft", + "submitted", + "changes_requested", + "approved", + "rejected" ], - "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." + "description": "Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move." }, - "holder_name": { + "answers": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "submitted_at": { "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." + "format": "date-time", + "nullable": true }, - "account": { + "reviewed_at": { "type": "string", - "description": "The account, masked to its last 4 characters." + "format": "date-time", + "nullable": true }, - "vendor_data": { + "review_note": { "type": "string", - "nullable": true, - "description": "Your own reference, as you sent it." + "description": "Didit's note on its last decision, for example what to change." }, - "created_at": { + "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", - "description": "When the check ran, in UTC." + "nullable": true } } }, - "BankAccountCheckError": { + "TravelRuleNetworkParticipant": { "type": "object", - "required": [ - "detail" - ], + "description": "This application's own participant on one network.", "properties": { - "detail": { + "network": { "type": "string", - "description": "What went wrong, in English." + "enum": [ + "gtr", + "trust", + "verifyvasp", + "sygna" + ] }, - "code": { + "rail": { "type": "string", - "description": "A stable code to branch on. Absent on `401` and `403`." + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] }, - "field": { + "mode": { "type": "string", - "description": "`400` only: the body field to correct, as a dotted path, for example `account.sort_code`." + "enum": [ + "platform", + "own" + ] }, - "reason": { + "status": { "type": "string", "enum": [ - "required", - "length", - "format", - "checksum", - "country_mismatch", + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", "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." + "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." }, - "node_id": { - "type": "string", - "description": "The workflow node this result belongs to." + "participant_code": { + "type": "string" }, - "status": { + "requested_at": { "type": "string", - "enum": [ - "Not Finished", - "Approved", - "Declined", - "In Review", - "Expired" - ], - "description": "The step's verdict, after its actions and status rules." + "format": "date-time", + "nullable": true }, - "original_status": { - "type": "string", - "nullable": true, - "description": "The first verdict the step reached, before any later change such as a manual review." + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true }, - "method": { + "activated_at": { "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`." + "format": "date-time", + "nullable": true }, - "provider": { + "suspended_at": { "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." + "format": "date-time", + "nullable": true }, - "credential_source": { + "last_error": { "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." + "description": "The network's answer when it refused the registration; empty otherwise." }, - "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": { + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { "type": "string" }, - "description": "The data the connection asked for, for example `ownership` or `balances`." + "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." }, - "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`." - } - } + "questionnaire": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" }, - "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 - } + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" } }, - "ownership": { + "networks": { "type": "object", - "nullable": true, - "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", + "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": { - "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." + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" }, - "owner_identity_available": { - "type": "boolean", - "nullable": true + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" }, - "evidence_freshness": { - "type": "string", - "nullable": true, - "enum": [ - "live", - "cached", - "refreshed" - ] + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" }, - "evaluated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When the verdict was computed." + "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." }, - "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." - } - } + "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`." }, - "billing": { + "address": { "type": "object", "nullable": true, + "description": "The normalised address. `null` when `status` is `unavailable`.", "properties": { - "route": { - "type": "string", - "enum": [ - "native", - "byok" - ] - }, - "billed": { - "type": "boolean" - }, - "marketplace_fee": { + "address_line_1": { "type": "string", "nullable": true, - "description": "The marketplace platform fee charged, for `byok`." + "description": "First delivery line, written the way the country writes it.", + "example": "1600 Amphitheatre Pkwy" }, - "usage_type": { + "address_line_2": { "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." + "description": "Second delivery line (a flat, building or neighbourhood), when the address has one." }, - "country": { + "city": { "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." + "example": "Mountain View" }, - "institution_id": { + "region": { "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." + "description": "State, province or region.", + "example": "CA" }, - "institution_name": { + "postal_code": { "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." + "example": "94043-1351" }, - "continued_at": { + "country": { "type": "string", - "format": "date-time", "nullable": true, - "description": "When the person continued." + "description": "ISO 3166-1 alpha-2 country code.", + "example": "US" } } }, - "evidence_purged_at": { + "formatted_address": { "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" + "description": "The normalised address on one line.", + "example": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351" }, - "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" + "latitude": { + "type": "number", + "nullable": true, + "example": 37.422022 + }, + "longitude": { + "type": "number", + "nullable": true, + "example": -122.08429 } } }, - "BankAccountCheckBusinessHolder": { + "AddressValidationRequest": { "type": "object", - "description": "The company that should hold the account. Checks a business account.", "required": [ - "business_name" + "country" ], + "description": "One address: on one line (`address`) or as structured fields with at least `address_line_1`, never both.", "properties": { - "business_name": { + "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": "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": { + "description": "Street and number.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { "type": "string", - "enum": [ - "USD" - ], - "description": "Currency of every price in the response.", - "example": "USD" + "maxLength": 255, + "description": "Apartment, suite, floor or building." }, - "statement_upload": { - "$ref": "#/components/schemas/BankStatementUploadPricing" + "city": { + "type": "string", + "maxLength": 128, + "description": "City or locality.", + "example": "Mountain View" }, - "own_provider_fee_usd": { + "region": { "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" + "maxLength": 128, + "description": "State, province or region.", + "example": "CA" }, - "countries": { - "type": "object", - "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", - "additionalProperties": { - "$ref": "#/components/schemas/BankVerificationPricingCountry" - } + "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" } } }, - "BankStatementUploadPricing": { + "AddressValidationResponse": { "type": "object", - "description": "How the bank statement upload is priced.", "required": [ - "price_usd", - "included_after_charged_bank_attempt", - "standalone_available" + "request_id", + "address_validation", + "created_at" ], "properties": { - "price_usd": { + "request_id": { "type": "string", - "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", - "example": "0.0000" + "format": "uuid", + "description": "Identifier of this request; quote it to support. No session is created." }, - "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 + "address_validation": { + "$ref": "#/components/schemas/AddressValidationResult" }, - "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 + "created_at": { + "type": "string", + "format": "date-time" } } }, - "BankVerificationPricingCountry": { + "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", - "description": "One country's Bank Verification methods and their list prices.", - "required": [ - "name", - "methods" - ], "properties": { - "name": { + "detail": { "type": "string", - "description": "Country name in English.", - "example": "United Kingdom" + "description": "Human-readable message in English." }, - "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" + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Machine-readable reason, so a client can show its own translated message." }, - "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 + "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)." }, - "market": { + "proof_id": { "type": "string", - "nullable": true, - "description": "The market whose price applies. `null` when not available.", - "example": "UK and EEA" + "format": "uuid", + "description": "Present with status: the closed proof." }, - "price_usd": { + "method": { "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" + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "Present with status: the closed proof's method." }, - "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" - } - } + "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." } } }, - "BankAccountCheckPricing": { + "TravelRuleWidgetProofCreate": { "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" + "method" ], "properties": { - "available": { - "type": "boolean", - "description": "`true` only where Didit serves the account check in this country in production.", - "example": true + "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." }, - "price_usd": { + "wallet_address": { "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" + "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." } } }, - "ImportProvider": { + "TravelRuleWidgetProofVerifyResult": { "type": "object", "properties": { - "key": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { "type": "string", "enum": [ - "sumsub", - "jumio", - "veriff", - "onfido", - "persona", - "metamap", - "socure", - "idnow", - "shufti", - "idenfy", - "ondato", - "trulioo", - "au10tix", - "complycube", - "other" + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" ] }, - "name": { - "type": "string" - }, - "mode": { + "status": { "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" - } - } - } + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] }, - "regions": { - "type": "array", - "items": { - "type": "string" - } + "is_ownership_verified": { + "type": "boolean", + "description": "Present when the proof is VERIFIED: the address book entry is now ownership-verified." }, - "required_permissions": { - "type": "array", - "items": { - "type": "string" - } + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Present when the proof is VERIFIED: the linked transfer's status, null without one." }, - "docs_url": { + "detail": { "type": "string", - "nullable": true + "description": "Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet." }, - "status_mapping": { - "type": "object", - "additionalProperties": { - "type": "string", - "enum": [ - "Approved", - "Declined", - "In Review", - "Expired", - "Not Finished", - "Abandoned" - ] - } + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Present on a pending test deposit: why it is not confirmed yet." } } }, - "ImportJobSummary": { + "TravelRuleWalletProofReview": { "type": "object", - "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", "properties": { - "id": { + "proof_id": { "type": "string", "format": "uuid" }, - "kind": { + "method": { "type": "string", "enum": [ - "sessions", - "businesses", - "users", - "status_rules", - "transactions" + "SCREENSHOT" ] }, - "source": { + "status": { "type": "string", "enum": [ - "file", - "api", - "provider" + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" ] }, - "provider": { - "type": "string", - "nullable": true - }, - "provider_name": { - "type": "string", - "nullable": true - }, - "filename": { - "type": "string", - "nullable": true + "wallet_address": { + "type": "string" }, - "state": { + "chain": { "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." + "description": "Canonical chain identifier, e.g. ETH, BTC, SOL, TRX." }, - "status": { + "holder_name": { "type": "string" }, - "is_dry_run": { - "type": "boolean" + "is_ownership_verified": { + "type": "boolean", + "description": "Whether the address book entry is ownership-verified, by this proof or another one." }, - "credential_hint": { + "transaction_id": { "type": "string", - "nullable": true + "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." }, - "resumes_at": { + "file_name": { "type": "string", - "format": "date-time", "nullable": true }, - "last_error": { + "content_type": { "type": "string", "nullable": true }, - "started_by": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true - }, - "email": { - "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." }, - "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" + "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." }, - "face_templates": { - "type": "object", + "method_chosen_at": { + "type": "string", + "format": "date-time", "nullable": true, - "properties": { - "enabled": { - "type": "boolean" - }, - "built": { - "type": "integer", - "format": "int64" - }, - "duplicates": { - "type": "integer", - "format": "int64" - }, - "blocklisted": { - "type": "integer", - "format": "int64" - } - } + "description": "When the end user picked the screenshot method." }, - "eta_seconds": { - "type": "integer", + "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" - }, - "started_at": { + } + } + }, + "TravelRuleWalletProofDecision": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { "type": "string", - "format": "date-time", - "nullable": true + "enum": [ + "approve", + "reject" + ] }, - "completed_at": { + "note": { "type": "string", - "format": "date-time", - "nullable": true + "maxLength": 512, + "description": "Optional. Kept with the proof and returned as review_note." } } } @@ -106402,6 +109105,56 @@ } } } + }, + "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": { diff --git a/src/main/java/me/didit/resources/AccountResource.java b/src/main/java/me/didit/resources/AccountResource.java index 3ff7590..12ebb8c 100644 --- a/src/main/java/me/didit/resources/AccountResource.java +++ b/src/main/java/me/didit/resources/AccountResource.java @@ -62,7 +62,7 @@ public WithRawResponse withRawResponse() { /** * Register Account * - *

Step 1 of programmatic onboarding. Creates a pending account and emails a 6-char code (10 min TTL). Rate limit: 5/IP/hour. + *

Step 1 of programmatic onboarding. Creates a pending account and emails a 6-char code (10 min TTL). The address is checked before any account is created: one that cannot receive the code returns 400 email_undeliverable. Rate limits: 5/IP/hour, plus a shared hourly budget per network (/24 for IPv4, /64 for IPv6). * *

{@code POST /programmatic/register/} * @@ -202,7 +202,7 @@ public CompletableFuture loginAsync(LoginRequest params) { * *

Resend the 6-character verification code for an unverified programmatic account. Requires the account's email and password so only the owner can trigger a new code (no console Basic Auth). A fresh code replaces the previous one and is valid for 10 minutes. * - *

Rate limits: a per-email cooldown of 60 seconds between resends (HTTP 429 with Retry-After), and the same 5-per-IP-per-hour budget as registration. + *

Rate limits: a per-email cooldown of 60 seconds between resends (HTTP 429 with Retry-After), and the same 5-per-IP-per-hour and per-network hourly budgets as registration. * *

{@code POST /programmatic/resend-otp/} * diff --git a/src/main/java/me/didit/resources/AddressValidationResource.java b/src/main/java/me/didit/resources/AddressValidationResource.java new file mode 100644 index 0000000..54dd7c4 --- /dev/null +++ b/src/main/java/me/didit/resources/AddressValidationResource.java @@ -0,0 +1,151 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import java.util.concurrent.CompletableFuture; +import me.didit.core.ApiClient; +import me.didit.core.ApiRequest; +import me.didit.core.Conv; +import me.didit.core.RawResponse; +import me.didit.core.RequestOptions; +import me.didit.core.ResponseHandler; +import me.didit.core.Responses; +import me.didit.types.AddressValidationRequest; +import me.didit.types.AddressValidationResponse; + +/** The {@code address_validation} resource: {@code client.addressValidation()}. */ +public final class AddressValidationResource { + private final ApiClient client; + private final WithRawResponse withRawResponse; + + AddressValidationResource(ApiClient client) { + this.client = client; + this.withRawResponse = new WithRawResponse(client); + } + + /** + * The same operations, returning the HTTP status, headers and request id with the parsed body. + * + * @return the raw response view + */ + public WithRawResponse withRawResponse() { + return withRawResponse; + } + + /** + * Validate an address + * + *

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. + * + *

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). + * + *

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. + * + *

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. + * + *

Sandbox. Sandbox keys validate the request and always return a deterministic verified result that echoes the address sent, without consuming credits. + * + *

Authentication. Send your application's API key in x-api-key. Missing or invalid credentials return 403 - this API never returns 401. + * + *

Rate limit. Shared write budget of 300 requests per minute per API key across all POST/PATCH/DELETE endpoints. + * + *

{@code POST /v3/address-validation/} + * + * @param params the request parameters + * @param options per-request options that override the client options + * @return the response body + */ + public AddressValidationResponse validate(AddressValidationRequest params, RequestOptions options) { + return client.execute(validateRequest(params), options, VALIDATE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #validate(AddressValidationRequest, RequestOptions)}. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture validateAsync(AddressValidationRequest params, RequestOptions options) { + return client.executeAsync(validateRequest(params), options, VALIDATE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #validate(AddressValidationRequest, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public AddressValidationResponse validate(AddressValidationRequest params) { + return client.execute(validateRequest(params), RequestOptions.none(), VALIDATE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #validate(AddressValidationRequest, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture validateAsync(AddressValidationRequest params) { + return client.executeAsync(validateRequest(params), RequestOptions.none(), VALIDATE_RESPONSE).thenApply(RawResponse::data); + } + + private static final ResponseHandler VALIDATE_RESPONSE = Responses.json(Conv.model(AddressValidationResponse::fromJson)); + + private static ApiRequest validateRequest(AddressValidationRequest params) { + return ApiRequest.builder("POST", "/v3/address-validation/") + .params(params) + .body(ApiRequest.BodyEncoding.JSON) + .build(); + } + + /** The {@code address_validation} operations returning {@link RawResponse}. */ + public static final class WithRawResponse { + private final ApiClient client; + + WithRawResponse(ApiClient client) { + this.client = client; + } + + /** + * {@link #validate(AddressValidationRequest, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse validate(AddressValidationRequest params, RequestOptions options) { + return client.execute(validateRequest(params), options, VALIDATE_RESPONSE); + } + + /** + * Asynchronous {@link #validate(AddressValidationRequest, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> validateAsync(AddressValidationRequest params, RequestOptions options) { + return client.executeAsync(validateRequest(params), options, VALIDATE_RESPONSE); + } + + /** + * {@link #validate(AddressValidationRequest, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public RawResponse validate(AddressValidationRequest params) { + return client.execute(validateRequest(params), RequestOptions.none(), VALIDATE_RESPONSE); + } + + /** + * Asynchronous {@link #validate(AddressValidationRequest, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> validateAsync(AddressValidationRequest params) { + return client.executeAsync(validateRequest(params), RequestOptions.none(), VALIDATE_RESPONSE); + } + } +} diff --git a/src/main/java/me/didit/resources/AgeEstimationResource.java b/src/main/java/me/didit/resources/AgeEstimationResource.java index 5d43973..e77f04f 100644 --- a/src/main/java/me/didit/resources/AgeEstimationResource.java +++ b/src/main/java/me/didit/resources/AgeEstimationResource.java @@ -40,7 +40,7 @@ public WithRawResponse withRawResponse() { * *

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. * - *

Billing. Each 200 response consumes one Age Estimation API credit (standalone APIs have no free tier). When the organization's balance cannot cover the call, the endpoint returns 403 with the not-enough-credits error before any image processing. + *

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. * *

**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. * diff --git a/src/main/java/me/didit/resources/BillingResource.java b/src/main/java/me/didit/resources/BillingResource.java index ec507d6..24a971e 100644 --- a/src/main/java/me/didit/resources/BillingResource.java +++ b/src/main/java/me/didit/resources/BillingResource.java @@ -77,7 +77,7 @@ public CompletableFuture retrieveBalanceAsync() /** * Top up credits * - *

Add credits via a hosted checkout session ($50 minimum); not available on annual plans. Redirect the customer to the returned checkout_session_url. + *

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. * *

{@code POST /v3/billing/top-up/} * diff --git a/src/main/java/me/didit/resources/BusinessesResource.java b/src/main/java/me/didit/resources/BusinessesResource.java index 060606f..66e3637 100644 --- a/src/main/java/me/didit/resources/BusinessesResource.java +++ b/src/main/java/me/didit/resources/BusinessesResource.java @@ -325,6 +325,8 @@ public CompletableFuture updateStatusAsync(String vendorData * *

Permanently delete businesses — this cannot be undone. For blocking prefer [Update Business Status](#patch-/v3/businesses/-vendor_data-/update-status/) with BLOCKED. * + *

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. + * *

{@code POST /v3/businesses/delete/} * * @param params the request parameters diff --git a/src/main/java/me/didit/resources/CasesResource.java b/src/main/java/me/didit/resources/CasesResource.java index e413882..dcd9b00 100644 --- a/src/main/java/me/didit/resources/CasesResource.java +++ b/src/main/java/me/didit/resources/CasesResource.java @@ -422,7 +422,9 @@ public CompletableFuture updateAsync(String caseUuid) { /** * Delete case * - *

Delete a case. There is no undelete endpoint. + *

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. + * + *

If 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. * *

{@code DELETE /v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/} *

Application scoped: the organization and application ids come from the client options. diff --git a/src/main/java/me/didit/resources/DiditResources.java b/src/main/java/me/didit/resources/DiditResources.java index 4353d23..a9776cd 100644 --- a/src/main/java/me/didit/resources/DiditResources.java +++ b/src/main/java/me/didit/resources/DiditResources.java @@ -9,6 +9,7 @@ */ public abstract class DiditResources { private final AccountResource account; + private final AddressValidationResource addressValidation; private final AgeEstimationResource ageEstimation; private final AmlResource aml; private final AmlMonitoringResource amlMonitoring; @@ -57,6 +58,7 @@ public abstract class DiditResources { */ protected DiditResources(ApiClient client) { this.account = new AccountResource(client); + this.addressValidation = new AddressValidationResource(client); this.ageEstimation = new AgeEstimationResource(client); this.aml = new AmlResource(client); this.amlMonitoring = new AmlMonitoringResource(client); @@ -108,6 +110,15 @@ public final AccountResource account() { return account; } + /** + * The {@code address_validation} resource. + * + * @return the resource + */ + public final AddressValidationResource addressValidation() { + return addressValidation; + } + /** * The {@code age_estimation} resource. * diff --git a/src/main/java/me/didit/resources/DocumentAiResource.java b/src/main/java/me/didit/resources/DocumentAiResource.java index ae2c4bf..53d7a47 100644 --- a/src/main/java/me/didit/resources/DocumentAiResource.java +++ b/src/main/java/me/didit/resources/DocumentAiResource.java @@ -44,7 +44,7 @@ public WithRawResponse withRawResponse() { * *

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. * - *

Billing. Each 200 response consumes one Document AI API credit — $0.20 per document, no free tier (standalone APIs never have one). 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. + *

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. * *

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/. * diff --git a/src/main/java/me/didit/resources/FaceMatchResource.java b/src/main/java/me/didit/resources/FaceMatchResource.java index 7335924..8d34731 100644 --- a/src/main/java/me/didit/resources/FaceMatchResource.java +++ b/src/main/java/me/didit/resources/FaceMatchResource.java @@ -38,7 +38,7 @@ public WithRawResponse withRawResponse() { * *

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. * - *

Billing. Each 200 response consumes one Face Match API credit (standalone APIs have no free tier). When the organization's balance cannot cover the call, the endpoint returns 403 with the not-enough-credits error before any image processing. + *

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. * *

**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. * diff --git a/src/main/java/me/didit/resources/FaceSearchResource.java b/src/main/java/me/didit/resources/FaceSearchResource.java index 45928db..f73e722 100644 --- a/src/main/java/me/didit/resources/FaceSearchResource.java +++ b/src/main/java/me/didit/resources/FaceSearchResource.java @@ -43,7 +43,7 @@ public WithRawResponse withRawResponse() { * *

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. * - *

Billing. Each 200 response consumes one Face Search API credit (standalone APIs have no free tier). Insufficient balance returns 403 before any image processing. + *

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. * *

**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. * diff --git a/src/main/java/me/didit/resources/IdVerificationResource.java b/src/main/java/me/didit/resources/IdVerificationResource.java index 5575bc3..9610325 100644 --- a/src/main/java/me/didit/resources/IdVerificationResource.java +++ b/src/main/java/me/didit/resources/IdVerificationResource.java @@ -34,13 +34,13 @@ public WithRawResponse withRawResponse() { /** * ID Verification (document OCR + fraud checks) * - *

OCR an identity document and run document fraud checks in one call — returns the extracted holder fields, the parsed MRZ, an Approved/Declined status, and a warnings list explaining every issue found. Supports 14,000+ document types from 220+ countries and territories. + *

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. * *

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. * - *

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. 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. + *

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. * - *

Billing. Each 200 response consumes one ID Verification API credit (standalone APIs have no free tier). When the organization's balance cannot cover the call, the endpoint returns 403 with the not-enough-credits error before any image processing. + *

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. * *

**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://<media-host>/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. * diff --git a/src/main/java/me/didit/resources/PassiveLivenessResource.java b/src/main/java/me/didit/resources/PassiveLivenessResource.java index 9ddd938..05a84dd 100644 --- a/src/main/java/me/didit/resources/PassiveLivenessResource.java +++ b/src/main/java/me/didit/resources/PassiveLivenessResource.java @@ -46,7 +46,7 @@ public WithRawResponse withRawResponse() { * *

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). * - *

Billing. Each 200 response consumes one Passive Liveness API credit (standalone APIs have no free tier). Insufficient balance returns 403 before any image processing. + *

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. * *

**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). * diff --git a/src/main/java/me/didit/resources/ProofOfAddressResource.java b/src/main/java/me/didit/resources/ProofOfAddressResource.java index 7157e3d..78b95b5 100644 --- a/src/main/java/me/didit/resources/ProofOfAddressResource.java +++ b/src/main/java/me/didit/resources/ProofOfAddressResource.java @@ -42,7 +42,7 @@ public WithRawResponse withRawResponse() { * *

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. * - *

Billing. Each 200 response consumes one Proof of Address API credit (standalone APIs have no free tier). When the organization's balance cannot cover the call, the endpoint returns 403 with the not-enough-credits error before any document processing. + *

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. * *

**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. * diff --git a/src/main/java/me/didit/resources/RegulatoryReportsResource.java b/src/main/java/me/didit/resources/RegulatoryReportsResource.java index 1f39e5d..bd8b3eb 100644 --- a/src/main/java/me/didit/resources/RegulatoryReportsResource.java +++ b/src/main/java/me/didit/resources/RegulatoryReportsResource.java @@ -12,6 +12,7 @@ import me.didit.core.Responses; import me.didit.types.RegulatoryReportDetail; import me.didit.types.RegulatoryReportsCreateParams; +import me.didit.types.RegulatoryReportsDeleteParams; import me.didit.types.RegulatoryReportsListParams; import me.didit.types.RegulatoryReportsRetrieveDownloadUrlParams; import me.didit.types.RegulatoryReportsRetrieveDownloadUrlResponse; @@ -314,46 +315,92 @@ public CompletableFuture updateAsync(String reportUuid) /** * Delete report * - *

Delete a draft or validated report. Finalized reports cannot be deleted (409). + *

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. + * + *

A 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. * *

{@code DELETE /v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/} *

Application scoped: the organization and application ids come from the client options. * * @param reportUuid Report UUID. + * @param params the request parameters * @param options per-request options that override the client options */ + public void delete(String reportUuid, RegulatoryReportsDeleteParams params, RequestOptions options) { + client.execute(deleteRequest(reportUuid, params), options, DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture deleteAsync(String reportUuid, RegulatoryReportsDeleteParams params, RequestOptions options) { + return client.executeAsync(deleteRequest(reportUuid, params), options, DELETE_RESPONSE).thenAccept(response -> {}); + } + + /** + * Same as {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, with the client's options. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + */ + public void delete(String reportUuid, RegulatoryReportsDeleteParams params) { + client.execute(deleteRequest(reportUuid, params), RequestOptions.none(), DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, with the client's options. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture deleteAsync(String reportUuid, RegulatoryReportsDeleteParams params) { + return client.executeAsync(deleteRequest(reportUuid, params), RequestOptions.none(), DELETE_RESPONSE).thenAccept(response -> {}); + } + + /** + * Same as {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, without parameters. + * + * @param reportUuid the {@code report_uuid} + * @param options per-request options + */ public void delete(String reportUuid, RequestOptions options) { - client.execute(deleteRequest(reportUuid), options, DELETE_RESPONSE); + client.execute(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), options, DELETE_RESPONSE); } /** - * Asynchronous {@link #delete(String, RequestOptions)}. + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, without parameters. * * @param reportUuid the {@code report_uuid} * @param options per-request options * @return a future completed with the result */ public CompletableFuture deleteAsync(String reportUuid, RequestOptions options) { - return client.executeAsync(deleteRequest(reportUuid), options, DELETE_RESPONSE).thenAccept(response -> {}); + return client.executeAsync(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), options, DELETE_RESPONSE).thenAccept(response -> {}); } /** - * Same as {@link #delete(String, RequestOptions)}, with the client's options. + * Same as {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, without parameters, with the client's options. * * @param reportUuid the {@code report_uuid} */ public void delete(String reportUuid) { - client.execute(deleteRequest(reportUuid), RequestOptions.none(), DELETE_RESPONSE); + client.execute(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), RequestOptions.none(), DELETE_RESPONSE); } /** - * Asynchronous {@link #delete(String, RequestOptions)}, with the client's options. + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)}, without parameters, with the client's options. * * @param reportUuid the {@code report_uuid} * @return a future completed with the result */ public CompletableFuture deleteAsync(String reportUuid) { - return client.executeAsync(deleteRequest(reportUuid), RequestOptions.none(), DELETE_RESPONSE).thenAccept(response -> {}); + return client.executeAsync(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), RequestOptions.none(), DELETE_RESPONSE).thenAccept(response -> {}); } /** @@ -585,10 +632,12 @@ private static ApiRequest updateRequest(String reportUuid, RegulatoryReportsUpda private static final ResponseHandler<@Nullable Void> DELETE_RESPONSE = Responses.none(); - private static ApiRequest deleteRequest(String reportUuid) { + private static ApiRequest deleteRequest(String reportUuid, RegulatoryReportsDeleteParams params) { return ApiRequest.builder("DELETE", "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/") .applicationScoped() .pathParam("report_uuid", reportUuid) + .params(params) + .body(ApiRequest.BodyEncoding.JSON) .build(); } @@ -882,45 +931,91 @@ public CompletableFuture> updateAsync(String } /** - * {@link #delete(String, RequestOptions)} with the raw response. + * {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse<@Nullable Void> delete(String reportUuid, RegulatoryReportsDeleteParams params, RequestOptions options) { + return client.execute(deleteRequest(reportUuid, params), options, DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> deleteAsync(String reportUuid, RegulatoryReportsDeleteParams params, RequestOptions options) { + return client.executeAsync(deleteRequest(reportUuid, params), options, DELETE_RESPONSE); + } + + /** + * {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, with the client's options. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @return the result + */ + public RawResponse<@Nullable Void> delete(String reportUuid, RegulatoryReportsDeleteParams params) { + return client.execute(deleteRequest(reportUuid, params), RequestOptions.none(), DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, with the client's options. + * + * @param reportUuid the {@code report_uuid} + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> deleteAsync(String reportUuid, RegulatoryReportsDeleteParams params) { + return client.executeAsync(deleteRequest(reportUuid, params), RequestOptions.none(), DELETE_RESPONSE); + } + + /** + * {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, without parameters. * * @param reportUuid the {@code report_uuid} * @param options per-request options * @return the result */ public RawResponse<@Nullable Void> delete(String reportUuid, RequestOptions options) { - return client.execute(deleteRequest(reportUuid), options, DELETE_RESPONSE); + return client.execute(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), options, DELETE_RESPONSE); } /** - * Asynchronous {@link #delete(String, RequestOptions)} with the raw response. + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, without parameters. * * @param reportUuid the {@code report_uuid} * @param options per-request options * @return a future completed with the result */ public CompletableFuture> deleteAsync(String reportUuid, RequestOptions options) { - return client.executeAsync(deleteRequest(reportUuid), options, DELETE_RESPONSE); + return client.executeAsync(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), options, DELETE_RESPONSE); } /** - * {@link #delete(String, RequestOptions)} with the raw response, with the client's options. + * {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, without parameters, with the client's options. * * @param reportUuid the {@code report_uuid} * @return the result */ public RawResponse<@Nullable Void> delete(String reportUuid) { - return client.execute(deleteRequest(reportUuid), RequestOptions.none(), DELETE_RESPONSE); + return client.execute(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), RequestOptions.none(), DELETE_RESPONSE); } /** - * Asynchronous {@link #delete(String, RequestOptions)} with the raw response, with the client's options. + * Asynchronous {@link #delete(String, RegulatoryReportsDeleteParams, RequestOptions)} with the raw response, without parameters, with the client's options. * * @param reportUuid the {@code report_uuid} * @return a future completed with the result */ public CompletableFuture> deleteAsync(String reportUuid) { - return client.executeAsync(deleteRequest(reportUuid), RequestOptions.none(), DELETE_RESPONSE); + return client.executeAsync(deleteRequest(reportUuid, RegulatoryReportsDeleteParams.builder().build()), RequestOptions.none(), DELETE_RESPONSE); } /** diff --git a/src/main/java/me/didit/resources/SessionsResource.java b/src/main/java/me/didit/resources/SessionsResource.java index 67b367d..761f7f4 100644 --- a/src/main/java/me/didit/resources/SessionsResource.java +++ b/src/main/java/me/didit/resources/SessionsResource.java @@ -312,6 +312,8 @@ public CompletableFuture retrieveDecisionAsync(String sessionId * *

To bulk-delete KYC sessions by their numeric session_number, use POST /v3/sessions/delete/ instead. * + *

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. + * *

{@code DELETE /v3/session/{sessionId}/delete/} * * @param sessionId 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`. @@ -761,6 +763,8 @@ public CompletableFuture importSharedAsync(SessionsImportShared * *

This endpoint accepts API keys only. Business Console user Bearer tokens are rejected with 403 (unlike the single-session delete, which accepts both). * + *

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. + * *

{@code POST /v3/sessions/delete/} * * @param params the request parameters diff --git a/src/main/java/me/didit/resources/TransactionsResource.java b/src/main/java/me/didit/resources/TransactionsResource.java index 40dd239..52de27d 100644 --- a/src/main/java/me/didit/resources/TransactionsResource.java +++ b/src/main/java/me/didit/resources/TransactionsResource.java @@ -14,12 +14,14 @@ import me.didit.types.EntityNetworkMembershipList; import me.didit.types.TransactionDetail; import me.didit.types.TransactionSummary; +import me.didit.types.TransactionsBatchDeleteParams; import me.didit.types.TransactionsCreateParams; import me.didit.types.TransactionsCreateSdkTokenParams; import me.didit.types.TransactionsCreateSdkTokenResponse; import me.didit.types.TransactionsListParams; import me.didit.types.TransactionsListRelatedParams; import me.didit.types.TransactionsListResponse; +import org.jspecify.annotations.Nullable; /** The {@code transactions} resource: {@code client.transactions()}. */ public final class TransactionsResource { @@ -543,6 +545,90 @@ public CompletableFuture generatePdfAsync(String transactionUuid return client.executeAsync(generatePdfRequest(transactionUuid), RequestOptions.none(), GENERATE_PDF_RESPONSE).thenApply(RawResponse::data); } + /** + * Batch delete transactions + * + *

Delete one or more transactions permanently. Select them with transaction_uuids, or delete every transaction in the application with delete_all: true. + * + *

A 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. + * + *

If 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. + * + *

{@code DELETE /v3/organization/{organization_id}/application/{application_id}/transactions/delete/} + *

Application scoped: the organization and application ids come from the client options. + * + * @param params the request parameters + * @param options per-request options that override the client options + */ + public void batchDelete(TransactionsBatchDeleteParams params, RequestOptions options) { + client.execute(batchDeleteRequest(params), options, BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture batchDeleteAsync(TransactionsBatchDeleteParams params, RequestOptions options) { + return client.executeAsync(batchDeleteRequest(params), options, BATCH_DELETE_RESPONSE).thenAccept(response -> {}); + } + + /** + * Same as {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, with the client's options. + * + * @param params the request parameters + */ + public void batchDelete(TransactionsBatchDeleteParams params) { + client.execute(batchDeleteRequest(params), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture batchDeleteAsync(TransactionsBatchDeleteParams params) { + return client.executeAsync(batchDeleteRequest(params), RequestOptions.none(), BATCH_DELETE_RESPONSE).thenAccept(response -> {}); + } + + /** + * Same as {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, without parameters. + * + * @param options per-request options + */ + public void batchDelete(RequestOptions options) { + client.execute(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), options, BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, without parameters. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture batchDeleteAsync(RequestOptions options) { + return client.executeAsync(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), options, BATCH_DELETE_RESPONSE).thenAccept(response -> {}); + } + + /** + * Same as {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, without parameters, with the client's options. + */ + public void batchDelete() { + client.execute(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)}, without parameters, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture batchDeleteAsync() { + return client.executeAsync(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), RequestOptions.none(), BATCH_DELETE_RESPONSE).thenAccept(response -> {}); + } + private static final ResponseHandler> LIST_RESPONSE = Responses.page(Conv.model(TransactionsListResponse.Results::fromJson)); private static ApiRequest listRequest(TransactionsListParams params) { @@ -617,6 +703,16 @@ private static ApiRequest generatePdfRequest(String transactionUuid) { .build(); } + private static final ResponseHandler<@Nullable Void> BATCH_DELETE_RESPONSE = Responses.none(); + + private static ApiRequest batchDeleteRequest(TransactionsBatchDeleteParams params) { + return ApiRequest.builder("DELETE", "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/") + .applicationScoped() + .params(params) + .body(ApiRequest.BodyEncoding.JSON) + .build(); + } + /** The {@code transactions} operations returning {@link RawResponse}. */ public static final class WithRawResponse { private final ApiClient client; @@ -1048,5 +1144,85 @@ public RawResponse generatePdf(String transactionUuid) { public CompletableFuture> generatePdfAsync(String transactionUuid) { return client.executeAsync(generatePdfRequest(transactionUuid), RequestOptions.none(), GENERATE_PDF_RESPONSE); } + + /** + * {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse<@Nullable Void> batchDelete(TransactionsBatchDeleteParams params, RequestOptions options) { + return client.execute(batchDeleteRequest(params), options, BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> batchDeleteAsync(TransactionsBatchDeleteParams params, RequestOptions options) { + return client.executeAsync(batchDeleteRequest(params), options, BATCH_DELETE_RESPONSE); + } + + /** + * {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public RawResponse<@Nullable Void> batchDelete(TransactionsBatchDeleteParams params) { + return client.execute(batchDeleteRequest(params), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> batchDeleteAsync(TransactionsBatchDeleteParams params) { + return client.executeAsync(batchDeleteRequest(params), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } + + /** + * {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, without parameters. + * + * @param options per-request options + * @return the result + */ + public RawResponse<@Nullable Void> batchDelete(RequestOptions options) { + return client.execute(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), options, BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, without parameters. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> batchDeleteAsync(RequestOptions options) { + return client.executeAsync(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), options, BATCH_DELETE_RESPONSE); + } + + /** + * {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, without parameters, with the client's options. + * + * @return the result + */ + public RawResponse<@Nullable Void> batchDelete() { + return client.execute(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } + + /** + * Asynchronous {@link #batchDelete(TransactionsBatchDeleteParams, RequestOptions)} with the raw response, without parameters, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture> batchDeleteAsync() { + return client.executeAsync(batchDeleteRequest(TransactionsBatchDeleteParams.builder().build()), RequestOptions.none(), BATCH_DELETE_RESPONSE); + } } } diff --git a/src/main/java/me/didit/resources/TransactionsTravelRuleResource.java b/src/main/java/me/didit/resources/TransactionsTravelRuleResource.java index fe4f448..fd66fc2 100644 --- a/src/main/java/me/didit/resources/TransactionsTravelRuleResource.java +++ b/src/main/java/me/didit/resources/TransactionsTravelRuleResource.java @@ -40,7 +40,7 @@ public WithRawResponse withRawResponse() { /** * Finish, cancel, resend, or update the originator wallet of a Travel Rule transfer * - *

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. + *

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": "<directory VASP id>"} 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. * *

{@code PATCH /v3/transactions/{transaction_id}/travel-rule/} * diff --git a/src/main/java/me/didit/resources/TravelRuleNetworksResource.java b/src/main/java/me/didit/resources/TravelRuleNetworksResource.java new file mode 100644 index 0000000..17b99bf --- /dev/null +++ b/src/main/java/me/didit/resources/TravelRuleNetworksResource.java @@ -0,0 +1,498 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import java.util.concurrent.CompletableFuture; +import me.didit.core.ApiClient; +import me.didit.core.ApiRequest; +import me.didit.core.Conv; +import me.didit.core.RawResponse; +import me.didit.core.RequestOptions; +import me.didit.core.ResponseHandler; +import me.didit.core.Responses; +import me.didit.types.TravelRuleNetworkParticipant; +import me.didit.types.TravelRuleNetworkQuestionnaire; +import me.didit.types.TravelRuleNetworkQuestionnaireAnswers; +import me.didit.types.TravelRuleNetworksOverview; + +/** The {@code travel_rule.networks} resource: {@code client.travelRule().networks()}. */ +public final class TravelRuleNetworksResource { + private final ApiClient client; + private final WithRawResponse withRawResponse; + + TravelRuleNetworksResource(ApiClient client) { + this.client = client; + this.withRawResponse = new WithRawResponse(client); + } + + /** + * The same operations, returning the HTTP status, headers and request id with the parsed body. + * + * @return the raw response view + */ + public WithRawResponse withRawResponse() { + return withRawResponse; + } + + /** + * Get Travel Rule networks + * + *

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. + * + *

{@code GET /v3/travel-rule/networks/} + * + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleNetworksOverview retrieve(RequestOptions options) { + return client.execute(retrieveRequest(), options, RETRIEVE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #retrieve(RequestOptions)}. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture retrieveAsync(RequestOptions options) { + return client.executeAsync(retrieveRequest(), options, RETRIEVE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #retrieve(RequestOptions)}, with the client's options. + * + * @return the result + */ + public TravelRuleNetworksOverview retrieve() { + return client.execute(retrieveRequest(), RequestOptions.none(), RETRIEVE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #retrieve(RequestOptions)}, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture retrieveAsync() { + return client.executeAsync(retrieveRequest(), RequestOptions.none(), RETRIEVE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Get the network questionnaire + * + *

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. + * + *

{@code GET /v3/travel-rule/networks/questionnaire/} + * + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleNetworkQuestionnaire retrieveQuestionnaire(RequestOptions options) { + return client.execute(retrieveQuestionnaireRequest(), options, RETRIEVE_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #retrieveQuestionnaire(RequestOptions)}. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture retrieveQuestionnaireAsync(RequestOptions options) { + return client.executeAsync(retrieveQuestionnaireRequest(), options, RETRIEVE_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #retrieveQuestionnaire(RequestOptions)}, with the client's options. + * + * @return the result + */ + public TravelRuleNetworkQuestionnaire retrieveQuestionnaire() { + return client.execute(retrieveQuestionnaireRequest(), RequestOptions.none(), RETRIEVE_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #retrieveQuestionnaire(RequestOptions)}, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture retrieveQuestionnaireAsync() { + return client.executeAsync(retrieveQuestionnaireRequest(), RequestOptions.none(), RETRIEVE_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Save network questionnaire answers + * + *

Save answers to the questionnaire while it is draft or changes_requested. Saves are partial: answers you do not send are kept. + * + *

{@code PUT /v3/travel-rule/networks/questionnaire/} + * + * @param params the request parameters + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleNetworkQuestionnaire updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers params, RequestOptions options) { + return client.execute(updateQuestionnaireRequest(params), options, UPDATE_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)}. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture updateQuestionnaireAsync(TravelRuleNetworkQuestionnaireAnswers params, RequestOptions options) { + return client.executeAsync(updateQuestionnaireRequest(params), options, UPDATE_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public TravelRuleNetworkQuestionnaire updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers params) { + return client.execute(updateQuestionnaireRequest(params), RequestOptions.none(), UPDATE_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture updateQuestionnaireAsync(TravelRuleNetworkQuestionnaireAnswers params) { + return client.executeAsync(updateQuestionnaireRequest(params), RequestOptions.none(), UPDATE_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Submit the network questionnaire + * + *

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. + * + *

{@code POST /v3/travel-rule/networks/questionnaire/submit/} + * + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleNetworkQuestionnaire submitQuestionnaire(RequestOptions options) { + return client.execute(submitQuestionnaireRequest(), options, SUBMIT_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #submitQuestionnaire(RequestOptions)}. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture submitQuestionnaireAsync(RequestOptions options) { + return client.executeAsync(submitQuestionnaireRequest(), options, SUBMIT_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #submitQuestionnaire(RequestOptions)}, with the client's options. + * + * @return the result + */ + public TravelRuleNetworkQuestionnaire submitQuestionnaire() { + return client.execute(submitQuestionnaireRequest(), RequestOptions.none(), SUBMIT_QUESTIONNAIRE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #submitQuestionnaire(RequestOptions)}, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture submitQuestionnaireAsync() { + return client.executeAsync(submitQuestionnaireRequest(), RequestOptions.none(), SUBMIT_QUESTIONNAIRE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Request a Travel Rule network + * + *

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. + * + *

{@code POST /v3/travel-rule/networks/{network}/request/} + * + * @param network The network to join through Didit's membership. + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleNetworkParticipant requestAccess(String network, RequestOptions options) { + return client.execute(requestAccessRequest(network), options, REQUEST_ACCESS_RESPONSE).data(); + } + + /** + * Asynchronous {@link #requestAccess(String, RequestOptions)}. + * + * @param network the {@code network} + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture requestAccessAsync(String network, RequestOptions options) { + return client.executeAsync(requestAccessRequest(network), options, REQUEST_ACCESS_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #requestAccess(String, RequestOptions)}, with the client's options. + * + * @param network the {@code network} + * @return the result + */ + public TravelRuleNetworkParticipant requestAccess(String network) { + return client.execute(requestAccessRequest(network), RequestOptions.none(), REQUEST_ACCESS_RESPONSE).data(); + } + + /** + * Asynchronous {@link #requestAccess(String, RequestOptions)}, with the client's options. + * + * @param network the {@code network} + * @return a future completed with the result + */ + public CompletableFuture requestAccessAsync(String network) { + return client.executeAsync(requestAccessRequest(network), RequestOptions.none(), REQUEST_ACCESS_RESPONSE).thenApply(RawResponse::data); + } + + private static final ResponseHandler RETRIEVE_RESPONSE = Responses.json(Conv.model(TravelRuleNetworksOverview::fromJson)); + + private static ApiRequest retrieveRequest() { + return ApiRequest.builder("GET", "/v3/travel-rule/networks/") + .build(); + } + + private static final ResponseHandler RETRIEVE_QUESTIONNAIRE_RESPONSE = Responses.json(Conv.model(TravelRuleNetworkQuestionnaire::fromJson)); + + private static ApiRequest retrieveQuestionnaireRequest() { + return ApiRequest.builder("GET", "/v3/travel-rule/networks/questionnaire/") + .build(); + } + + private static final ResponseHandler UPDATE_QUESTIONNAIRE_RESPONSE = Responses.json(Conv.model(TravelRuleNetworkQuestionnaire::fromJson)); + + private static ApiRequest updateQuestionnaireRequest(TravelRuleNetworkQuestionnaireAnswers params) { + return ApiRequest.builder("PUT", "/v3/travel-rule/networks/questionnaire/") + .params(params) + .body(ApiRequest.BodyEncoding.JSON) + .build(); + } + + private static final ResponseHandler SUBMIT_QUESTIONNAIRE_RESPONSE = Responses.json(Conv.model(TravelRuleNetworkQuestionnaire::fromJson)); + + private static ApiRequest submitQuestionnaireRequest() { + return ApiRequest.builder("POST", "/v3/travel-rule/networks/questionnaire/submit/") + .build(); + } + + private static final ResponseHandler REQUEST_ACCESS_RESPONSE = Responses.json(Conv.model(TravelRuleNetworkParticipant::fromJson)); + + private static ApiRequest requestAccessRequest(String network) { + return ApiRequest.builder("POST", "/v3/travel-rule/networks/{network}/request/") + .pathParam("network", network) + .build(); + } + + /** The {@code travel_rule.networks} operations returning {@link RawResponse}. */ + public static final class WithRawResponse { + private final ApiClient client; + + WithRawResponse(ApiClient client) { + this.client = client; + } + + /** + * {@link #retrieve(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return the result + */ + public RawResponse retrieve(RequestOptions options) { + return client.execute(retrieveRequest(), options, RETRIEVE_RESPONSE); + } + + /** + * Asynchronous {@link #retrieve(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> retrieveAsync(RequestOptions options) { + return client.executeAsync(retrieveRequest(), options, RETRIEVE_RESPONSE); + } + + /** + * {@link #retrieve(RequestOptions)} with the raw response, with the client's options. + * + * @return the result + */ + public RawResponse retrieve() { + return client.execute(retrieveRequest(), RequestOptions.none(), RETRIEVE_RESPONSE); + } + + /** + * Asynchronous {@link #retrieve(RequestOptions)} with the raw response, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture> retrieveAsync() { + return client.executeAsync(retrieveRequest(), RequestOptions.none(), RETRIEVE_RESPONSE); + } + + /** + * {@link #retrieveQuestionnaire(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return the result + */ + public RawResponse retrieveQuestionnaire(RequestOptions options) { + return client.execute(retrieveQuestionnaireRequest(), options, RETRIEVE_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #retrieveQuestionnaire(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> retrieveQuestionnaireAsync(RequestOptions options) { + return client.executeAsync(retrieveQuestionnaireRequest(), options, RETRIEVE_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #retrieveQuestionnaire(RequestOptions)} with the raw response, with the client's options. + * + * @return the result + */ + public RawResponse retrieveQuestionnaire() { + return client.execute(retrieveQuestionnaireRequest(), RequestOptions.none(), RETRIEVE_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #retrieveQuestionnaire(RequestOptions)} with the raw response, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture> retrieveQuestionnaireAsync() { + return client.executeAsync(retrieveQuestionnaireRequest(), RequestOptions.none(), RETRIEVE_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers params, RequestOptions options) { + return client.execute(updateQuestionnaireRequest(params), options, UPDATE_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> updateQuestionnaireAsync(TravelRuleNetworkQuestionnaireAnswers params, RequestOptions options) { + return client.executeAsync(updateQuestionnaireRequest(params), options, UPDATE_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public RawResponse updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers params) { + return client.execute(updateQuestionnaireRequest(params), RequestOptions.none(), UPDATE_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> updateQuestionnaireAsync(TravelRuleNetworkQuestionnaireAnswers params) { + return client.executeAsync(updateQuestionnaireRequest(params), RequestOptions.none(), UPDATE_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #submitQuestionnaire(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return the result + */ + public RawResponse submitQuestionnaire(RequestOptions options) { + return client.execute(submitQuestionnaireRequest(), options, SUBMIT_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #submitQuestionnaire(RequestOptions)} with the raw response. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> submitQuestionnaireAsync(RequestOptions options) { + return client.executeAsync(submitQuestionnaireRequest(), options, SUBMIT_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #submitQuestionnaire(RequestOptions)} with the raw response, with the client's options. + * + * @return the result + */ + public RawResponse submitQuestionnaire() { + return client.execute(submitQuestionnaireRequest(), RequestOptions.none(), SUBMIT_QUESTIONNAIRE_RESPONSE); + } + + /** + * Asynchronous {@link #submitQuestionnaire(RequestOptions)} with the raw response, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture> submitQuestionnaireAsync() { + return client.executeAsync(submitQuestionnaireRequest(), RequestOptions.none(), SUBMIT_QUESTIONNAIRE_RESPONSE); + } + + /** + * {@link #requestAccess(String, RequestOptions)} with the raw response. + * + * @param network the {@code network} + * @param options per-request options + * @return the result + */ + public RawResponse requestAccess(String network, RequestOptions options) { + return client.execute(requestAccessRequest(network), options, REQUEST_ACCESS_RESPONSE); + } + + /** + * Asynchronous {@link #requestAccess(String, RequestOptions)} with the raw response. + * + * @param network the {@code network} + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> requestAccessAsync(String network, RequestOptions options) { + return client.executeAsync(requestAccessRequest(network), options, REQUEST_ACCESS_RESPONSE); + } + + /** + * {@link #requestAccess(String, RequestOptions)} with the raw response, with the client's options. + * + * @param network the {@code network} + * @return the result + */ + public RawResponse requestAccess(String network) { + return client.execute(requestAccessRequest(network), RequestOptions.none(), REQUEST_ACCESS_RESPONSE); + } + + /** + * Asynchronous {@link #requestAccess(String, RequestOptions)} with the raw response, with the client's options. + * + * @param network the {@code network} + * @return a future completed with the result + */ + public CompletableFuture> requestAccessAsync(String network) { + return client.executeAsync(requestAccessRequest(network), RequestOptions.none(), REQUEST_ACCESS_RESPONSE); + } + } +} diff --git a/src/main/java/me/didit/resources/TravelRuleResource.java b/src/main/java/me/didit/resources/TravelRuleResource.java index 6d1943f..cd288ff 100644 --- a/src/main/java/me/didit/resources/TravelRuleResource.java +++ b/src/main/java/me/didit/resources/TravelRuleResource.java @@ -33,16 +33,20 @@ public final class TravelRuleResource { private final ApiClient client; private final WithRawResponse withRawResponse; private final TravelRuleCounterpartyContactsResource counterpartyContacts; + private final TravelRuleNetworksResource networks; private final TravelRuleTransfersResource transfers; private final TravelRuleWalletAddressesResource walletAddresses; + private final TravelRuleWalletProofsResource walletProofs; private final TravelRuleWorkspaceResource workspace; TravelRuleResource(ApiClient client) { this.client = client; this.withRawResponse = new WithRawResponse(client); this.counterpartyContacts = new TravelRuleCounterpartyContactsResource(client); + this.networks = new TravelRuleNetworksResource(client); this.transfers = new TravelRuleTransfersResource(client); this.walletAddresses = new TravelRuleWalletAddressesResource(client); + this.walletProofs = new TravelRuleWalletProofsResource(client); this.workspace = new TravelRuleWorkspaceResource(client); } @@ -55,6 +59,15 @@ public TravelRuleCounterpartyContactsResource counterpartyContacts() { return counterpartyContacts; } + /** + * The {@code travel_rule.networks} resource. + * + * @return the resource + */ + public TravelRuleNetworksResource networks() { + return networks; + } + /** * The {@code travel_rule.transfers} resource. * @@ -73,6 +86,15 @@ public TravelRuleWalletAddressesResource walletAddresses() { return walletAddresses; } + /** + * The {@code travel_rule.wallet_proofs} resource. + * + * @return the resource + */ + public TravelRuleWalletProofsResource walletProofs() { + return walletProofs; + } + /** * The {@code travel_rule.workspace} resource. * diff --git a/src/main/java/me/didit/resources/TravelRuleWalletProofsResource.java b/src/main/java/me/didit/resources/TravelRuleWalletProofsResource.java new file mode 100644 index 0000000..b470104 --- /dev/null +++ b/src/main/java/me/didit/resources/TravelRuleWalletProofsResource.java @@ -0,0 +1,324 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import java.util.concurrent.CompletableFuture; +import me.didit.core.ApiClient; +import me.didit.core.ApiRequest; +import me.didit.core.Conv; +import me.didit.core.Page; +import me.didit.core.RawResponse; +import me.didit.core.RequestOptions; +import me.didit.core.ResponseHandler; +import me.didit.core.Responses; +import me.didit.types.TravelRuleWalletProofDecision; +import me.didit.types.TravelRuleWalletProofReview; +import me.didit.types.TravelRuleWalletProofsListParams; + +/** The {@code travel_rule.wallet_proofs} resource: {@code client.travelRule().walletProofs()}. */ +public final class TravelRuleWalletProofsResource { + private final ApiClient client; + private final WithRawResponse withRawResponse; + + TravelRuleWalletProofsResource(ApiClient client) { + this.client = client; + this.withRawResponse = new WithRawResponse(client); + } + + /** + * The same operations, returning the HTTP status, headers and request id with the parsed body. + * + * @return the raw response view + */ + public WithRawResponse withRawResponse() { + return withRawResponse; + } + + /** + * List screenshot wallet proofs to review + * + *

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). + * + *

{@code GET /v3/travel-rule/wallet-proofs/} + *

Returns one page; iterate it to fetch every following page automatically. + * + * @param params the request parameters + * @param options per-request options that override the client options + * @return the response body + */ + public Page list(TravelRuleWalletProofsListParams params, RequestOptions options) { + return client.execute(listRequest(params), options, LIST_RESPONSE).data(); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> listAsync(TravelRuleWalletProofsListParams params, RequestOptions options) { + return client.executeAsync(listRequest(params), options, LIST_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public Page list(TravelRuleWalletProofsListParams params) { + return client.execute(listRequest(params), RequestOptions.none(), LIST_RESPONSE).data(); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> listAsync(TravelRuleWalletProofsListParams params) { + return client.executeAsync(listRequest(params), RequestOptions.none(), LIST_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, without parameters. + * + * @param options per-request options + * @return the result + */ + public Page list(RequestOptions options) { + return client.execute(listRequest(TravelRuleWalletProofsListParams.builder().build()), options, LIST_RESPONSE).data(); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, without parameters. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> listAsync(RequestOptions options) { + return client.executeAsync(listRequest(TravelRuleWalletProofsListParams.builder().build()), options, LIST_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, without parameters, with the client's options. + * + * @return the result + */ + public Page list() { + return client.execute(listRequest(TravelRuleWalletProofsListParams.builder().build()), RequestOptions.none(), LIST_RESPONSE).data(); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)}, without parameters, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture> listAsync() { + return client.executeAsync(listRequest(TravelRuleWalletProofsListParams.builder().build()), RequestOptions.none(), LIST_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Approve or reject a screenshot wallet proof + * + *

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. + * + *

{@code POST /v3/travel-rule/wallet-proofs/{proof_uuid}/decision/} + * + * @param proofUuid proof_id from the review queue. + * @param params the request parameters + * @param options per-request options that override the client options + * @return the response body + */ + public TravelRuleWalletProofReview decide(String proofUuid, TravelRuleWalletProofDecision params, RequestOptions options) { + return client.execute(decideRequest(proofUuid, params), options, DECIDE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)}. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture decideAsync(String proofUuid, TravelRuleWalletProofDecision params, RequestOptions options) { + return client.executeAsync(decideRequest(proofUuid, params), options, DECIDE_RESPONSE).thenApply(RawResponse::data); + } + + /** + * Same as {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)}, with the client's options. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @return the result + */ + public TravelRuleWalletProofReview decide(String proofUuid, TravelRuleWalletProofDecision params) { + return client.execute(decideRequest(proofUuid, params), RequestOptions.none(), DECIDE_RESPONSE).data(); + } + + /** + * Asynchronous {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)}, with the client's options. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture decideAsync(String proofUuid, TravelRuleWalletProofDecision params) { + return client.executeAsync(decideRequest(proofUuid, params), RequestOptions.none(), DECIDE_RESPONSE).thenApply(RawResponse::data); + } + + private static final ResponseHandler> LIST_RESPONSE = Responses.page(Conv.model(TravelRuleWalletProofReview::fromJson)); + + private static ApiRequest listRequest(TravelRuleWalletProofsListParams params) { + return ApiRequest.builder("GET", "/v3/travel-rule/wallet-proofs/") + .params(params) + .queryFields("status", "limit", "offset") + .build(); + } + + private static final ResponseHandler DECIDE_RESPONSE = Responses.json(Conv.model(TravelRuleWalletProofReview::fromJson)); + + private static ApiRequest decideRequest(String proofUuid, TravelRuleWalletProofDecision params) { + return ApiRequest.builder("POST", "/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/") + .pathParam("proof_uuid", proofUuid) + .params(params) + .body(ApiRequest.BodyEncoding.JSON) + .build(); + } + + /** The {@code travel_rule.wallet_proofs} operations returning {@link RawResponse}. */ + public static final class WithRawResponse { + private final ApiClient client; + + WithRawResponse(ApiClient client) { + this.client = client; + } + + /** + * {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse> list(TravelRuleWalletProofsListParams params, RequestOptions options) { + return client.execute(listRequest(params), options, LIST_RESPONSE); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response. + * + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture>> listAsync(TravelRuleWalletProofsListParams params, RequestOptions options) { + return client.executeAsync(listRequest(params), options, LIST_RESPONSE); + } + + /** + * {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return the result + */ + public RawResponse> list(TravelRuleWalletProofsListParams params) { + return client.execute(listRequest(params), RequestOptions.none(), LIST_RESPONSE); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, with the client's options. + * + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture>> listAsync(TravelRuleWalletProofsListParams params) { + return client.executeAsync(listRequest(params), RequestOptions.none(), LIST_RESPONSE); + } + + /** + * {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, without parameters. + * + * @param options per-request options + * @return the result + */ + public RawResponse> list(RequestOptions options) { + return client.execute(listRequest(TravelRuleWalletProofsListParams.builder().build()), options, LIST_RESPONSE); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, without parameters. + * + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture>> listAsync(RequestOptions options) { + return client.executeAsync(listRequest(TravelRuleWalletProofsListParams.builder().build()), options, LIST_RESPONSE); + } + + /** + * {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, without parameters, with the client's options. + * + * @return the result + */ + public RawResponse> list() { + return client.execute(listRequest(TravelRuleWalletProofsListParams.builder().build()), RequestOptions.none(), LIST_RESPONSE); + } + + /** + * Asynchronous {@link #list(TravelRuleWalletProofsListParams, RequestOptions)} with the raw response, without parameters, with the client's options. + * + * @return a future completed with the result + */ + public CompletableFuture>> listAsync() { + return client.executeAsync(listRequest(TravelRuleWalletProofsListParams.builder().build()), RequestOptions.none(), LIST_RESPONSE); + } + + /** + * {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)} with the raw response. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @param options per-request options + * @return the result + */ + public RawResponse decide(String proofUuid, TravelRuleWalletProofDecision params, RequestOptions options) { + return client.execute(decideRequest(proofUuid, params), options, DECIDE_RESPONSE); + } + + /** + * Asynchronous {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)} with the raw response. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @param options per-request options + * @return a future completed with the result + */ + public CompletableFuture> decideAsync(String proofUuid, TravelRuleWalletProofDecision params, RequestOptions options) { + return client.executeAsync(decideRequest(proofUuid, params), options, DECIDE_RESPONSE); + } + + /** + * {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)} with the raw response, with the client's options. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @return the result + */ + public RawResponse decide(String proofUuid, TravelRuleWalletProofDecision params) { + return client.execute(decideRequest(proofUuid, params), RequestOptions.none(), DECIDE_RESPONSE); + } + + /** + * Asynchronous {@link #decide(String, TravelRuleWalletProofDecision, RequestOptions)} with the raw response, with the client's options. + * + * @param proofUuid the {@code proof_uuid} + * @param params the request parameters + * @return a future completed with the result + */ + public CompletableFuture> decideAsync(String proofUuid, TravelRuleWalletProofDecision params) { + return client.executeAsync(decideRequest(proofUuid, params), RequestOptions.none(), DECIDE_RESPONSE); + } + } +} diff --git a/src/main/java/me/didit/resources/UsersResource.java b/src/main/java/me/didit/resources/UsersResource.java index 557d0d3..3eda0dc 100644 --- a/src/main/java/me/didit/resources/UsersResource.java +++ b/src/main/java/me/didit/resources/UsersResource.java @@ -341,6 +341,8 @@ public CompletableFuture generatePdfAsync(String vendorData) { * *

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. * + *

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. + * *

{@code POST /v3/users/delete/} * * @param params the request parameters diff --git a/src/main/java/me/didit/types/AddressValidationRequest.java b/src/main/java/me/didit/types/AddressValidationRequest.java new file mode 100644 index 0000000..a95dc3d --- /dev/null +++ b/src/main/java/me/didit/types/AddressValidationRequest.java @@ -0,0 +1,223 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +/** + * One address: on one line (address) or as structured fields with at least address_line_1, never both. + */ +public final class AddressValidationRequest extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("address", "address_line_1", "address_line_2", "city", "region", "postal_code", "country"); + + private AddressValidationRequest(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code AddressValidationRequest} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static AddressValidationRequest fromJson(JsonNode json) { + return new AddressValidationRequest(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The whole address on one line. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional address() { + return Json.optional(json, "address", Conv.STRING); + } + + /** + * Street and number. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressLine1() { + return Json.optional(json, "address_line_1", Conv.STRING); + } + + /** + * Apartment, suite, floor or building. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressLine2() { + return Json.optional(json, "address_line_2", Conv.STRING); + } + + /** + * City or locality. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional city() { + return Json.optional(json, "city", Conv.STRING); + } + + /** + * State, province or region. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional region() { + return Json.optional(json, "region", Conv.STRING); + } + + /** + * The postal_code field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional postalCode() { + return Json.optional(json, "postal_code", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 or alpha-3 code of the address's country. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String country() { + return Json.required(json, "country", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link AddressValidationRequest}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(AddressValidationRequest from) { + super(from); + } + + /** + * The whole address on one line. + * + * @param address the value + * @return this builder + */ + public Builder address(String address) { + return set("address", Conv.STRING, address); + } + + /** + * Street and number. + * + * @param addressLine1 the value + * @return this builder + */ + public Builder addressLine1(String addressLine1) { + return set("address_line_1", Conv.STRING, addressLine1); + } + + /** + * Apartment, suite, floor or building. + * + * @param addressLine2 the value + * @return this builder + */ + public Builder addressLine2(String addressLine2) { + return set("address_line_2", Conv.STRING, addressLine2); + } + + /** + * City or locality. + * + * @param city the value + * @return this builder + */ + public Builder city(String city) { + return set("city", Conv.STRING, city); + } + + /** + * State, province or region. + * + * @param region the value + * @return this builder + */ + public Builder region(String region) { + return set("region", Conv.STRING, region); + } + + /** + * The postal_code field. + * + * @param postalCode the value + * @return this builder + */ + public Builder postalCode(String postalCode) { + return set("postal_code", Conv.STRING, postalCode); + } + + /** + * ISO 3166-1 alpha-2 or alpha-3 code of the address's country. + * + * @param country the value + * @return this builder + */ + public Builder country(String country) { + return set("country", Conv.STRING, country); + } + + /** + * Builds the value. + * + * @return a new immutable {@code AddressValidationRequest} + * @throws IllegalStateException when a required field is missing + */ + public AddressValidationRequest build() { + require("country", false); + return new AddressValidationRequest(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/AddressValidationResponse.java b/src/main/java/me/didit/types/AddressValidationResponse.java new file mode 100644 index 0000000..f96dd54 --- /dev/null +++ b/src/main/java/me/didit/types/AddressValidationResponse.java @@ -0,0 +1,142 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +public final class AddressValidationResponse extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("request_id", "address_validation", "created_at"); + + private AddressValidationResponse(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code AddressValidationResponse} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static AddressValidationResponse fromJson(JsonNode json) { + return new AddressValidationResponse(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Identifier of this request; quote it to support. No session is created. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String requestId() { + return Json.required(json, "request_id", Conv.STRING); + } + + /** + * 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. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public AddressValidationResult addressValidation() { + return Json.required(json, "address_validation", Conv.model(AddressValidationResult::fromJson)); + } + + /** + * The created_at field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public OffsetDateTime createdAt() { + return Json.required(json, "created_at", Conv.DATE_TIME); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link AddressValidationResponse}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(AddressValidationResponse from) { + super(from); + } + + /** + * Identifier of this request; quote it to support. No session is created. + * + * @param requestId the value + * @return this builder + */ + public Builder requestId(String requestId) { + return set("request_id", Conv.STRING, requestId); + } + + /** + * 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. + * + * @param addressValidation the value + * @return this builder + */ + public Builder addressValidation(AddressValidationResult addressValidation) { + return set("address_validation", Conv.model(AddressValidationResult::fromJson), addressValidation); + } + + /** + * The created_at field. + * + * @param createdAt the value + * @return this builder + */ + public Builder createdAt(OffsetDateTime createdAt) { + return set("created_at", Conv.DATE_TIME, createdAt); + } + + /** + * Builds the value. + * + * @return a new immutable {@code AddressValidationResponse} + * @throws IllegalStateException when a required field is missing + */ + public AddressValidationResponse build() { + require("request_id", false); + require("address_validation", false); + require("created_at", false); + return new AddressValidationResponse(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/AddressValidationResult.java b/src/main/java/me/didit/types/AddressValidationResult.java new file mode 100644 index 0000000..d722a5a --- /dev/null +++ b/src/main/java/me/didit/types/AddressValidationResult.java @@ -0,0 +1,499 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +/** + * 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. + */ +public final class AddressValidationResult extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("status", "verification_level", "address", "formatted_address", "latitude", "longitude"); + + private AddressValidationResult(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code AddressValidationResult} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static AddressValidationResult fromJson(JsonNode json) { + return new AddressValidationResult(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * 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. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public AddressValidationResult.Status status() { + return Json.required(json, "status", Conv.enumeration(AddressValidationResult.Status::of)); + } + + /** + * The most precise level the address was matched to. null when nothing matched or status is unavailable. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional verificationLevel() { + return Json.optional(json, "verification_level", Conv.enumeration(AddressValidationResult.VerificationLevel::of)); + } + + /** + * The normalised address. null when status is unavailable. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional address() { + return Json.optional(json, "address", Conv.model(AddressValidationResult.Address::fromJson)); + } + + /** + * The normalised address on one line. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional formattedAddress() { + return Json.optional(json, "formatted_address", Conv.STRING); + } + + /** + * The latitude field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional latitude() { + return Json.optional(json, "latitude", Conv.DOUBLE); + } + + /** + * The longitude field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional longitude() { + return Json.optional(json, "longitude", Conv.DOUBLE); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link AddressValidationResult}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(AddressValidationResult from) { + super(from); + } + + /** + * 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. + * + * @param status the value + * @return this builder + */ + public Builder status(AddressValidationResult.Status status) { + return set("status", Conv.enumeration(AddressValidationResult.Status::of), status); + } + + /** + * The most precise level the address was matched to. null when nothing matched or status is unavailable. + * + * @param verificationLevel the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder verificationLevel(AddressValidationResult.@Nullable VerificationLevel verificationLevel) { + return setNullable("verification_level", Conv.enumeration(AddressValidationResult.VerificationLevel::of), verificationLevel); + } + + /** + * The normalised address. null when status is unavailable. + * + * @param address the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder address(AddressValidationResult.@Nullable Address address) { + return setNullable("address", Conv.model(AddressValidationResult.Address::fromJson), address); + } + + /** + * The normalised address on one line. + * + * @param formattedAddress the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder formattedAddress(@Nullable String formattedAddress) { + return setNullable("formatted_address", Conv.STRING, formattedAddress); + } + + /** + * The latitude field. + * + * @param latitude the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder latitude(@Nullable Double latitude) { + return setNullable("latitude", Conv.DOUBLE, latitude); + } + + /** + * The longitude field. + * + * @param longitude the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder longitude(@Nullable Double longitude) { + return setNullable("longitude", Conv.DOUBLE, longitude); + } + + /** + * Builds the value. + * + * @return a new immutable {@code AddressValidationResult} + * @throws IllegalStateException when a required field is missing + */ + public AddressValidationResult build() { + require("status", false); + require("verification_level", true); + require("address", true); + require("formatted_address", true); + require("latitude", true); + require("longitude", true); + return new AddressValidationResult(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "verified"}. */ + public static final Status VERIFIED = new Status("verified"); + + /** The value {@code "partially_verified"}. */ + public static final Status PARTIALLY_VERIFIED = new Status("partially_verified"); + + /** The value {@code "not_verified"}. */ + public static final Status NOT_VERIFIED = new Status("not_verified"); + + /** The value {@code "unavailable"}. */ + public static final Status UNAVAILABLE = new Status("unavailable"); + + private static final List KNOWN_VALUES = List.of(VERIFIED, PARTIALLY_VERIFIED, NOT_VERIFIED, UNAVAILABLE); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * The most precise level the address was matched to. null when nothing matched or status is unavailable. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class VerificationLevel extends StringEnum { + /** The value {@code "administrative_area"}. */ + public static final VerificationLevel ADMINISTRATIVE_AREA = new VerificationLevel("administrative_area"); + + /** The value {@code "locality"}. */ + public static final VerificationLevel LOCALITY = new VerificationLevel("locality"); + + /** The value {@code "thoroughfare"}. */ + public static final VerificationLevel THOROUGHFARE = new VerificationLevel("thoroughfare"); + + /** The value {@code "premise"}. */ + public static final VerificationLevel PREMISE = new VerificationLevel("premise"); + + /** The value {@code "delivery_point"}. */ + public static final VerificationLevel DELIVERY_POINT = new VerificationLevel("delivery_point"); + + private static final List KNOWN_VALUES = List.of(ADMINISTRATIVE_AREA, LOCALITY, THOROUGHFARE, PREMISE, DELIVERY_POINT); + + private VerificationLevel(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static VerificationLevel of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, VerificationLevel::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * The normalised address. null when status is unavailable. + */ + public static final class Address extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("address_line_1", "address_line_2", "city", "region", "postal_code", "country"); + + private Address(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code Address} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static Address fromJson(JsonNode json) { + return new Address(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * First delivery line, written the way the country writes it. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressLine1() { + return Json.optional(json, "address_line_1", Conv.STRING); + } + + /** + * Second delivery line (a flat, building or neighbourhood), when the address has one. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressLine2() { + return Json.optional(json, "address_line_2", Conv.STRING); + } + + /** + * The city field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional city() { + return Json.optional(json, "city", Conv.STRING); + } + + /** + * State, province or region. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional region() { + return Json.optional(json, "region", Conv.STRING); + } + + /** + * The postal_code field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional postalCode() { + return Json.optional(json, "postal_code", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional country() { + return Json.optional(json, "country", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link Address}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(Address from) { + super(from); + } + + /** + * First delivery line, written the way the country writes it. + * + * @param addressLine1 the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder addressLine1(@Nullable String addressLine1) { + return setNullable("address_line_1", Conv.STRING, addressLine1); + } + + /** + * Second delivery line (a flat, building or neighbourhood), when the address has one. + * + * @param addressLine2 the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder addressLine2(@Nullable String addressLine2) { + return setNullable("address_line_2", Conv.STRING, addressLine2); + } + + /** + * The city field. + * + * @param city the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder city(@Nullable String city) { + return setNullable("city", Conv.STRING, city); + } + + /** + * State, province or region. + * + * @param region the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder region(@Nullable String region) { + return setNullable("region", Conv.STRING, region); + } + + /** + * The postal_code field. + * + * @param postalCode the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder postalCode(@Nullable String postalCode) { + return setNullable("postal_code", Conv.STRING, postalCode); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @param country the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder country(@Nullable String country) { + return setNullable("country", Conv.STRING, country); + } + + /** + * Builds the value. + * + * @return a new immutable {@code Address} + */ + public Address build() { + return new Address(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/src/main/java/me/didit/types/Application.java b/src/main/java/me/didit/types/Application.java index b882c55..620ebee 100644 --- a/src/main/java/me/didit/types/Application.java +++ b/src/main/java/me/didit/types/Application.java @@ -84,7 +84,7 @@ public Optional clientId() { } /** - * Long-lived secret (also called client_secret). Use as the x-api-key header for every call to https://verification.didit.me/v3/... (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with sk_live_ (live application) or sk_test_ (sandbox application), followed by 43 URL-safe characters (letters, digits, - and _). Keys created before this format have no prefix and keep working unchanged. + * Long-lived secret (also called client_secret). Use as the x-api-key header for every call to https://verification.didit.me/v3/... (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with sk_live_ (live application) or sk_test_ (sandbox application), followed by 43 URL-safe characters (letters, digits, - and _). Keys created before this format have no prefix and keep working unchanged. For 72 hours after you recovered your account, list, retrieve and update return an empty string here instead of the key. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -207,7 +207,7 @@ public Builder clientId(String clientId) { } /** - * Long-lived secret (also called client_secret). Use as the x-api-key header for every call to https://verification.didit.me/v3/... (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with sk_live_ (live application) or sk_test_ (sandbox application), followed by 43 URL-safe characters (letters, digits, - and _). Keys created before this format have no prefix and keep working unchanged. + * Long-lived secret (also called client_secret). Use as the x-api-key header for every call to https://verification.didit.me/v3/... (sessions, workflows, AML, etc.). Treat as a credential; never expose client-side. New keys start with sk_live_ (live application) or sk_test_ (sandbox application), followed by 43 URL-safe characters (letters, digits, - and _). Keys created before this format have no prefix and keep working unchanged. For 72 hours after you recovered your account, list, retrieve and update return an empty string here instead of the key. * * @param apiKey the value * @return this builder diff --git a/src/main/java/me/didit/types/BillingRetrieveBalanceResponse.java b/src/main/java/me/didit/types/BillingRetrieveBalanceResponse.java index 85da277..e1ca3c6 100644 --- a/src/main/java/me/didit/types/BillingRetrieveBalanceResponse.java +++ b/src/main/java/me/didit/types/BillingRetrieveBalanceResponse.java @@ -70,7 +70,7 @@ public boolean autoRefillEnabled() { } /** - * USD amount that will be charged on each auto-refill. null when auto-refill is disabled or unset. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -123,7 +123,7 @@ public Builder autoRefillEnabled(boolean autoRefillEnabled) { } /** - * USD amount that will be charged on each auto-refill. null when auto-refill is disabled or unset. + * 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. * * @param autoRefillAmount the value, or {@code null} to send JSON null * @return this builder diff --git a/src/main/java/me/didit/types/BillingTopUpParams.java b/src/main/java/me/didit/types/BillingTopUpParams.java index 12c6bce..0dc0390 100644 --- a/src/main/java/me/didit/types/BillingTopUpParams.java +++ b/src/main/java/me/didit/types/BillingTopUpParams.java @@ -50,7 +50,7 @@ public Builder toBuilder() { } /** - * Amount to charge, in USD. Must be at least 50. Tax is added automatically at checkout based on the customer's billing address. + * 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. * * @return the value * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type @@ -93,7 +93,7 @@ private Builder(BillingTopUpParams from) { } /** - * Amount to charge, in USD. Must be at least 50. Tax is added automatically at checkout based on the customer's billing address. + * 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. * * @param amountInDollars the value * @return this builder diff --git a/src/main/java/me/didit/types/EmailUndeliverableError.java b/src/main/java/me/didit/types/EmailUndeliverableError.java new file mode 100644 index 0000000..d0d149b --- /dev/null +++ b/src/main/java/me/didit/types/EmailUndeliverableError.java @@ -0,0 +1,261 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** + * Returned by POST /programmatic/register/ when the address cannot receive the verification code. The check runs before any account is created or changed, so nothing is left behind. + */ +public final class EmailUndeliverableError extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("detail", "code", "reason", "suggestion"); + + private EmailUndeliverableError(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code EmailUndeliverableError} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static EmailUndeliverableError fromJson(JsonNode json) { + return new EmailUndeliverableError(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Human-readable message. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String detail() { + return Json.required(json, "detail", Conv.STRING); + } + + /** + * Always email_undeliverable. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public EmailUndeliverableError.Code code() { + return Json.required(json, "code", Conv.enumeration(EmailUndeliverableError.Code::of)); + } + + /** + * Why the address was refused: invalid_address (not a valid address), reserved_domain (a reserved or test domain such as example.com), no_mail_records (the domain has no mail server), disposable_domain (a throwaway mailbox provider), possible_typo (a likely misspelling of a common provider; see suggestion), suppressed (the address previously bounced or complained). + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public EmailUndeliverableError.Reason reason() { + return Json.required(json, "reason", Conv.enumeration(EmailUndeliverableError.Reason::of)); + } + + /** + * The likely intended domain, present only when the domain looks like a typo (for example gmail.com for gmial.com). The API does not accept a confirmation: register again with the corrected address. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional suggestion() { + return Json.optional(json, "suggestion", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link EmailUndeliverableError}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(EmailUndeliverableError from) { + super(from); + } + + /** + * Human-readable message. + * + * @param detail the value + * @return this builder + */ + public Builder detail(String detail) { + return set("detail", Conv.STRING, detail); + } + + /** + * Always email_undeliverable. + * + * @param code the value + * @return this builder + */ + public Builder code(EmailUndeliverableError.Code code) { + return set("code", Conv.enumeration(EmailUndeliverableError.Code::of), code); + } + + /** + * Why the address was refused: invalid_address (not a valid address), reserved_domain (a reserved or test domain such as example.com), no_mail_records (the domain has no mail server), disposable_domain (a throwaway mailbox provider), possible_typo (a likely misspelling of a common provider; see suggestion), suppressed (the address previously bounced or complained). + * + * @param reason the value + * @return this builder + */ + public Builder reason(EmailUndeliverableError.Reason reason) { + return set("reason", Conv.enumeration(EmailUndeliverableError.Reason::of), reason); + } + + /** + * The likely intended domain, present only when the domain looks like a typo (for example gmail.com for gmial.com). The API does not accept a confirmation: register again with the corrected address. + * + * @param suggestion the value + * @return this builder + */ + public Builder suggestion(String suggestion) { + return set("suggestion", Conv.STRING, suggestion); + } + + /** + * Builds the value. + * + * @return a new immutable {@code EmailUndeliverableError} + * @throws IllegalStateException when a required field is missing + */ + public EmailUndeliverableError build() { + require("detail", false); + require("code", false); + require("reason", false); + return new EmailUndeliverableError(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * Always email_undeliverable. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Code extends StringEnum { + /** The value {@code "email_undeliverable"}. */ + public static final Code EMAIL_UNDELIVERABLE = new Code("email_undeliverable"); + + private static final List KNOWN_VALUES = List.of(EMAIL_UNDELIVERABLE); + + private Code(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Code of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Code::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * Why the address was refused: invalid_address (not a valid address), reserved_domain (a reserved or test domain such as example.com), no_mail_records (the domain has no mail server), disposable_domain (a throwaway mailbox provider), possible_typo (a likely misspelling of a common provider; see suggestion), suppressed (the address previously bounced or complained). + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Reason extends StringEnum { + /** The value {@code "invalid_address"}. */ + public static final Reason INVALID_ADDRESS = new Reason("invalid_address"); + + /** The value {@code "reserved_domain"}. */ + public static final Reason RESERVED_DOMAIN = new Reason("reserved_domain"); + + /** The value {@code "no_mail_records"}. */ + public static final Reason NO_MAIL_RECORDS = new Reason("no_mail_records"); + + /** The value {@code "disposable_domain"}. */ + public static final Reason DISPOSABLE_DOMAIN = new Reason("disposable_domain"); + + /** The value {@code "possible_typo"}. */ + public static final Reason POSSIBLE_TYPO = new Reason("possible_typo"); + + /** The value {@code "suppressed"}. */ + public static final Reason SUPPRESSED = new Reason("suppressed"); + + private static final List KNOWN_VALUES = List.of(INVALID_ADDRESS, RESERVED_DOMAIN, NO_MAIL_RECORDS, DISPOSABLE_DOMAIN, POSSIBLE_TYPO, SUPPRESSED); + + private Reason(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Reason of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Reason::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/FiledRegulatoryReport.java b/src/main/java/me/didit/types/FiledRegulatoryReport.java new file mode 100644 index 0000000..a9a19be --- /dev/null +++ b/src/main/java/me/didit/types/FiledRegulatoryReport.java @@ -0,0 +1,225 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** A finalized regulatory report that prevents the requested deletion. */ +public final class FiledRegulatoryReport extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("uuid", "report_number", "report_type", "case_uuid"); + + private FiledRegulatoryReport(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code FiledRegulatoryReport} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static FiledRegulatoryReport fromJson(JsonNode json) { + return new FiledRegulatoryReport(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Report UUID. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String uuid() { + return Json.required(json, "uuid", Conv.STRING); + } + + /** + * Report number shown in the Console (for example, SAR-0001). + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String reportNumber() { + return Json.required(json, "report_number", Conv.STRING); + } + + /** + * Regulatory report type. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public FiledRegulatoryReport.ReportType reportType() { + return Json.required(json, "report_type", Conv.enumeration(FiledRegulatoryReport.ReportType::of)); + } + + /** + * Case the report belongs to. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String caseUuid() { + return Json.required(json, "case_uuid", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link FiledRegulatoryReport}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(FiledRegulatoryReport from) { + super(from); + } + + /** + * Report UUID. + * + * @param uuid the value + * @return this builder + */ + public Builder uuid(String uuid) { + return set("uuid", Conv.STRING, uuid); + } + + /** + * Report number shown in the Console (for example, SAR-0001). + * + * @param reportNumber the value + * @return this builder + */ + public Builder reportNumber(String reportNumber) { + return set("report_number", Conv.STRING, reportNumber); + } + + /** + * Regulatory report type. + * + * @param reportType the value + * @return this builder + */ + public Builder reportType(FiledRegulatoryReport.ReportType reportType) { + return set("report_type", Conv.enumeration(FiledRegulatoryReport.ReportType::of), reportType); + } + + /** + * Case the report belongs to. + * + * @param caseUuid the value + * @return this builder + */ + public Builder caseUuid(String caseUuid) { + return set("case_uuid", Conv.STRING, caseUuid); + } + + /** + * Builds the value. + * + * @return a new immutable {@code FiledRegulatoryReport} + * @throws IllegalStateException when a required field is missing + */ + public FiledRegulatoryReport build() { + require("uuid", false); + require("report_number", false); + require("report_type", false); + require("case_uuid", false); + return new FiledRegulatoryReport(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * Regulatory report type. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class ReportType extends StringEnum { + /** The value {@code "SAR"}. */ + public static final ReportType SAR = new ReportType("SAR"); + + /** The value {@code "STR"}. */ + public static final ReportType STR = new ReportType("STR"); + + /** The value {@code "CTR"}. */ + public static final ReportType CTR = new ReportType("CTR"); + + /** The value {@code "TTR"}. */ + public static final ReportType TTR = new ReportType("TTR"); + + /** The value {@code "UTR"}. */ + public static final ReportType UTR = new ReportType("UTR"); + + /** The value {@code "IFT"}. */ + public static final ReportType IFT = new ReportType("IFT"); + + /** The value {@code "CBR"}. */ + public static final ReportType CBR = new ReportType("CBR"); + + /** The value {@code "TFR"}. */ + public static final ReportType TFR = new ReportType("TFR"); + + private static final List KNOWN_VALUES = List.of(SAR, STR, CTR, TTR, UTR, IFT, CBR, TFR); + + private ReportType(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static ReportType of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, ReportType::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/FiledReportTransaction.java b/src/main/java/me/didit/types/FiledReportTransaction.java new file mode 100644 index 0000000..9c2adac --- /dev/null +++ b/src/main/java/me/didit/types/FiledReportTransaction.java @@ -0,0 +1,142 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +/** A transaction that cannot be deleted because a finalized regulatory report lists it. */ +public final class FiledReportTransaction extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("uuid", "transaction_id", "reports"); + + private FiledReportTransaction(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code FiledReportTransaction} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static FiledReportTransaction fromJson(JsonNode json) { + return new FiledReportTransaction(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Didit transaction UUID. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String uuid() { + return Json.required(json, "uuid", Conv.STRING); + } + + /** + * The transaction identifier supplied when the transaction was created. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String transactionId() { + return Json.required(json, "transaction_id", Conv.STRING); + } + + /** + * Finalized reports that list this transaction. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public List reports() { + return Json.required(json, "reports", Conv.list(Conv.model(FiledRegulatoryReport::fromJson))); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link FiledReportTransaction}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(FiledReportTransaction from) { + super(from); + } + + /** + * Didit transaction UUID. + * + * @param uuid the value + * @return this builder + */ + public Builder uuid(String uuid) { + return set("uuid", Conv.STRING, uuid); + } + + /** + * The transaction identifier supplied when the transaction was created. + * + * @param transactionId the value + * @return this builder + */ + public Builder transactionId(String transactionId) { + return set("transaction_id", Conv.STRING, transactionId); + } + + /** + * Finalized reports that list this transaction. + * + * @param reports the value + * @return this builder + */ + public Builder reports(List reports) { + return set("reports", Conv.list(Conv.model(FiledRegulatoryReport::fromJson)), reports); + } + + /** + * Builds the value. + * + * @return a new immutable {@code FiledReportTransaction} + * @throws IllegalStateException when a required field is missing + */ + public FiledReportTransaction build() { + require("uuid", false); + require("transaction_id", false); + require("reports", false); + return new FiledReportTransaction(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/IdVerificationVerifyParams.java b/src/main/java/me/didit/types/IdVerificationVerifyParams.java index 701db06..918350d 100644 --- a/src/main/java/me/didit/types/IdVerificationVerifyParams.java +++ b/src/main/java/me/didit/types/IdVerificationVerifyParams.java @@ -91,7 +91,7 @@ public Optional backImagePassword() { } /** - * 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -274,7 +274,7 @@ public Builder backImagePassword(String backImagePassword) { } /** - * 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. + * 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. * * @param performDocumentLiveness the value * @return this builder diff --git a/src/main/java/me/didit/types/IdVerificationVerifyResponse.java b/src/main/java/me/didit/types/IdVerificationVerifyResponse.java index 8f955fa..faa84d3 100644 --- a/src/main/java/me/didit/types/IdVerificationVerifyResponse.java +++ b/src/main/java/me/didit/types/IdVerificationVerifyResponse.java @@ -217,7 +217,7 @@ public Builder toBuilder() { } /** - * Approved when no warning resolves to a decline; Declined otherwise — the warnings list explains why. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -537,7 +537,7 @@ public Optional parse } /** - * Empty on a clean approval. Every entry explains one detected risk; entries with log_type: "error" set status to Declined. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -570,7 +570,7 @@ private Builder(IdVerification from) { } /** - * Approved when no warning resolves to a decline; Declined otherwise — the warnings list explains why. + * 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. * * @param status the value * @return this builder @@ -890,7 +890,7 @@ public Builder parsedAddress(IdVerificationVerifyResponse.IdVerification.@Nullab } /** - * Empty on a clean approval. Every entry explains one detected risk; entries with log_type: "error" set status to Declined. + * 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. * * @param warnings the value * @return this builder @@ -925,7 +925,7 @@ protected Builder self() { } /** - * Approved when no warning resolves to a decline; Declined otherwise — the warnings list explains why. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -936,7 +936,10 @@ public static final class Status extends StringEnum { /** The value {@code "Declined"}. */ public static final Status DECLINED = new Status("Declined"); - private static final List KNOWN_VALUES = List.of(APPROVED, DECLINED); + /** The value {@code "In Review"}. */ + public static final Status IN_REVIEW = new Status("In Review"); + + private static final List KNOWN_VALUES = List.of(APPROVED, DECLINED, IN_REVIEW); private Status(String value) { super(value); @@ -2302,7 +2305,7 @@ public Builder toBuilder() { } /** - * 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -2332,7 +2335,7 @@ public Optional> additionalData() { } /** - * 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.) + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -2375,7 +2378,7 @@ private Builder(Warnings from) { } /** - * 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. + * 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. * * @param risk the value * @return this builder @@ -2405,7 +2408,7 @@ public Builder additionalData(@Nullable Map additionalData) { } /** - * 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.) + * 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. * * @param logType the value * @return this builder @@ -2490,7 +2493,7 @@ public boolean isKnown() { } /** - * 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.) + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ diff --git a/src/main/java/me/didit/types/InFiledReportError.java b/src/main/java/me/didit/types/InFiledReportError.java new file mode 100644 index 0000000..ac61b6f --- /dev/null +++ b/src/main/java/me/didit/types/InFiledReportError.java @@ -0,0 +1,205 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** + * The deletion is refused because the selected data is in a finalized regulatory report. Nothing is deleted. Delete the report, then repeat the request. + */ +public final class InFiledReportError extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("detail", "code", "reports", "transactions"); + + private InFiledReportError(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code InFiledReportError} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static InFiledReportError fromJson(JsonNode json) { + return new InFiledReportError(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The detail field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String detail() { + return Json.required(json, "detail", Conv.STRING); + } + + /** + * Stable error code for a deletion blocked by a finalized regulatory report. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public InFiledReportError.Code code() { + return Json.required(json, "code", Conv.enumeration(InFiledReportError.Code::of)); + } + + /** + * Reports that block a person, company, case or session deletion. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> reports() { + return Json.optional(json, "reports", Conv.list(Conv.model(FiledRegulatoryReport::fromJson))); + } + + /** + * Transactions that block a transaction deletion, each with the reports that list it. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> transactions() { + return Json.optional(json, "transactions", Conv.list(Conv.model(FiledReportTransaction::fromJson))); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link InFiledReportError}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(InFiledReportError from) { + super(from); + } + + /** + * The detail field. + * + * @param detail the value + * @return this builder + */ + public Builder detail(String detail) { + return set("detail", Conv.STRING, detail); + } + + /** + * Stable error code for a deletion blocked by a finalized regulatory report. + * + * @param code the value + * @return this builder + */ + public Builder code(InFiledReportError.Code code) { + return set("code", Conv.enumeration(InFiledReportError.Code::of), code); + } + + /** + * Reports that block a person, company, case or session deletion. + * + * @param reports the value + * @return this builder + */ + public Builder reports(List reports) { + return set("reports", Conv.list(Conv.model(FiledRegulatoryReport::fromJson)), reports); + } + + /** + * Transactions that block a transaction deletion, each with the reports that list it. + * + * @param transactions the value + * @return this builder + */ + public Builder transactions(List transactions) { + return set("transactions", Conv.list(Conv.model(FiledReportTransaction::fromJson)), transactions); + } + + /** + * Builds the value. + * + * @return a new immutable {@code InFiledReportError} + * @throws IllegalStateException when a required field is missing + */ + public InFiledReportError build() { + require("detail", false); + require("code", false); + return new InFiledReportError(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * Stable error code for a deletion blocked by a finalized regulatory report. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Code extends StringEnum { + /** The value {@code "in_filed_report"}. */ + public static final Code IN_FILED_REPORT = new Code("in_filed_report"); + + private static final List KNOWN_VALUES = List.of(IN_FILED_REPORT); + + private Code(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Code of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Code::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/KYBRegistryOwnership.java b/src/main/java/me/didit/types/KYBRegistryOwnership.java index 63d9a45..ed4e1cc 100644 --- a/src/main/java/me/didit/types/KYBRegistryOwnership.java +++ b/src/main/java/me/didit/types/KYBRegistryOwnership.java @@ -54,7 +54,7 @@ public Builder toBuilder() { } /** - * delivered when the registry published ownership; unavailable when it did not (see unavailable_reason); pending while the registry is still fetching it; not_requested for the Basic tier. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -127,7 +127,7 @@ private Builder(KYBRegistryOwnership from) { } /** - * delivered when the registry published ownership; unavailable when it did not (see unavailable_reason); pending while the registry is still fetching it; not_requested for the Basic tier. + * 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. * * @param status the value, or {@code null} to send JSON null * @return this builder @@ -209,7 +209,7 @@ protected Builder self() { } /** - * delivered when the registry published ownership; unavailable when it did not (see unavailable_reason); pending while the registry is still fetching it; not_requested for the Basic tier. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -223,10 +223,13 @@ public static final class Status extends StringEnum { /** The value {@code "delivered"}. */ public static final Status DELIVERED = new Status("delivered"); + /** The value {@code "not_held"}. */ + public static final Status NOT_HELD = new Status("not_held"); + /** The value {@code "unavailable"}. */ public static final Status UNAVAILABLE = new Status("unavailable"); - private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, PENDING, DELIVERED, UNAVAILABLE); + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, PENDING, DELIVERED, NOT_HELD, UNAVAILABLE); private Status(String value) { super(value); diff --git a/src/main/java/me/didit/types/KybListShareholdersResponse.java b/src/main/java/me/didit/types/KybListShareholdersResponse.java index a0494f0..811b1ab 100644 --- a/src/main/java/me/didit/types/KybListShareholdersResponse.java +++ b/src/main/java/me/didit/types/KybListShareholdersResponse.java @@ -810,7 +810,7 @@ public Optional r } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -1313,7 +1313,7 @@ public Builder registryTierDelivered(KybListShareholdersResponse.KybRegistry.@Nu } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * * @param registryOwnershipStatus the value, or {@code null} to send JSON null * @return this builder @@ -2820,7 +2820,7 @@ public boolean isKnown() { } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -2831,13 +2831,16 @@ public static final class RegistryOwnershipStatus extends StringEnum { /** The value {@code "delivered"}. */ public static final RegistryOwnershipStatus DELIVERED = new RegistryOwnershipStatus("delivered"); + /** The value {@code "not_held"}. */ + public static final RegistryOwnershipStatus NOT_HELD = new RegistryOwnershipStatus("not_held"); + /** The value {@code "unavailable"}. */ public static final RegistryOwnershipStatus UNAVAILABLE = new RegistryOwnershipStatus("unavailable"); /** The value {@code "pending"}. */ public static final RegistryOwnershipStatus PENDING = new RegistryOwnershipStatus("pending"); - private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, UNAVAILABLE, PENDING); + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, NOT_HELD, UNAVAILABLE, PENDING); private RegistryOwnershipStatus(String value) { super(value); diff --git a/src/main/java/me/didit/types/KybListUbosResponse.java b/src/main/java/me/didit/types/KybListUbosResponse.java index 6b743ab..93119b7 100644 --- a/src/main/java/me/didit/types/KybListUbosResponse.java +++ b/src/main/java/me/didit/types/KybListUbosResponse.java @@ -810,7 +810,7 @@ public Optional registryT } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -1313,7 +1313,7 @@ public Builder registryTierDelivered(KybListUbosResponse.KybRegistry.@Nullable R } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * * @param registryOwnershipStatus the value, or {@code null} to send JSON null * @return this builder @@ -2820,7 +2820,7 @@ public boolean isKnown() { } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (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. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -2831,13 +2831,16 @@ public static final class RegistryOwnershipStatus extends StringEnum { /** The value {@code "delivered"}. */ public static final RegistryOwnershipStatus DELIVERED = new RegistryOwnershipStatus("delivered"); + /** The value {@code "not_held"}. */ + public static final RegistryOwnershipStatus NOT_HELD = new RegistryOwnershipStatus("not_held"); + /** The value {@code "unavailable"}. */ public static final RegistryOwnershipStatus UNAVAILABLE = new RegistryOwnershipStatus("unavailable"); /** The value {@code "pending"}. */ public static final RegistryOwnershipStatus PENDING = new RegistryOwnershipStatus("pending"); - private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, UNAVAILABLE, PENDING); + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, NOT_HELD, UNAVAILABLE, PENDING); private RegistryOwnershipStatus(String value) { super(value); diff --git a/src/main/java/me/didit/types/KybSelectResponse.java b/src/main/java/me/didit/types/KybSelectResponse.java index 9abae2e..2760964 100644 --- a/src/main/java/me/didit/types/KybSelectResponse.java +++ b/src/main/java/me/didit/types/KybSelectResponse.java @@ -810,7 +810,7 @@ public Optional registryTie } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (see registry_data.ownership_error), not_requested on the Lite tier. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -1313,7 +1313,7 @@ public Builder registryTierDelivered(KybSelectResponse.KybRegistry.RegistryTierD } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (see registry_data.ownership_error), not_requested on the Lite tier. + * 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. * * @param registryOwnershipStatus the value, or {@code null} to send JSON null * @return this builder @@ -2820,7 +2820,7 @@ public boolean isKnown() { } /** - * delivered when the ownership data came back, unavailable when an ownership tier was requested but the registry had none for this company (see registry_data.ownership_error), not_requested on the Lite tier. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -2831,10 +2831,13 @@ public static final class RegistryOwnershipStatus extends StringEnum { /** The value {@code "delivered"}. */ public static final RegistryOwnershipStatus DELIVERED = new RegistryOwnershipStatus("delivered"); + /** The value {@code "not_held"}. */ + public static final RegistryOwnershipStatus NOT_HELD = new RegistryOwnershipStatus("not_held"); + /** The value {@code "unavailable"}. */ public static final RegistryOwnershipStatus UNAVAILABLE = new RegistryOwnershipStatus("unavailable"); - private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, UNAVAILABLE); + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, DELIVERED, NOT_HELD, UNAVAILABLE); private RegistryOwnershipStatus(String value) { super(value); diff --git a/src/main/java/me/didit/types/NetworkDetailResponse.java b/src/main/java/me/didit/types/NetworkDetailResponse.java index fac1562..a45fbbb 100644 --- a/src/main/java/me/didit/types/NetworkDetailResponse.java +++ b/src/main/java/me/didit/types/NetworkDetailResponse.java @@ -255,7 +255,7 @@ public Optional sharingMode() { } /** - * Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the global sharing mode. + * Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the global sharing mode. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -265,7 +265,7 @@ public Optional crossOrgAvailable() { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -518,7 +518,7 @@ public Builder sharingMode(NetworkDetailResponse.SharingMode sharingMode) { } /** - * Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the global sharing mode. + * Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the global sharing mode. * * @param crossOrgAvailable the value * @return this builder @@ -528,7 +528,7 @@ public Builder crossOrgAvailable(boolean crossOrgAvailable) { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * * @param crossOrgUnavailableReason the value, or {@code null} to send JSON null * @return this builder @@ -629,7 +629,7 @@ public boolean isKnown() { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ diff --git a/src/main/java/me/didit/types/NetworksListResponse.java b/src/main/java/me/didit/types/NetworksListResponse.java index f2ebb86..147c36f 100644 --- a/src/main/java/me/didit/types/NetworksListResponse.java +++ b/src/main/java/me/didit/types/NetworksListResponse.java @@ -121,7 +121,7 @@ public NetworksListResponse.SharingMode sharingMode() { } /** - * Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the global sharing mode. + * Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the global sharing mode. * * @return the value * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type @@ -131,7 +131,7 @@ public boolean crossOrgAvailable() { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -224,7 +224,7 @@ public Builder sharingMode(NetworksListResponse.SharingMode sharingMode) { } /** - * Whether cross-organization signals apply to this organization. They need both the Identity Enterprise plan and the global sharing mode. + * Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the global sharing mode. * * @param crossOrgAvailable the value * @return this builder @@ -234,7 +234,7 @@ public Builder crossOrgAvailable(boolean crossOrgAvailable) { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * * @param crossOrgUnavailableReason the value, or {@code null} to send JSON null * @return this builder @@ -481,7 +481,7 @@ public boolean isKnown() { } /** - * Why cross-organization signals do not apply, checked in this order. plan: the organization's Identity plan does not include the cross-organization fraud network (it needs Identity Enterprise), 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. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ diff --git a/src/main/java/me/didit/types/RecoveryHoldError.java b/src/main/java/me/didit/types/RecoveryHoldError.java new file mode 100644 index 0000000..6361b82 --- /dev/null +++ b/src/main/java/me/didit/types/RecoveryHoldError.java @@ -0,0 +1,185 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** + * Returned for 72 hours after the account was recovered (lost sign-in methods were reset). Actions that create organizations, applications or secrets are refused until until; read operations keep working. + */ +public final class RecoveryHoldError extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("detail", "code", "until"); + + private RecoveryHoldError(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code RecoveryHoldError} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static RecoveryHoldError fromJson(JsonNode json) { + return new RecoveryHoldError(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The detail field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String detail() { + return Json.required(json, "detail", Conv.STRING); + } + + /** + * The code field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public RecoveryHoldError.Code code() { + return Json.required(json, "code", Conv.enumeration(RecoveryHoldError.Code::of)); + } + + /** + * When the hold ends (ISO 8601). Retry the request after this time. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public OffsetDateTime until() { + return Json.required(json, "until", Conv.DATE_TIME); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link RecoveryHoldError}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(RecoveryHoldError from) { + super(from); + } + + /** + * The detail field. + * + * @param detail the value + * @return this builder + */ + public Builder detail(String detail) { + return set("detail", Conv.STRING, detail); + } + + /** + * The code field. + * + * @param code the value + * @return this builder + */ + public Builder code(RecoveryHoldError.Code code) { + return set("code", Conv.enumeration(RecoveryHoldError.Code::of), code); + } + + /** + * When the hold ends (ISO 8601). Retry the request after this time. + * + * @param until the value + * @return this builder + */ + public Builder until(OffsetDateTime until) { + return set("until", Conv.DATE_TIME, until); + } + + /** + * Builds the value. + * + * @return a new immutable {@code RecoveryHoldError} + * @throws IllegalStateException when a required field is missing + */ + public RecoveryHoldError build() { + require("detail", false); + require("code", false); + require("until", false); + return new RecoveryHoldError(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Code extends StringEnum { + /** The value {@code "recovery_hold"}. */ + public static final Code RECOVERY_HOLD = new Code("recovery_hold"); + + private static final List KNOWN_VALUES = List.of(RECOVERY_HOLD); + + private Code(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Code of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Code::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/RecoverySetupRequiredError.java b/src/main/java/me/didit/types/RecoverySetupRequiredError.java new file mode 100644 index 0000000..9cc381d --- /dev/null +++ b/src/main/java/me/didit/types/RecoverySetupRequiredError.java @@ -0,0 +1,205 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** + * The account was recovered and has no sign-in method set up yet. No tokens are issued; a one-time link to set up a new method was emailed to the account's address. Open it, set up the method, then log in again. + */ +public final class RecoverySetupRequiredError extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("recovery_setup_required", "setup_link_sent", "message", "code"); + + private RecoverySetupRequiredError(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code RecoverySetupRequiredError} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static RecoverySetupRequiredError fromJson(JsonNode json) { + return new RecoverySetupRequiredError(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The recovery_setup_required field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public boolean recoverySetupRequired() { + return Json.required(json, "recovery_setup_required", Conv.BOOLEAN); + } + + /** + * The setup_link_sent field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public boolean setupLinkSent() { + return Json.required(json, "setup_link_sent", Conv.BOOLEAN); + } + + /** + * The message field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String message() { + return Json.required(json, "message", Conv.STRING); + } + + /** + * The code field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public RecoverySetupRequiredError.Code code() { + return Json.required(json, "code", Conv.enumeration(RecoverySetupRequiredError.Code::of)); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link RecoverySetupRequiredError}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(RecoverySetupRequiredError from) { + super(from); + } + + /** + * The recovery_setup_required field. + * + * @param recoverySetupRequired the value + * @return this builder + */ + public Builder recoverySetupRequired(boolean recoverySetupRequired) { + return set("recovery_setup_required", Conv.BOOLEAN, recoverySetupRequired); + } + + /** + * The setup_link_sent field. + * + * @param setupLinkSent the value + * @return this builder + */ + public Builder setupLinkSent(boolean setupLinkSent) { + return set("setup_link_sent", Conv.BOOLEAN, setupLinkSent); + } + + /** + * The message field. + * + * @param message the value + * @return this builder + */ + public Builder message(String message) { + return set("message", Conv.STRING, message); + } + + /** + * The code field. + * + * @param code the value + * @return this builder + */ + public Builder code(RecoverySetupRequiredError.Code code) { + return set("code", Conv.enumeration(RecoverySetupRequiredError.Code::of), code); + } + + /** + * Builds the value. + * + * @return a new immutable {@code RecoverySetupRequiredError} + * @throws IllegalStateException when a required field is missing + */ + public RecoverySetupRequiredError build() { + require("recovery_setup_required", false); + require("setup_link_sent", false); + require("message", false); + require("code", false); + return new RecoverySetupRequiredError(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Code extends StringEnum { + /** The value {@code "recovery_setup_required"}. */ + public static final Code RECOVERY_SETUP_REQUIRED = new Code("recovery_setup_required"); + + private static final List KNOWN_VALUES = List.of(RECOVERY_SETUP_REQUIRED); + + private Code(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Code of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Code::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/RegulatoryReportsDeleteParams.java b/src/main/java/me/didit/types/RegulatoryReportsDeleteParams.java new file mode 100644 index 0000000..a58baad --- /dev/null +++ b/src/main/java/me/didit/types/RegulatoryReportsDeleteParams.java @@ -0,0 +1,99 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +/** Parameters for {@code regulatory_reports.delete}. */ +public final class RegulatoryReportsDeleteParams extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("confirm_delete"); + + private RegulatoryReportsDeleteParams(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code RegulatoryReportsDeleteParams} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static RegulatoryReportsDeleteParams fromJson(JsonNode json) { + return new RegulatoryReportsDeleteParams(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Set to true to confirm that a finalized report and its generated files are deleted permanently. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional confirmDelete() { + return Json.optional(json, "confirm_delete", Conv.BOOLEAN); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link RegulatoryReportsDeleteParams}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(RegulatoryReportsDeleteParams from) { + super(from); + } + + /** + * Set to true to confirm that a finalized report and its generated files are deleted permanently. + * + * @param confirmDelete the value + * @return this builder + */ + public Builder confirmDelete(boolean confirmDelete) { + return set("confirm_delete", Conv.BOOLEAN, confirmDelete); + } + + /** + * Builds the value. + * + * @return a new immutable {@code RegulatoryReportsDeleteParams} + */ + public RegulatoryReportsDeleteParams build() { + return new RegulatoryReportsDeleteParams(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/SessionDecision.java b/src/main/java/me/didit/types/SessionDecision.java index 583cab4..153e914 100644 --- a/src/main/java/me/didit/types/SessionDecision.java +++ b/src/main/java/me/didit/types/SessionDecision.java @@ -17,7 +17,7 @@ import org.jspecify.annotations.Nullable; public final class SessionDecision extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("session_id", "session_kind", "shared_from_session", "session_number", "session_url", "status", "workflow_id", "features", "vendor_data", "metadata", "expected_details", "contact_details", "callback", "id_verifications", "nfc_skip_reason", "nfc_verifications", "liveness_checks", "face_matches", "phone_verifications", "email_verifications", "poa_verifications", "document_ai_documents", "questionnaire_responses", "bank_verifications", "signatures", "aml_screenings", "ip_analyses", "database_validations", "reviews", "created_at", "expires_at", "registry_checks", "key_people_checks", "document_verifications", "environment", "resubmissions", "investigator"); + private static final Set KNOWN_FIELDS = Set.of("session_id", "session_kind", "shared_from_session", "session_number", "session_url", "status", "workflow_id", "features", "vendor_data", "metadata", "expected_details", "contact_details", "callback", "id_verifications", "nfc_skip_reason", "nfc_verifications", "liveness_checks", "face_matches", "phone_verifications", "email_verifications", "poa_verifications", "document_ai_documents", "questionnaire_responses", "bank_verifications", "signatures", "aml_screenings", "ip_analyses", "database_validations", "reviews", "created_at", "expires_at", "registry_checks", "key_people_checks", "document_verifications", "environment", "resubmissions", "biometric_consent", "investigator"); private SessionDecision(ObjectNode json, Map> files) { super(json, files); @@ -448,6 +448,16 @@ public Optional> resubmissions() { return Json.optional(json, "resubmissions", Conv.list(Conv.model(SessionDecision.Resubmissions::fromJson))); } + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional biometricConsent() { + return Json.optional(json, "biometric_consent", Conv.model(SessionDecision.BiometricConsent::fromJson)); + } + /** * 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). * @@ -867,6 +877,16 @@ public Builder resubmissions(List resubmissions) return set("resubmissions", Conv.list(Conv.model(SessionDecision.Resubmissions::fromJson)), resubmissions); } + /** + * 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). + * + * @param biometricConsent the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder biometricConsent(SessionDecision.@Nullable BiometricConsent biometricConsent) { + return setNullable("biometric_consent", Conv.model(SessionDecision.BiometricConsent::fromJson), biometricConsent); + } + /** * 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). * @@ -1622,7 +1642,7 @@ protected Builder self() { * 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. */ public static final class IdVerifications extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("node_id", "verification_method", "assurance", "wallet_provider", "fallback_from", "id_lookup", "wallet_verification", "status", "document_type", "document_subtype", "document_number", "personal_number", "portrait_image", "front_image", "front_video", "back_image", "back_video", "full_front_image", "full_back_image", "front_image_camera_front", "back_image_camera_front", "date_of_birth", "age", "expiration_date", "date_of_issue", "issuing_state", "issuing_state_name", "first_name", "last_name", "full_name", "gender", "address", "formatted_address", "place_of_birth", "marital_status", "nationality", "extra_fields", "mrz", "parsed_address", "extra_files", "front_image_camera_front_face_match_score", "back_image_camera_front_face_match_score", "document_selfie_face_matches", "front_image_quality_score", "back_image_quality_score", "warnings", "matches", "document_liveness"); + private static final Set KNOWN_FIELDS = Set.of("node_id", "verification_method", "assurance", "wallet_provider", "fallback_from", "id_lookup", "wallet_verification", "status", "document_type", "document_subtype", "document_number", "personal_number", "portrait_image", "front_image", "front_video", "back_image", "back_video", "full_front_image", "full_back_image", "front_image_camera_front", "back_image_camera_front", "date_of_birth", "age", "expiration_date", "date_of_issue", "issuing_state", "issuing_state_name", "first_name", "last_name", "full_name", "gender", "address", "formatted_address", "place_of_birth", "marital_status", "nationality", "extra_fields", "mrz", "parsed_address", "extra_files", "front_image_camera_front_face_match_score", "back_image_camera_front_face_match_score", "document_selfie_face_matches", "front_image_quality_score", "back_image_quality_score", "warnings", "matches", "document_liveness", "address_validation"); private IdVerifications(ObjectNode json, Map> files) { super(json, files); @@ -2137,6 +2157,16 @@ public Optional documentLivene return Json.optional(json, "document_liveness", Conv.model(SessionDecision.IdVerifications.DocumentLiveness::fromJson)); } + /** + * Address Validation of the address printed on this document: whether it exists and its normalised form, as the document read it (a later correction of address does not run it again). Present when this ID verification step enabled the Address Validation add-on; null otherwise. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressValidation() { + return Json.optional(json, "address_validation", Conv.model(AddressValidationResult::fromJson)); + } + @Override protected Set knownFields() { return KNOWN_FIELDS; @@ -2630,6 +2660,16 @@ public Builder documentLiveness(SessionDecision.IdVerifications.@Nullable Docume return setNullable("document_liveness", Conv.model(SessionDecision.IdVerifications.DocumentLiveness::fromJson), documentLiveness); } + /** + * Address Validation of the address printed on this document: whether it exists and its normalised form, as the document read it (a later correction of address does not run it again). Present when this ID verification step enabled the Address Validation add-on; null otherwise. + * + * @param addressValidation the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder addressValidation(@Nullable AddressValidationResult addressValidation) { + return setNullable("address_validation", Conv.model(AddressValidationResult::fromJson), addressValidation); + } + /** * Builds the value. * @@ -15487,7 +15527,7 @@ public boolean isKnown() { } public static final class PoaVerifications extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("node_id", "status", "document_file", "issuing_state", "document_type", "document_subtype", "document_language", "document_metadata", "issuer", "issue_date", "expiration_date", "poa_address", "poa_formatted_address", "poa_parsed_address", "name_on_document", "name_match_score_expected_details", "name_match_score_id_verification", "expected_details_address", "expected_details_formatted_address", "expected_details_parsed_address", "extra_fields", "extra_files", "warnings", "detected_codes"); + private static final Set KNOWN_FIELDS = Set.of("node_id", "status", "document_file", "issuing_state", "document_type", "document_subtype", "document_language", "document_metadata", "issuer", "issue_date", "expiration_date", "poa_address", "poa_formatted_address", "poa_parsed_address", "name_on_document", "name_match_score_expected_details", "name_match_score_id_verification", "expected_details_address", "expected_details_formatted_address", "expected_details_parsed_address", "extra_fields", "extra_files", "warnings", "detected_codes", "address_validation"); private PoaVerifications(ObjectNode json, Map> files) { super(json, files); @@ -15762,6 +15802,16 @@ public Optional> detectedCodes() { return Json.optional(json, "detected_codes", Conv.list(Conv.model(DocumentAIDetectedCode::fromJson))); } + /** + * Address Validation of the address on this proof of address: whether it exists and its normalised form, as the document read it (a later correction of poa_address does not run it again). Present when this Proof of Address step enabled the Address Validation add-on and the upload passed the document checks; null otherwise. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional addressValidation() { + return Json.optional(json, "address_validation", Conv.model(AddressValidationResult::fromJson)); + } + @Override protected Set knownFields() { return KNOWN_FIELDS; @@ -16015,6 +16065,16 @@ public Builder detectedCodes(List detectedCodes) { return set("detected_codes", Conv.list(Conv.model(DocumentAIDetectedCode::fromJson)), detectedCodes); } + /** + * Address Validation of the address on this proof of address: whether it exists and its normalised form, as the document read it (a later correction of poa_address does not run it again). Present when this Proof of Address step enabled the Address Validation add-on and the upload passed the document checks; null otherwise. + * + * @param addressValidation the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder addressValidation(@Nullable AddressValidationResult addressValidation) { + return setNullable("address_validation", Conv.model(AddressValidationResult::fromJson), addressValidation); + } + /** * Builds the value. * @@ -24258,7 +24318,7 @@ public Optional registryTierDelivered() { } /** - * not_requested, pending, delivered or unavailable. Also in ownership.status. + * not_requested, pending, delivered, not_held or unavailable. Also in ownership.status. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -24761,7 +24821,7 @@ public Builder registryTierDelivered(@Nullable String registryTierDelivered) { } /** - * not_requested, pending, delivered or unavailable. Also in ownership.status. + * not_requested, pending, delivered, not_held or unavailable. Also in ownership.status. * * @param registryOwnershipStatus the value, or {@code null} to send JSON null * @return this builder @@ -32650,4 +32710,799 @@ protected Builder self() { } } } + + /** + * 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). + */ + public static final class BiometricConsent extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("required", "trigger", "status", "given_at", "declined_at", "text_version", "text_hash", "locale", "policy_url", "step", "node_id", "ip_address", "ip_country_code", "ip_state", "ip_city", "user_agent", "detected_ip_address", "detected_country_code", "detected_state", "detected_city", "declared_state", "document_state", "jurisdictions", "retention_months", "reason", "decline_reason", "face_data_deleted_at"); + + private BiometricConsent(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code BiometricConsent} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static BiometricConsent fromJson(JsonNode json) { + return new BiometricConsent(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Whether the session must collect explicit biometric consent. Never goes back to false. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional required() { + return Json.optional(json, "required", Conv.BOOLEAN); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional trigger() { + return Json.optional(json, "trigger", Conv.enumeration(SessionDecision.BiometricConsent.Trigger::of)); + } + + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(SessionDecision.BiometricConsent.Status::of)); + } + + /** + * When consent was given. For customer_collected, the given_at you sent. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional givenAt() { + return Json.optional(json, "given_at", Conv.DATE_TIME); + } + + /** + * When a decline was recorded. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional declinedAt() { + return Json.optional(json, "declined_at", Conv.DATE_TIME); + } + + /** + * Version id of the consent text the person answered, or the one you sent. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional textVersion() { + return Json.optional(json, "text_version", Conv.STRING); + } + + /** + * SHA-256 (hex) of the exact consent text shown, in the language it was shown in. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional textHash() { + return Json.optional(json, "text_hash", Conv.STRING); + } + + /** + * Language the consent text was shown in. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional locale() { + return Json.optional(json, "locale", Conv.STRING); + } + + /** + * The biometric data policy the consent step linked to. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional policyUrl() { + return Json.optional(json, "policy_url", Conv.STRING); + } + + /** + * Workflow step the session was on when the person answered, for example LIVENESS. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional step() { + return Json.optional(json, "step", Conv.STRING); + } + + /** + * Workflow graph node the session was on when the person answered. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional nodeId() { + return Json.optional(json, "node_id", Conv.STRING); + } + + /** + * IP the answer came from. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional ipAddress() { + return Json.optional(json, "ip_address", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 country of the answer's IP. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional ipCountryCode() { + return Json.optional(json, "ip_country_code", Conv.STRING); + } + + /** + * ISO 3166-2 subdivision of the answer's IP, for example US-IL. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional ipState() { + return Json.optional(json, "ip_state", Conv.STRING); + } + + /** + * City of the answer's IP. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional ipCity() { + return Json.optional(json, "ip_city", Conv.STRING); + } + + /** + * User agent of the answer's request. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional userAgent() { + return Json.optional(json, "user_agent", Conv.STRING); + } + + /** + * The person's IP the requirement was last evaluated from. Can differ from ip_address when the person answered on another device. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detectedIpAddress() { + return Json.optional(json, "detected_ip_address", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 country of the detected IP. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detectedCountryCode() { + return Json.optional(json, "detected_country_code", Conv.STRING); + } + + /** + * ISO 3166-2 subdivision of the detected IP. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detectedState() { + return Json.optional(json, "detected_state", Conv.STRING); + } + + /** + * City of the detected IP. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detectedCity() { + return Json.optional(json, "detected_city", Conv.STRING); + } + + /** + * ISO 3166-2 subdivision you declared: user_state, or the state of a US address in expected_details. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional declaredState() { + return Json.optional(json, "declared_state", Conv.STRING); + } + + /** + * ISO 3166-2 subdivision that issued the identity document, read at ID capture. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional documentState() { + return Json.optional(json, "document_state", Conv.STRING); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> jurisdictions() { + return Json.optional(json, "jurisdictions", Conv.list(Conv.STRING)); + } + + /** + * The shortest biometric retention limit, in months, among jurisdictions. null when none applies. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional retentionMonths() { + return Json.optional(json, "retention_months", Conv.LONG); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional reason() { + return Json.optional(json, "reason", Conv.enumeration(SessionDecision.BiometricConsent.Reason::of)); + } + + /** + * BIOMETRIC_CONSENT_DECLINED when a recorded decline ended the session, otherwise null. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional declineReason() { + return Json.optional(json, "decline_reason", Conv.enumeration(SessionDecision.BiometricConsent.DeclineReason::of)); + } + + /** + * When the decline deleted the session's face images, videos and face templates. null unless a decline was recorded. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional faceDataDeletedAt() { + return Json.optional(json, "face_data_deleted_at", Conv.DATE_TIME); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link BiometricConsent}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(BiometricConsent from) { + super(from); + } + + /** + * Whether the session must collect explicit biometric consent. Never goes back to false. + * + * @param required the value + * @return this builder + */ + public Builder required(boolean required) { + return set("required", Conv.BOOLEAN, required); + } + + /** + * 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. + * + * @param trigger the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder trigger(SessionDecision.BiometricConsent.@Nullable Trigger trigger) { + return setNullable("trigger", Conv.enumeration(SessionDecision.BiometricConsent.Trigger::of), trigger); + } + + /** + * 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). + * + * @param status the value + * @return this builder + */ + public Builder status(SessionDecision.BiometricConsent.Status status) { + return set("status", Conv.enumeration(SessionDecision.BiometricConsent.Status::of), status); + } + + /** + * When consent was given. For customer_collected, the given_at you sent. + * + * @param givenAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder givenAt(@Nullable OffsetDateTime givenAt) { + return setNullable("given_at", Conv.DATE_TIME, givenAt); + } + + /** + * When a decline was recorded. + * + * @param declinedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder declinedAt(@Nullable OffsetDateTime declinedAt) { + return setNullable("declined_at", Conv.DATE_TIME, declinedAt); + } + + /** + * Version id of the consent text the person answered, or the one you sent. + * + * @param textVersion the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder textVersion(@Nullable String textVersion) { + return setNullable("text_version", Conv.STRING, textVersion); + } + + /** + * SHA-256 (hex) of the exact consent text shown, in the language it was shown in. + * + * @param textHash the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder textHash(@Nullable String textHash) { + return setNullable("text_hash", Conv.STRING, textHash); + } + + /** + * Language the consent text was shown in. + * + * @param locale the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder locale(@Nullable String locale) { + return setNullable("locale", Conv.STRING, locale); + } + + /** + * The biometric data policy the consent step linked to. + * + * @param policyUrl the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder policyUrl(@Nullable String policyUrl) { + return setNullable("policy_url", Conv.STRING, policyUrl); + } + + /** + * Workflow step the session was on when the person answered, for example LIVENESS. + * + * @param step the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder step(@Nullable String step) { + return setNullable("step", Conv.STRING, step); + } + + /** + * Workflow graph node the session was on when the person answered. + * + * @param nodeId the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder nodeId(@Nullable String nodeId) { + return setNullable("node_id", Conv.STRING, nodeId); + } + + /** + * IP the answer came from. + * + * @param ipAddress the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder ipAddress(@Nullable String ipAddress) { + return setNullable("ip_address", Conv.STRING, ipAddress); + } + + /** + * ISO 3166-1 alpha-2 country of the answer's IP. + * + * @param ipCountryCode the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder ipCountryCode(@Nullable String ipCountryCode) { + return setNullable("ip_country_code", Conv.STRING, ipCountryCode); + } + + /** + * ISO 3166-2 subdivision of the answer's IP, for example US-IL. + * + * @param ipState the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder ipState(@Nullable String ipState) { + return setNullable("ip_state", Conv.STRING, ipState); + } + + /** + * City of the answer's IP. + * + * @param ipCity the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder ipCity(@Nullable String ipCity) { + return setNullable("ip_city", Conv.STRING, ipCity); + } + + /** + * User agent of the answer's request. + * + * @param userAgent the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder userAgent(@Nullable String userAgent) { + return setNullable("user_agent", Conv.STRING, userAgent); + } + + /** + * The person's IP the requirement was last evaluated from. Can differ from ip_address when the person answered on another device. + * + * @param detectedIpAddress the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder detectedIpAddress(@Nullable String detectedIpAddress) { + return setNullable("detected_ip_address", Conv.STRING, detectedIpAddress); + } + + /** + * ISO 3166-1 alpha-2 country of the detected IP. + * + * @param detectedCountryCode the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder detectedCountryCode(@Nullable String detectedCountryCode) { + return setNullable("detected_country_code", Conv.STRING, detectedCountryCode); + } + + /** + * ISO 3166-2 subdivision of the detected IP. + * + * @param detectedState the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder detectedState(@Nullable String detectedState) { + return setNullable("detected_state", Conv.STRING, detectedState); + } + + /** + * City of the detected IP. + * + * @param detectedCity the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder detectedCity(@Nullable String detectedCity) { + return setNullable("detected_city", Conv.STRING, detectedCity); + } + + /** + * ISO 3166-2 subdivision you declared: user_state, or the state of a US address in expected_details. + * + * @param declaredState the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder declaredState(@Nullable String declaredState) { + return setNullable("declared_state", Conv.STRING, declaredState); + } + + /** + * ISO 3166-2 subdivision that issued the identity document, read at ID capture. + * + * @param documentState the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder documentState(@Nullable String documentState) { + return setNullable("document_state", Conv.STRING, documentState); + } + + /** + * 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. + * + * @param jurisdictions the value + * @return this builder + */ + public Builder jurisdictions(List jurisdictions) { + return set("jurisdictions", Conv.list(Conv.STRING), jurisdictions); + } + + /** + * The shortest biometric retention limit, in months, among jurisdictions. null when none applies. + * + * @param retentionMonths the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder retentionMonths(@Nullable Long retentionMonths) { + return setNullable("retention_months", Conv.LONG, retentionMonths); + } + + /** + * 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. + * + * @param reason the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder reason(SessionDecision.BiometricConsent.@Nullable Reason reason) { + return setNullable("reason", Conv.enumeration(SessionDecision.BiometricConsent.Reason::of), reason); + } + + /** + * BIOMETRIC_CONSENT_DECLINED when a recorded decline ended the session, otherwise null. + * + * @param declineReason the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder declineReason(SessionDecision.BiometricConsent.@Nullable DeclineReason declineReason) { + return setNullable("decline_reason", Conv.enumeration(SessionDecision.BiometricConsent.DeclineReason::of), declineReason); + } + + /** + * When the decline deleted the session's face images, videos and face templates. null unless a decline was recorded. + * + * @param faceDataDeletedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder faceDataDeletedAt(@Nullable OffsetDateTime faceDataDeletedAt) { + return setNullable("face_data_deleted_at", Conv.DATE_TIME, faceDataDeletedAt); + } + + /** + * Builds the value. + * + * @return a new immutable {@code BiometricConsent} + */ + public BiometricConsent build() { + return new BiometricConsent(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Trigger extends StringEnum { + /** The value {@code "declared_state"}. */ + public static final Trigger DECLARED_STATE = new Trigger("declared_state"); + + /** The value {@code "ip_state"}. */ + public static final Trigger IP_STATE = new Trigger("ip_state"); + + /** The value {@code "ip_unknown"}. */ + public static final Trigger IP_UNKNOWN = new Trigger("ip_unknown"); + + /** The value {@code "document_issuer"}. */ + public static final Trigger DOCUMENT_ISSUER = new Trigger("document_issuer"); + + /** The value {@code "forced"}. */ + public static final Trigger FORCED = new Trigger("forced"); + + /** The value {@code "customer_collected"}. */ + public static final Trigger CUSTOMER_COLLECTED = new Trigger("customer_collected"); + + private static final List KNOWN_VALUES = List.of(DECLARED_STATE, IP_STATE, IP_UNKNOWN, DOCUMENT_ISSUER, FORCED, CUSTOMER_COLLECTED); + + private Trigger(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Trigger of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Trigger::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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). + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "not_required"}. */ + public static final Status NOT_REQUIRED = new Status("not_required"); + + /** The value {@code "pending"}. */ + public static final Status PENDING = new Status("pending"); + + /** The value {@code "given"}. */ + public static final Status GIVEN = new Status("given"); + + /** The value {@code "declined"}. */ + public static final Status DECLINED = new Status("declined"); + + private static final List KNOWN_VALUES = List.of(NOT_REQUIRED, PENDING, GIVEN, DECLINED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Reason extends StringEnum { + /** The value {@code "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"}. */ + public static final Reason BIOMETRIC_CONSENT_UNSUPPORTED_SDK = new Reason("BIOMETRIC_CONSENT_UNSUPPORTED_SDK"); + + /** The value {@code "BIOMETRIC_CONSENT_DECLINED"}. */ + public static final Reason BIOMETRIC_CONSENT_DECLINED = new Reason("BIOMETRIC_CONSENT_DECLINED"); + + private static final List KNOWN_VALUES = List.of(BIOMETRIC_CONSENT_UNSUPPORTED_SDK, BIOMETRIC_CONSENT_DECLINED); + + private Reason(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Reason of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Reason::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * BIOMETRIC_CONSENT_DECLINED when a recorded decline ended the session, otherwise null. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class DeclineReason extends StringEnum { + /** The value {@code "BIOMETRIC_CONSENT_DECLINED"}. */ + public static final DeclineReason BIOMETRIC_CONSENT_DECLINED = new DeclineReason("BIOMETRIC_CONSENT_DECLINED"); + + private static final List KNOWN_VALUES = List.of(BIOMETRIC_CONSENT_DECLINED); + + private DeclineReason(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static DeclineReason of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, DeclineReason::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + } } diff --git a/src/main/java/me/didit/types/SessionsCreateParams.java b/src/main/java/me/didit/types/SessionsCreateParams.java index bddf661..cf639d0 100644 --- a/src/main/java/me/didit/types/SessionsCreateParams.java +++ b/src/main/java/me/didit/types/SessionsCreateParams.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.time.LocalDate; +import java.time.OffsetDateTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -18,7 +19,7 @@ /** Parameters for {@code sessions.create}. */ public final class SessionsCreateParams extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("workflow_id", "vendor_data", "callback", "callback_method", "metadata", "language", "contact_details", "expected_details", "portrait_image", "sandbox_scenario", "sign"); + private static final Set KNOWN_FIELDS = Set.of("workflow_id", "vendor_data", "callback", "callback_method", "metadata", "tags", "language", "contact_details", "expected_details", "portrait_image", "sandbox_scenario", "sign", "user_state", "biometric_consent"); private SessionsCreateParams(ObjectNode json, Map> files) { super(json, files); @@ -103,6 +104,16 @@ public Optional metadata() { return Json.optional(json, "metadata", Conv.ANY); } + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> tags() { + return Json.optional(json, "tags", Conv.list(Conv.STRING)); + } + /** * 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). * @@ -163,6 +174,26 @@ public Optional sign() { return Json.optional(json, "sign", Conv.model(SessionsCreateParams.Sign::fromJson)); } + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional userState() { + return Json.optional(json, "user_state", Conv.STRING); + } + + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional biometricConsent() { + return Json.optional(json, "biometric_consent", Conv.model(SessionsCreateParams.BiometricConsent::fromJson)); + } + @Override protected Set knownFields() { return KNOWN_FIELDS; @@ -226,6 +257,16 @@ public Builder metadata(@Nullable Object metadata) { return setAny("metadata", metadata); } + /** + * 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. + * + * @param tags the value + * @return this builder + */ + public Builder tags(List tags) { + return set("tags", Conv.list(Conv.STRING), tags); + } + /** * 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). * @@ -286,6 +327,26 @@ public Builder sign(SessionsCreateParams.Sign sign) { return set("sign", Conv.model(SessionsCreateParams.Sign::fromJson), sign); } + /** + * 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). + * + * @param userState the value + * @return this builder + */ + public Builder userState(String userState) { + return set("user_state", Conv.STRING, userState); + } + + /** + * 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). + * + * @param biometricConsent the value + * @return this builder + */ + public Builder biometricConsent(SessionsCreateParams.BiometricConsent biometricConsent) { + return set("biometric_consent", Conv.model(SessionsCreateParams.BiometricConsent::fromJson), biometricConsent); + } + /** * Builds the value. * @@ -1547,4 +1608,115 @@ public Optional asBoolean() { } } } + + /** + * 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). + */ + public static final class BiometricConsent extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("given_at", "text_version"); + + private BiometricConsent(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code BiometricConsent} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static BiometricConsent fromJson(JsonNode json) { + return new BiometricConsent(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * When the person gave explicit biometric consent (ISO 8601). Cannot be in the future. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public OffsetDateTime givenAt() { + return Json.required(json, "given_at", Conv.DATE_TIME); + } + + /** + * Version id of the consent text the person accepted, in your own records. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String textVersion() { + return Json.required(json, "text_version", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link BiometricConsent}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(BiometricConsent from) { + super(from); + } + + /** + * When the person gave explicit biometric consent (ISO 8601). Cannot be in the future. + * + * @param givenAt the value + * @return this builder + */ + public Builder givenAt(OffsetDateTime givenAt) { + return set("given_at", Conv.DATE_TIME, givenAt); + } + + /** + * Version id of the consent text the person accepted, in your own records. + * + * @param textVersion the value + * @return this builder + */ + public Builder textVersion(String textVersion) { + return set("text_version", Conv.STRING, textVersion); + } + + /** + * Builds the value. + * + * @return a new immutable {@code BiometricConsent} + * @throws IllegalStateException when a required field is missing + */ + public BiometricConsent build() { + require("given_at", false); + require("text_version", false); + return new BiometricConsent(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } } diff --git a/src/main/java/me/didit/types/SessionsCreateResponse.java b/src/main/java/me/didit/types/SessionsCreateResponse.java index 9f82fbb..c3458f2 100644 --- a/src/main/java/me/didit/types/SessionsCreateResponse.java +++ b/src/main/java/me/didit/types/SessionsCreateResponse.java @@ -15,7 +15,7 @@ import org.jspecify.annotations.Nullable; public final class SessionsCreateResponse extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("session_id", "session_number", "session_token", "url", "vendor_data", "metadata", "status", "callback", "workflow_id", "workflow_version"); + private static final Set KNOWN_FIELDS = Set.of("session_id", "session_number", "session_token", "url", "vendor_data", "metadata", "tags", "status", "callback", "workflow_id", "workflow_version"); private SessionsCreateResponse(ObjectNode json, Map> files) { super(json, files); @@ -110,6 +110,16 @@ public JsonNode metadata() { return Json.required(json, "metadata", Conv.ANY); } + /** + * The tags on the session, by name. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> tags() { + return Json.optional(json, "tags", Conv.list(Conv.STRING)); + } + /** * 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. * @@ -223,6 +233,16 @@ public Builder metadata(@Nullable Object metadata) { return setAny("metadata", metadata); } + /** + * The tags on the session, by name. + * + * @param tags the value + * @return this builder + */ + public Builder tags(List tags) { + return set("tags", Conv.list(Conv.STRING), tags); + } + /** * 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. * diff --git a/src/main/java/me/didit/types/SessionsListParams.java b/src/main/java/me/didit/types/SessionsListParams.java index b2e5ba6..5c82ed1 100644 --- a/src/main/java/me/didit/types/SessionsListParams.java +++ b/src/main/java/me/didit/types/SessionsListParams.java @@ -102,7 +102,7 @@ public Optional vendorData() { } /** - * 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -335,7 +335,7 @@ public Builder vendorData(String vendorData) { } /** - * 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. + * 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. * * @param search the value * @return this builder diff --git a/src/main/java/me/didit/types/TransactionsBatchDeleteParams.java b/src/main/java/me/didit/types/TransactionsBatchDeleteParams.java new file mode 100644 index 0000000..1b26513 --- /dev/null +++ b/src/main/java/me/didit/types/TransactionsBatchDeleteParams.java @@ -0,0 +1,77 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Set; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +/** Parameters for {@code transactions.batch_delete}. */ +public final class TransactionsBatchDeleteParams extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of(); + + private TransactionsBatchDeleteParams(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TransactionsBatchDeleteParams} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TransactionsBatchDeleteParams fromJson(JsonNode json) { + return new TransactionsBatchDeleteParams(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TransactionsBatchDeleteParams}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TransactionsBatchDeleteParams from) { + super(from); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TransactionsBatchDeleteParams} + */ + public TransactionsBatchDeleteParams build() { + return new TransactionsBatchDeleteParams(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworkParticipant.java b/src/main/java/me/didit/types/TravelRuleNetworkParticipant.java new file mode 100644 index 0000000..ba77566 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworkParticipant.java @@ -0,0 +1,640 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +/** This application's own participant on one network. */ +public final class TravelRuleNetworkParticipant extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("network", "rail", "mode", "status", "participant_code", "requested_at", "submitted_at", "activated_at", "suspended_at", "last_error", "history"); + + private TravelRuleNetworkParticipant(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworkParticipant} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworkParticipant fromJson(JsonNode json) { + return new TravelRuleNetworkParticipant(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The network field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional network() { + return Json.optional(json, "network", Conv.enumeration(TravelRuleNetworkParticipant.Network::of)); + } + + /** + * The rail field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional rail() { + return Json.optional(json, "rail", Conv.enumeration(TravelRuleNetworkParticipant.Rail::of)); + } + + /** + * The mode field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional mode() { + return Json.optional(json, "mode", Conv.enumeration(TravelRuleNetworkParticipant.Mode::of)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleNetworkParticipant.Status::of)); + } + + /** + * The participant_code field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional participantCode() { + return Json.optional(json, "participant_code", Conv.STRING); + } + + /** + * The requested_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional requestedAt() { + return Json.optional(json, "requested_at", Conv.DATE_TIME); + } + + /** + * The submitted_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional submittedAt() { + return Json.optional(json, "submitted_at", Conv.DATE_TIME); + } + + /** + * The activated_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional activatedAt() { + return Json.optional(json, "activated_at", Conv.DATE_TIME); + } + + /** + * The suspended_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional suspendedAt() { + return Json.optional(json, "suspended_at", Conv.DATE_TIME); + } + + /** + * The network's answer when it refused the registration; empty otherwise. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional lastError() { + return Json.optional(json, "last_error", Conv.STRING); + } + + /** + * Every status change, oldest first. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> history() { + return Json.optional(json, "history", Conv.list(Conv.model(TravelRuleNetworkParticipant.History::fromJson))); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworkParticipant}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworkParticipant from) { + super(from); + } + + /** + * The network field. + * + * @param network the value + * @return this builder + */ + public Builder network(TravelRuleNetworkParticipant.Network network) { + return set("network", Conv.enumeration(TravelRuleNetworkParticipant.Network::of), network); + } + + /** + * The rail field. + * + * @param rail the value + * @return this builder + */ + public Builder rail(TravelRuleNetworkParticipant.Rail rail) { + return set("rail", Conv.enumeration(TravelRuleNetworkParticipant.Rail::of), rail); + } + + /** + * The mode field. + * + * @param mode the value + * @return this builder + */ + public Builder mode(TravelRuleNetworkParticipant.Mode mode) { + return set("mode", Conv.enumeration(TravelRuleNetworkParticipant.Mode::of), mode); + } + + /** + * 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. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleNetworkParticipant.Status status) { + return set("status", Conv.enumeration(TravelRuleNetworkParticipant.Status::of), status); + } + + /** + * The participant_code field. + * + * @param participantCode the value + * @return this builder + */ + public Builder participantCode(String participantCode) { + return set("participant_code", Conv.STRING, participantCode); + } + + /** + * The requested_at field. + * + * @param requestedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder requestedAt(@Nullable OffsetDateTime requestedAt) { + return setNullable("requested_at", Conv.DATE_TIME, requestedAt); + } + + /** + * The submitted_at field. + * + * @param submittedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder submittedAt(@Nullable OffsetDateTime submittedAt) { + return setNullable("submitted_at", Conv.DATE_TIME, submittedAt); + } + + /** + * The activated_at field. + * + * @param activatedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder activatedAt(@Nullable OffsetDateTime activatedAt) { + return setNullable("activated_at", Conv.DATE_TIME, activatedAt); + } + + /** + * The suspended_at field. + * + * @param suspendedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder suspendedAt(@Nullable OffsetDateTime suspendedAt) { + return setNullable("suspended_at", Conv.DATE_TIME, suspendedAt); + } + + /** + * The network's answer when it refused the registration; empty otherwise. + * + * @param lastError the value + * @return this builder + */ + public Builder lastError(String lastError) { + return set("last_error", Conv.STRING, lastError); + } + + /** + * Every status change, oldest first. + * + * @param history the value + * @return this builder + */ + public Builder history(List history) { + return set("history", Conv.list(Conv.model(TravelRuleNetworkParticipant.History::fromJson)), history); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworkParticipant} + */ + public TravelRuleNetworkParticipant build() { + return new TravelRuleNetworkParticipant(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Network extends StringEnum { + /** The value {@code "gtr"}. */ + public static final Network GTR = new Network("gtr"); + + /** The value {@code "trust"}. */ + public static final Network TRUST = new Network("trust"); + + /** The value {@code "verifyvasp"}. */ + public static final Network VERIFYVASP = new Network("verifyvasp"); + + /** The value {@code "sygna"}. */ + public static final Network SYGNA = new Network("sygna"); + + private static final List KNOWN_VALUES = List.of(GTR, TRUST, VERIFYVASP, SYGNA); + + private Network(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Network of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Network::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Rail extends StringEnum { + /** The value {@code "GTR"}. */ + public static final Rail GTR = new Rail("GTR"); + + /** The value {@code "TRUST"}. */ + public static final Rail TRUST = new Rail("TRUST"); + + /** The value {@code "VERIFYVASP"}. */ + public static final Rail VERIFYVASP = new Rail("VERIFYVASP"); + + /** The value {@code "SYGNA"}. */ + public static final Rail SYGNA = new Rail("SYGNA"); + + private static final List KNOWN_VALUES = List.of(GTR, TRUST, VERIFYVASP, SYGNA); + + private Rail(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Rail of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Mode extends StringEnum { + /** The value {@code "platform"}. */ + public static final Mode PLATFORM = new Mode("platform"); + + /** The value {@code "own"}. */ + public static final Mode OWN = new Mode("own"); + + private static final List KNOWN_VALUES = List.of(PLATFORM, OWN); + + private Mode(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Mode of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "not_requested"}. */ + public static final Status NOT_REQUESTED = new Status("not_requested"); + + /** The value {@code "questionnaire_pending"}. */ + public static final Status QUESTIONNAIRE_PENDING = new Status("questionnaire_pending"); + + /** The value {@code "submitted_to_network"}. */ + public static final Status SUBMITTED_TO_NETWORK = new Status("submitted_to_network"); + + /** The value {@code "active"}. */ + public static final Status ACTIVE = new Status("active"); + + /** The value {@code "suspended"}. */ + public static final Status SUSPENDED = new Status("suspended"); + + /** The value {@code "rejected"}. */ + public static final Status REJECTED = new Status("rejected"); + + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, QUESTIONNAIRE_PENDING, SUBMITTED_TO_NETWORK, ACTIVE, SUSPENDED, REJECTED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + public static final class History extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("at", "from", "to", "note"); + + private History(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code History} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static History fromJson(JsonNode json) { + return new History(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional at() { + return Json.optional(json, "at", Conv.DATE_TIME); + } + + /** + * The from field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional from() { + return Json.optional(json, "from", Conv.STRING); + } + + /** + * The to field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional to() { + return Json.optional(json, "to", Conv.STRING); + } + + /** + * Review note or the network's answer, when there is one. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional note() { + return Json.optional(json, "note", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link History}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(History from) { + super(from); + } + + /** + * The at field. + * + * @param at the value + * @return this builder + */ + public Builder at(OffsetDateTime at) { + return set("at", Conv.DATE_TIME, at); + } + + /** + * The from field. + * + * @param from the value + * @return this builder + */ + public Builder from(String from) { + return set("from", Conv.STRING, from); + } + + /** + * The to field. + * + * @param to the value + * @return this builder + */ + public Builder to(String to) { + return set("to", Conv.STRING, to); + } + + /** + * Review note or the network's answer, when there is one. + * + * @param note the value + * @return this builder + */ + public Builder note(String note) { + return set("note", Conv.STRING, note); + } + + /** + * Builds the value. + * + * @return a new immutable {@code History} + */ + public History build() { + return new History(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworkParticipantSummary.java b/src/main/java/me/didit/types/TravelRuleNetworkParticipantSummary.java new file mode 100644 index 0000000..d866e8d --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworkParticipantSummary.java @@ -0,0 +1,300 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +/** This application's own participant on the network, or null when it has none. */ +public final class TravelRuleNetworkParticipantSummary extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("mode", "status", "participant_code", "requested_at", "activated_at", "last_error"); + + private TravelRuleNetworkParticipantSummary(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworkParticipantSummary} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworkParticipantSummary fromJson(JsonNode json) { + return new TravelRuleNetworkParticipantSummary(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * platform: carried by Didit's membership; own: onboarded under the application's own membership. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional mode() { + return Json.optional(json, "mode", Conv.enumeration(TravelRuleNetworkParticipantSummary.Mode::of)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleNetworkParticipantSummary.Status::of)); + } + + /** + * The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional participantCode() { + return Json.optional(json, "participant_code", Conv.STRING); + } + + /** + * The requested_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional requestedAt() { + return Json.optional(json, "requested_at", Conv.DATE_TIME); + } + + /** + * The activated_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional activatedAt() { + return Json.optional(json, "activated_at", Conv.DATE_TIME); + } + + /** + * The network's answer when it refused the registration; empty otherwise. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional lastError() { + return Json.optional(json, "last_error", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworkParticipantSummary}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworkParticipantSummary from) { + super(from); + } + + /** + * platform: carried by Didit's membership; own: onboarded under the application's own membership. + * + * @param mode the value + * @return this builder + */ + public Builder mode(TravelRuleNetworkParticipantSummary.Mode mode) { + return set("mode", Conv.enumeration(TravelRuleNetworkParticipantSummary.Mode::of), mode); + } + + /** + * 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. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleNetworkParticipantSummary.Status status) { + return set("status", Conv.enumeration(TravelRuleNetworkParticipantSummary.Status::of), status); + } + + /** + * The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own. + * + * @param participantCode the value + * @return this builder + */ + public Builder participantCode(String participantCode) { + return set("participant_code", Conv.STRING, participantCode); + } + + /** + * The requested_at field. + * + * @param requestedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder requestedAt(@Nullable OffsetDateTime requestedAt) { + return setNullable("requested_at", Conv.DATE_TIME, requestedAt); + } + + /** + * The activated_at field. + * + * @param activatedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder activatedAt(@Nullable OffsetDateTime activatedAt) { + return setNullable("activated_at", Conv.DATE_TIME, activatedAt); + } + + /** + * The network's answer when it refused the registration; empty otherwise. + * + * @param lastError the value + * @return this builder + */ + public Builder lastError(String lastError) { + return set("last_error", Conv.STRING, lastError); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworkParticipantSummary} + */ + public TravelRuleNetworkParticipantSummary build() { + return new TravelRuleNetworkParticipantSummary(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * platform: carried by Didit's membership; own: onboarded under the application's own membership. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Mode extends StringEnum { + /** The value {@code "platform"}. */ + public static final Mode PLATFORM = new Mode("platform"); + + /** The value {@code "own"}. */ + public static final Mode OWN = new Mode("own"); + + private static final List KNOWN_VALUES = List.of(PLATFORM, OWN); + + private Mode(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Mode of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "not_requested"}. */ + public static final Status NOT_REQUESTED = new Status("not_requested"); + + /** The value {@code "questionnaire_pending"}. */ + public static final Status QUESTIONNAIRE_PENDING = new Status("questionnaire_pending"); + + /** The value {@code "submitted_to_network"}. */ + public static final Status SUBMITTED_TO_NETWORK = new Status("submitted_to_network"); + + /** The value {@code "active"}. */ + public static final Status ACTIVE = new Status("active"); + + /** The value {@code "suspended"}. */ + public static final Status SUSPENDED = new Status("suspended"); + + /** The value {@code "rejected"}. */ + public static final Status REJECTED = new Status("rejected"); + + private static final List KNOWN_VALUES = List.of(NOT_REQUESTED, QUESTIONNAIRE_PENDING, SUBMITTED_TO_NETWORK, ACTIVE, SUSPENDED, REJECTED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaire.java b/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaire.java new file mode 100644 index 0000000..eadcd57 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaire.java @@ -0,0 +1,418 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +public final class TravelRuleNetworkQuestionnaire extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("status", "answers", "submitted_at", "reviewed_at", "review_note", "history", "updated_at"); + + private TravelRuleNetworkQuestionnaire(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworkQuestionnaire} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworkQuestionnaire fromJson(JsonNode json) { + return new TravelRuleNetworkQuestionnaire(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleNetworkQuestionnaire.Status::of)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional answers() { + return Json.optional(json, "answers", Conv.model(TravelRuleNetworkQuestionnaireAnswers::fromJson)); + } + + /** + * The submitted_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional submittedAt() { + return Json.optional(json, "submitted_at", Conv.DATE_TIME); + } + + /** + * The reviewed_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional reviewedAt() { + return Json.optional(json, "reviewed_at", Conv.DATE_TIME); + } + + /** + * Didit's note on its last decision, for example what to change. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional reviewNote() { + return Json.optional(json, "review_note", Conv.STRING); + } + + /** + * Every status change, oldest first. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> history() { + return Json.optional(json, "history", Conv.list(Conv.model(TravelRuleNetworkQuestionnaire.History::fromJson))); + } + + /** + * The updated_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional updatedAt() { + return Json.optional(json, "updated_at", Conv.DATE_TIME); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworkQuestionnaire}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworkQuestionnaire from) { + super(from); + } + + /** + * Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleNetworkQuestionnaire.Status status) { + return set("status", Conv.enumeration(TravelRuleNetworkQuestionnaire.Status::of), status); + } + + /** + * 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. + * + * @param answers the value + * @return this builder + */ + public Builder answers(TravelRuleNetworkQuestionnaireAnswers answers) { + return set("answers", Conv.model(TravelRuleNetworkQuestionnaireAnswers::fromJson), answers); + } + + /** + * The submitted_at field. + * + * @param submittedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder submittedAt(@Nullable OffsetDateTime submittedAt) { + return setNullable("submitted_at", Conv.DATE_TIME, submittedAt); + } + + /** + * The reviewed_at field. + * + * @param reviewedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder reviewedAt(@Nullable OffsetDateTime reviewedAt) { + return setNullable("reviewed_at", Conv.DATE_TIME, reviewedAt); + } + + /** + * Didit's note on its last decision, for example what to change. + * + * @param reviewNote the value + * @return this builder + */ + public Builder reviewNote(String reviewNote) { + return set("review_note", Conv.STRING, reviewNote); + } + + /** + * Every status change, oldest first. + * + * @param history the value + * @return this builder + */ + public Builder history(List history) { + return set("history", Conv.list(Conv.model(TravelRuleNetworkQuestionnaire.History::fromJson)), history); + } + + /** + * The updated_at field. + * + * @param updatedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder updatedAt(@Nullable OffsetDateTime updatedAt) { + return setNullable("updated_at", Conv.DATE_TIME, updatedAt); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworkQuestionnaire} + */ + public TravelRuleNetworkQuestionnaire build() { + return new TravelRuleNetworkQuestionnaire(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "draft"}. */ + public static final Status DRAFT = new Status("draft"); + + /** The value {@code "submitted"}. */ + public static final Status SUBMITTED = new Status("submitted"); + + /** The value {@code "changes_requested"}. */ + public static final Status CHANGES_REQUESTED = new Status("changes_requested"); + + /** The value {@code "approved"}. */ + public static final Status APPROVED = new Status("approved"); + + /** The value {@code "rejected"}. */ + public static final Status REJECTED = new Status("rejected"); + + private static final List KNOWN_VALUES = List.of(DRAFT, SUBMITTED, CHANGES_REQUESTED, APPROVED, REJECTED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + public static final class History extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("at", "from", "to", "note"); + + private History(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code History} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static History fromJson(JsonNode json) { + return new History(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional at() { + return Json.optional(json, "at", Conv.DATE_TIME); + } + + /** + * The from field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional from() { + return Json.optional(json, "from", Conv.STRING); + } + + /** + * The to field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional to() { + return Json.optional(json, "to", Conv.STRING); + } + + /** + * Review note or the network's answer, when there is one. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional note() { + return Json.optional(json, "note", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link History}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(History from) { + super(from); + } + + /** + * The at field. + * + * @param at the value + * @return this builder + */ + public Builder at(OffsetDateTime at) { + return set("at", Conv.DATE_TIME, at); + } + + /** + * The from field. + * + * @param from the value + * @return this builder + */ + public Builder from(String from) { + return set("from", Conv.STRING, from); + } + + /** + * The to field. + * + * @param to the value + * @return this builder + */ + public Builder to(String to) { + return set("to", Conv.STRING, to); + } + + /** + * Review note or the network's answer, when there is one. + * + * @param note the value + * @return this builder + */ + public Builder note(String note) { + return set("note", Conv.STRING, note); + } + + /** + * Builds the value. + * + * @return a new immutable {@code History} + */ + public History build() { + return new History(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaireAnswers.java b/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaireAnswers.java new file mode 100644 index 0000000..473ef30 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworkQuestionnaireAnswers.java @@ -0,0 +1,571 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +/** + * 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. + */ +public final class TravelRuleNetworkQuestionnaireAnswers extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("legal_name", "registration_number", "incorporation_country", "registered_address", "website", "lei", "licences", "operating_jurisdictions", "compliance_officer_name", "compliance_officer_email", "aml_ctf_policy_attested", "sanctions_screening_attested", "data_protection_attested", "data_protection_contact_email", "expected_monthly_transfers", "expected_monthly_volume_usd", "assets"); + + private TravelRuleNetworkQuestionnaireAnswers(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworkQuestionnaireAnswers} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworkQuestionnaireAnswers fromJson(JsonNode json) { + return new TravelRuleNetworkQuestionnaireAnswers(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The legal_name field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional legalName() { + return Json.optional(json, "legal_name", Conv.STRING); + } + + /** + * The registration_number field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional registrationNumber() { + return Json.optional(json, "registration_number", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional incorporationCountry() { + return Json.optional(json, "incorporation_country", Conv.STRING); + } + + /** + * The registered_address field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional registeredAddress() { + return Json.optional(json, "registered_address", Conv.STRING); + } + + /** + * The website field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional website() { + return Json.optional(json, "website", Conv.STRING); + } + + /** + * Optional. A valid ISO 17442 LEI when given. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional lei() { + return Json.optional(json, "lei", Conv.STRING); + } + + /** + * Every licence or registration the VASP holds. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> licences() { + return Json.optional(json, "licences", Conv.list(Conv.model(TravelRuleNetworkQuestionnaireAnswers.Licences::fromJson))); + } + + /** + * The operating_jurisdictions field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> operatingJurisdictions() { + return Json.optional(json, "operating_jurisdictions", Conv.list(Conv.STRING)); + } + + /** + * The compliance_officer_name field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional complianceOfficerName() { + return Json.optional(json, "compliance_officer_name", Conv.STRING); + } + + /** + * The compliance_officer_email field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional complianceOfficerEmail() { + return Json.optional(json, "compliance_officer_email", Conv.STRING); + } + + /** + * Must be true to submit. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional amlCtfPolicyAttested() { + return Json.optional(json, "aml_ctf_policy_attested", Conv.BOOLEAN); + } + + /** + * Must be true to submit. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional sanctionsScreeningAttested() { + return Json.optional(json, "sanctions_screening_attested", Conv.BOOLEAN); + } + + /** + * Must be true to submit. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional dataProtectionAttested() { + return Json.optional(json, "data_protection_attested", Conv.BOOLEAN); + } + + /** + * Optional. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional dataProtectionContactEmail() { + return Json.optional(json, "data_protection_contact_email", Conv.STRING); + } + + /** + * The expected_monthly_transfers field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional expectedMonthlyTransfers() { + return Json.optional(json, "expected_monthly_transfers", Conv.LONG); + } + + /** + * Expected monthly volume in USD, up to 2 decimal places. Returned as a string. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional expectedMonthlyVolumeUsd() { + return Json.optional(json, "expected_monthly_volume_usd", Conv.STRING); + } + + /** + * Asset tickers, stored upper-case, sorted and de-duplicated. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> assets() { + return Json.optional(json, "assets", Conv.list(Conv.STRING)); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworkQuestionnaireAnswers}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworkQuestionnaireAnswers from) { + super(from); + } + + /** + * The legal_name field. + * + * @param legalName the value + * @return this builder + */ + public Builder legalName(String legalName) { + return set("legal_name", Conv.STRING, legalName); + } + + /** + * The registration_number field. + * + * @param registrationNumber the value + * @return this builder + */ + public Builder registrationNumber(String registrationNumber) { + return set("registration_number", Conv.STRING, registrationNumber); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @param incorporationCountry the value + * @return this builder + */ + public Builder incorporationCountry(String incorporationCountry) { + return set("incorporation_country", Conv.STRING, incorporationCountry); + } + + /** + * The registered_address field. + * + * @param registeredAddress the value + * @return this builder + */ + public Builder registeredAddress(String registeredAddress) { + return set("registered_address", Conv.STRING, registeredAddress); + } + + /** + * The website field. + * + * @param website the value + * @return this builder + */ + public Builder website(String website) { + return set("website", Conv.STRING, website); + } + + /** + * Optional. A valid ISO 17442 LEI when given. + * + * @param lei the value + * @return this builder + */ + public Builder lei(String lei) { + return set("lei", Conv.STRING, lei); + } + + /** + * Every licence or registration the VASP holds. + * + * @param licences the value + * @return this builder + */ + public Builder licences(List licences) { + return set("licences", Conv.list(Conv.model(TravelRuleNetworkQuestionnaireAnswers.Licences::fromJson)), licences); + } + + /** + * The operating_jurisdictions field. + * + * @param operatingJurisdictions the value + * @return this builder + */ + public Builder operatingJurisdictions(List operatingJurisdictions) { + return set("operating_jurisdictions", Conv.list(Conv.STRING), operatingJurisdictions); + } + + /** + * The compliance_officer_name field. + * + * @param complianceOfficerName the value + * @return this builder + */ + public Builder complianceOfficerName(String complianceOfficerName) { + return set("compliance_officer_name", Conv.STRING, complianceOfficerName); + } + + /** + * The compliance_officer_email field. + * + * @param complianceOfficerEmail the value + * @return this builder + */ + public Builder complianceOfficerEmail(String complianceOfficerEmail) { + return set("compliance_officer_email", Conv.STRING, complianceOfficerEmail); + } + + /** + * Must be true to submit. + * + * @param amlCtfPolicyAttested the value + * @return this builder + */ + public Builder amlCtfPolicyAttested(boolean amlCtfPolicyAttested) { + return set("aml_ctf_policy_attested", Conv.BOOLEAN, amlCtfPolicyAttested); + } + + /** + * Must be true to submit. + * + * @param sanctionsScreeningAttested the value + * @return this builder + */ + public Builder sanctionsScreeningAttested(boolean sanctionsScreeningAttested) { + return set("sanctions_screening_attested", Conv.BOOLEAN, sanctionsScreeningAttested); + } + + /** + * Must be true to submit. + * + * @param dataProtectionAttested the value + * @return this builder + */ + public Builder dataProtectionAttested(boolean dataProtectionAttested) { + return set("data_protection_attested", Conv.BOOLEAN, dataProtectionAttested); + } + + /** + * Optional. + * + * @param dataProtectionContactEmail the value + * @return this builder + */ + public Builder dataProtectionContactEmail(String dataProtectionContactEmail) { + return set("data_protection_contact_email", Conv.STRING, dataProtectionContactEmail); + } + + /** + * The expected_monthly_transfers field. + * + * @param expectedMonthlyTransfers the value + * @return this builder + */ + public Builder expectedMonthlyTransfers(long expectedMonthlyTransfers) { + return set("expected_monthly_transfers", Conv.LONG, expectedMonthlyTransfers); + } + + /** + * Expected monthly volume in USD, up to 2 decimal places. Returned as a string. + * + * @param expectedMonthlyVolumeUsd the value + * @return this builder + */ + public Builder expectedMonthlyVolumeUsd(String expectedMonthlyVolumeUsd) { + return set("expected_monthly_volume_usd", Conv.STRING, expectedMonthlyVolumeUsd); + } + + /** + * Asset tickers, stored upper-case, sorted and de-duplicated. + * + * @param assets the value + * @return this builder + */ + public Builder assets(List assets) { + return set("assets", Conv.list(Conv.STRING), assets); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworkQuestionnaireAnswers} + */ + public TravelRuleNetworkQuestionnaireAnswers build() { + return new TravelRuleNetworkQuestionnaireAnswers(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + public static final class Licences extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("regulator", "jurisdiction", "licence_type", "licence_number"); + + private Licences(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code Licences} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static Licences fromJson(JsonNode json) { + return new Licences(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The regulator field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String regulator() { + return Json.required(json, "regulator", Conv.STRING); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String jurisdiction() { + return Json.required(json, "jurisdiction", Conv.STRING); + } + + /** + * The licence_type field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String licenceType() { + return Json.required(json, "licence_type", Conv.STRING); + } + + /** + * The licence_number field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public String licenceNumber() { + return Json.required(json, "licence_number", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link Licences}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(Licences from) { + super(from); + } + + /** + * The regulator field. + * + * @param regulator the value + * @return this builder + */ + public Builder regulator(String regulator) { + return set("regulator", Conv.STRING, regulator); + } + + /** + * ISO 3166-1 alpha-2 country code. + * + * @param jurisdiction the value + * @return this builder + */ + public Builder jurisdiction(String jurisdiction) { + return set("jurisdiction", Conv.STRING, jurisdiction); + } + + /** + * The licence_type field. + * + * @param licenceType the value + * @return this builder + */ + public Builder licenceType(String licenceType) { + return set("licence_type", Conv.STRING, licenceType); + } + + /** + * The licence_number field. + * + * @param licenceNumber the value + * @return this builder + */ + public Builder licenceNumber(String licenceNumber) { + return set("licence_number", Conv.STRING, licenceNumber); + } + + /** + * Builds the value. + * + * @return a new immutable {@code Licences} + * @throws IllegalStateException when a required field is missing + */ + public Licences build() { + require("regulator", false); + require("jurisdiction", false); + require("licence_type", false); + require("licence_number", false); + return new Licences(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworkState.java b/src/main/java/me/didit/types/TravelRuleNetworkState.java new file mode 100644 index 0000000..b9537d7 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworkState.java @@ -0,0 +1,518 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +/** + * 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. + */ +public final class TravelRuleNetworkState extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("rail", "mode", "state", "reason", "platform_available", "connected", "participant", "vasps_reachable", "routed_30d"); + + private TravelRuleNetworkState(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworkState} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworkState fromJson(JsonNode json) { + return new TravelRuleNetworkState(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The rail field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional rail() { + return Json.optional(json, "rail", Conv.enumeration(TravelRuleNetworkState.Rail::of)); + } + + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional mode() { + return Json.optional(json, "mode", Conv.enumeration(TravelRuleNetworkState.Mode::of)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional state() { + return Json.optional(json, "state", Conv.enumeration(TravelRuleNetworkState.State::of)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional reason() { + return Json.optional(json, "reason", Conv.enumeration(TravelRuleNetworkState.Reason::of)); + } + + /** + * Whether Didit's own membership of this network is configured; not a live health check. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional platformAvailable() { + return Json.optional(json, "platform_available", Conv.BOOLEAN); + } + + /** + * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional connected() { + return Json.optional(json, "connected", Conv.BOOLEAN); + } + + /** + * This application's own participant on the network, or null when it has none. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional participant() { + return Json.optional(json, "participant", Conv.model(TravelRuleNetworkParticipantSummary::fromJson)); + } + + /** + * Directory VASPs reachable on this network. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional vaspsReachable() { + return Json.optional(json, "vasps_reachable", Conv.LONG); + } + + /** + * This application's transfers carried on this network in the last 30 days. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional routed30D() { + return Json.optional(json, "routed_30d", Conv.LONG); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworkState}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworkState from) { + super(from); + } + + /** + * The rail field. + * + * @param rail the value + * @return this builder + */ + public Builder rail(TravelRuleNetworkState.Rail rail) { + return set("rail", Conv.enumeration(TravelRuleNetworkState.Rail::of), rail); + } + + /** + * 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). + * + * @param mode the value + * @return this builder + */ + public Builder mode(TravelRuleNetworkState.Mode mode) { + return set("mode", Conv.enumeration(TravelRuleNetworkState.Mode::of), mode); + } + + /** + * 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. + * + * @param state the value + * @return this builder + */ + public Builder state(TravelRuleNetworkState.State state) { + return set("state", Conv.enumeration(TravelRuleNetworkState.State::of), state); + } + + /** + * 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. + * + * @param reason the value + * @return this builder + */ + public Builder reason(TravelRuleNetworkState.Reason reason) { + return set("reason", Conv.enumeration(TravelRuleNetworkState.Reason::of), reason); + } + + /** + * Whether Didit's own membership of this network is configured; not a live health check. + * + * @param platformAvailable the value + * @return this builder + */ + public Builder platformAvailable(boolean platformAvailable) { + return set("platform_available", Conv.BOOLEAN, platformAvailable); + } + + /** + * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. + * + * @param connected the value + * @return this builder + */ + public Builder connected(boolean connected) { + return set("connected", Conv.BOOLEAN, connected); + } + + /** + * This application's own participant on the network, or null when it has none. + * + * @param participant the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder participant(@Nullable TravelRuleNetworkParticipantSummary participant) { + return setNullable("participant", Conv.model(TravelRuleNetworkParticipantSummary::fromJson), participant); + } + + /** + * Directory VASPs reachable on this network. + * + * @param vaspsReachable the value + * @return this builder + */ + public Builder vaspsReachable(long vaspsReachable) { + return set("vasps_reachable", Conv.LONG, vaspsReachable); + } + + /** + * This application's transfers carried on this network in the last 30 days. + * + * @param routed30D the value + * @return this builder + */ + public Builder routed30D(long routed30D) { + return set("routed_30d", Conv.LONG, routed30D); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworkState} + */ + public TravelRuleNetworkState build() { + return new TravelRuleNetworkState(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Rail extends StringEnum { + /** The value {@code "GTR"}. */ + public static final Rail GTR = new Rail("GTR"); + + /** The value {@code "TRUST"}. */ + public static final Rail TRUST = new Rail("TRUST"); + + /** The value {@code "VERIFYVASP"}. */ + public static final Rail VERIFYVASP = new Rail("VERIFYVASP"); + + /** The value {@code "SYGNA"}. */ + public static final Rail SYGNA = new Rail("SYGNA"); + + private static final List KNOWN_VALUES = List.of(GTR, TRUST, VERIFYVASP, SYGNA); + + private Rail(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Rail of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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). + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Mode extends StringEnum { + /** The value {@code "own"}. */ + public static final Mode OWN = new Mode("own"); + + /** The value {@code "didit"}. */ + public static final Mode DIDIT = new Mode("didit"); + + /** The value {@code "off"}. */ + public static final Mode OFF = new Mode("off"); + + /** The value {@code "unavailable"}. */ + public static final Mode UNAVAILABLE = new Mode("unavailable"); + + private static final List KNOWN_VALUES = List.of(OWN, DIDIT, OFF, UNAVAILABLE); + + private Mode(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Mode of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class State extends StringEnum { + /** The value {@code "own_membership"}. */ + public static final State OWN_MEMBERSHIP = new State("own_membership"); + + /** The value {@code "live_through_didit"}. */ + public static final State LIVE_THROUGH_DIDIT = new State("live_through_didit"); + + /** The value {@code "off"}. */ + public static final State OFF = new State("off"); + + /** The value {@code "didit_onboarding"}. */ + public static final State DIDIT_ONBOARDING = new State("didit_onboarding"); + + /** The value {@code "questionnaire_needed"}. */ + public static final State QUESTIONNAIRE_NEEDED = new State("questionnaire_needed"); + + /** The value {@code "questionnaire_in_review"}. */ + public static final State QUESTIONNAIRE_IN_REVIEW = new State("questionnaire_in_review"); + + /** The value {@code "request_access"}. */ + public static final State REQUEST_ACCESS = new State("request_access"); + + /** The value {@code "request_sent"}. */ + public static final State REQUEST_SENT = new State("request_sent"); + + /** The value {@code "unavailable"}. */ + public static final State UNAVAILABLE = new State("unavailable"); + + /** The value {@code "error"}. */ + public static final State ERROR = new State("error"); + + private static final List KNOWN_VALUES = List.of(OWN_MEMBERSHIP, LIVE_THROUGH_DIDIT, OFF, DIDIT_ONBOARDING, QUESTIONNAIRE_NEEDED, QUESTIONNAIRE_IN_REVIEW, REQUEST_ACCESS, REQUEST_SENT, UNAVAILABLE, ERROR); + + private State(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static State of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, State::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Reason extends StringEnum { + /** The value {@code "own_membership_connected"}. */ + public static final Reason OWN_MEMBERSHIP_CONNECTED = new Reason("own_membership_connected"); + + /** The value {@code "platform_network_switched_off"}. */ + public static final Reason PLATFORM_NETWORK_SWITCHED_OFF = new Reason("platform_network_switched_off"); + + /** The value {@code "own_membership_only"}. */ + public static final Reason OWN_MEMBERSHIP_ONLY = new Reason("own_membership_only"); + + /** The value {@code "sandbox_application"}. */ + public static final Reason SANDBOX_APPLICATION = new Reason("sandbox_application"); + + /** The value {@code "didit_membership_not_live"}. */ + public static final Reason DIDIT_MEMBERSHIP_NOT_LIVE = new Reason("didit_membership_not_live"); + + /** The value {@code "participant_suspended"}. */ + public static final Reason PARTICIPANT_SUSPENDED = new Reason("participant_suspended"); + + /** The value {@code "participant_rejected"}. */ + public static final Reason PARTICIPANT_REJECTED = new Reason("participant_rejected"); + + /** The value {@code "questionnaire_not_submitted"}. */ + public static final Reason QUESTIONNAIRE_NOT_SUBMITTED = new Reason("questionnaire_not_submitted"); + + /** The value {@code "questionnaire_submitted"}. */ + public static final Reason QUESTIONNAIRE_SUBMITTED = new Reason("questionnaire_submitted"); + + /** The value {@code "questionnaire_changes_requested"}. */ + public static final Reason QUESTIONNAIRE_CHANGES_REQUESTED = new Reason("questionnaire_changes_requested"); + + /** The value {@code "questionnaire_rejected"}. */ + public static final Reason QUESTIONNAIRE_REJECTED = new Reason("questionnaire_rejected"); + + /** The value {@code "participant_not_requested"}. */ + public static final Reason PARTICIPANT_NOT_REQUESTED = new Reason("participant_not_requested"); + + /** The value {@code "participant_active"}. */ + public static final Reason PARTICIPANT_ACTIVE = new Reason("participant_active"); + + /** The value {@code "participant_key_missing"}. */ + public static final Reason PARTICIPANT_KEY_MISSING = new Reason("participant_key_missing"); + + /** The value {@code "participant_key_unresolved"}. */ + public static final Reason PARTICIPANT_KEY_UNRESOLVED = new Reason("participant_key_unresolved"); + + /** The value {@code "provisioning_failed"}. */ + public static final Reason PROVISIONING_FAILED = new Reason("provisioning_failed"); + + /** The value {@code "questionnaire_pending"}. */ + public static final Reason QUESTIONNAIRE_PENDING = new Reason("questionnaire_pending"); + + /** The value {@code "submitted_to_network"}. */ + public static final Reason SUBMITTED_TO_NETWORK = new Reason("submitted_to_network"); + + private static final List KNOWN_VALUES = List.of(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); + + private Reason(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Reason of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Reason::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNetworksOverview.java b/src/main/java/me/didit/types/TravelRuleNetworksOverview.java new file mode 100644 index 0000000..2e0c9ec --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleNetworksOverview.java @@ -0,0 +1,306 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; + +public final class TravelRuleNetworksOverview extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("platform_networks_enabled", "questionnaire", "participants", "networks"); + + private TravelRuleNetworksOverview(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleNetworksOverview} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleNetworksOverview fromJson(JsonNode json) { + return new TravelRuleNetworksOverview(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional platformNetworksEnabled() { + return Json.optional(json, "platform_networks_enabled", Conv.BOOLEAN); + } + + /** + * The questionnaire field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional questionnaire() { + return Json.optional(json, "questionnaire", Conv.model(TravelRuleNetworkQuestionnaire::fromJson)); + } + + /** + * The participants field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> participants() { + return Json.optional(json, "participants", Conv.list(Conv.model(TravelRuleNetworkParticipant::fromJson))); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional networks() { + return Json.optional(json, "networks", Conv.model(TravelRuleNetworksOverview.Networks::fromJson)); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleNetworksOverview}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleNetworksOverview from) { + super(from); + } + + /** + * Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403. + * + * @param platformNetworksEnabled the value + * @return this builder + */ + public Builder platformNetworksEnabled(boolean platformNetworksEnabled) { + return set("platform_networks_enabled", Conv.BOOLEAN, platformNetworksEnabled); + } + + /** + * The questionnaire field. + * + * @param questionnaire the value + * @return this builder + */ + public Builder questionnaire(TravelRuleNetworkQuestionnaire questionnaire) { + return set("questionnaire", Conv.model(TravelRuleNetworkQuestionnaire::fromJson), questionnaire); + } + + /** + * The participants field. + * + * @param participants the value + * @return this builder + */ + public Builder participants(List participants) { + return set("participants", Conv.list(Conv.model(TravelRuleNetworkParticipant::fromJson)), participants); + } + + /** + * 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. + * + * @param networks the value + * @return this builder + */ + public Builder networks(TravelRuleNetworksOverview.Networks networks) { + return set("networks", Conv.model(TravelRuleNetworksOverview.Networks::fromJson), networks); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleNetworksOverview} + */ + public TravelRuleNetworksOverview build() { + return new TravelRuleNetworksOverview(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * 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. + */ + public static final class Networks extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("gtr", "trust", "verifyvasp", "sygna"); + + private Networks(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code Networks} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static Networks fromJson(JsonNode json) { + return new Networks(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional gtr() { + return Json.optional(json, "gtr", Conv.model(TravelRuleNetworkState::fromJson)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional trust() { + return Json.optional(json, "trust", Conv.model(TravelRuleNetworkState::fromJson)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional verifyvasp() { + return Json.optional(json, "verifyvasp", Conv.model(TravelRuleNetworkState::fromJson)); + } + + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional sygna() { + return Json.optional(json, "sygna", Conv.model(TravelRuleNetworkState::fromJson)); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link Networks}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(Networks from) { + super(from); + } + + /** + * 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. + * + * @param gtr the value + * @return this builder + */ + public Builder gtr(TravelRuleNetworkState gtr) { + return set("gtr", Conv.model(TravelRuleNetworkState::fromJson), gtr); + } + + /** + * 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. + * + * @param trust the value + * @return this builder + */ + public Builder trust(TravelRuleNetworkState trust) { + return set("trust", Conv.model(TravelRuleNetworkState::fromJson), trust); + } + + /** + * 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. + * + * @param verifyvasp the value + * @return this builder + */ + public Builder verifyvasp(TravelRuleNetworkState verifyvasp) { + return set("verifyvasp", Conv.model(TravelRuleNetworkState::fromJson), verifyvasp); + } + + /** + * 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. + * + * @param sygna the value + * @return this builder + */ + public Builder sygna(TravelRuleNetworkState sygna) { + return set("sygna", Conv.model(TravelRuleNetworkState::fromJson), sygna); + } + + /** + * Builds the value. + * + * @return a new immutable {@code Networks} + */ + public Networks build() { + return new Networks(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleNextAction.java b/src/main/java/me/didit/types/TravelRuleNextAction.java index ac85953..c864824 100644 --- a/src/main/java/me/didit/types/TravelRuleNextAction.java +++ b/src/main/java/me/didit/types/TravelRuleNextAction.java @@ -53,7 +53,7 @@ public Builder toBuilder() { } /** - * select_counterparty_vasp: the end user picks the destination VASP in the wallet-ownership widget. 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. + * 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. * * @return the value * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type @@ -86,7 +86,7 @@ private Builder(TravelRuleNextAction from) { } /** - * select_counterparty_vasp: the end user picks the destination VASP in the wallet-ownership widget. 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. + * 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. * * @param action the value * @return this builder @@ -123,7 +123,7 @@ protected Builder self() { } /** - * select_counterparty_vasp: the end user picks the destination VASP in the wallet-ownership widget. 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. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ diff --git a/src/main/java/me/didit/types/TravelRuleSettingsDetail.java b/src/main/java/me/didit/types/TravelRuleSettingsDetail.java index 2f183a7..dadf71d 100644 --- a/src/main/java/me/didit/types/TravelRuleSettingsDetail.java +++ b/src/main/java/me/didit/types/TravelRuleSettingsDetail.java @@ -300,7 +300,7 @@ public Optional> disabledNetwork } /** - * Effective membership configuration for each network. No network, including GTR, is included by default. This is not a live health or delivery check. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -583,7 +583,7 @@ public Builder disabledNetworks(List } /** - * Effective membership configuration for each network. No network, including GTR, is included by default. This is not a live health or delivery check. + * 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. * * @param networks the value * @return this builder @@ -908,7 +908,7 @@ public boolean isKnown() { } /** - * Effective membership configuration for each network. No network, including GTR, is included by default. This is not a live health or delivery check. + * 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. */ public static final class Networks extends JsonModel { private static final Set KNOWN_FIELDS = Set.of("gtr", "trust", "verifyvasp", "sygna"); @@ -947,43 +947,43 @@ public Builder toBuilder() { } /** - * The gtr field. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type */ - public Optional gtr() { - return Json.optional(json, "gtr", Conv.model(TravelRuleSettingsDetail.Networks.Gtr::fromJson)); + public Optional gtr() { + return Json.optional(json, "gtr", Conv.model(TravelRuleNetworkState::fromJson)); } /** - * The trust field. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type */ - public Optional trust() { - return Json.optional(json, "trust", Conv.model(TravelRuleSettingsDetail.Networks.Trust::fromJson)); + public Optional trust() { + return Json.optional(json, "trust", Conv.model(TravelRuleNetworkState::fromJson)); } /** - * The verifyvasp field. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type */ - public Optional verifyvasp() { - return Json.optional(json, "verifyvasp", Conv.model(TravelRuleSettingsDetail.Networks.Verifyvasp::fromJson)); + public Optional verifyvasp() { + return Json.optional(json, "verifyvasp", Conv.model(TravelRuleNetworkState::fromJson)); } /** - * The sygna field. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type */ - public Optional sygna() { - return Json.optional(json, "sygna", Conv.model(TravelRuleSettingsDetail.Networks.Sygna::fromJson)); + public Optional sygna() { + return Json.optional(json, "sygna", Conv.model(TravelRuleNetworkState::fromJson)); } @Override @@ -1000,43 +1000,43 @@ private Builder(Networks from) { } /** - * The gtr field. + * 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. * * @param gtr the value * @return this builder */ - public Builder gtr(TravelRuleSettingsDetail.Networks.Gtr gtr) { - return set("gtr", Conv.model(TravelRuleSettingsDetail.Networks.Gtr::fromJson), gtr); + public Builder gtr(TravelRuleNetworkState gtr) { + return set("gtr", Conv.model(TravelRuleNetworkState::fromJson), gtr); } /** - * The trust field. + * 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. * * @param trust the value * @return this builder */ - public Builder trust(TravelRuleSettingsDetail.Networks.Trust trust) { - return set("trust", Conv.model(TravelRuleSettingsDetail.Networks.Trust::fromJson), trust); + public Builder trust(TravelRuleNetworkState trust) { + return set("trust", Conv.model(TravelRuleNetworkState::fromJson), trust); } /** - * The verifyvasp field. + * 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. * * @param verifyvasp the value * @return this builder */ - public Builder verifyvasp(TravelRuleSettingsDetail.Networks.Verifyvasp verifyvasp) { - return set("verifyvasp", Conv.model(TravelRuleSettingsDetail.Networks.Verifyvasp::fromJson), verifyvasp); + public Builder verifyvasp(TravelRuleNetworkState verifyvasp) { + return set("verifyvasp", Conv.model(TravelRuleNetworkState::fromJson), verifyvasp); } /** - * The sygna field. + * 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. * * @param sygna the value * @return this builder */ - public Builder sygna(TravelRuleSettingsDetail.Networks.Sygna sygna) { - return set("sygna", Conv.model(TravelRuleSettingsDetail.Networks.Sygna::fromJson), sygna); + public Builder sygna(TravelRuleNetworkState sygna) { + return set("sygna", Conv.model(TravelRuleNetworkState::fromJson), sygna); } /** @@ -1053,938 +1053,6 @@ protected Builder self() { return this; } } - - public static final class Gtr extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("rail", "mode", "platform_available", "connected"); - - private Gtr(ObjectNode json, Map> files) { - super(json, files); - } - - /** - * Reads a {@code Gtr} from JSON. Unknown fields are kept and readable - * through {@link #_additionalProperties()}. - * - * @param json a JSON object - * @return the parsed value - */ - public static Gtr fromJson(JsonNode json) { - return new Gtr(Json.copyObject(json), Map.of()); - } - - /** - * Returns an empty builder. - * - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Returns a builder holding a copy of every field of this value. - * - * @return a new builder - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * The rail field. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional rail() { - return Json.optional(json, "rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Gtr.Rail::of)); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional mode() { - return Json.optional(json, "mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Gtr.Mode::of)); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional platformAvailable() { - return Json.optional(json, "platform_available", Conv.BOOLEAN); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional connected() { - return Json.optional(json, "connected", Conv.BOOLEAN); - } - - @Override - protected Set knownFields() { - return KNOWN_FIELDS; - } - - /** Builds a {@link Gtr}. */ - public static final class Builder extends JsonModel.BaseBuilder { - private Builder() {} - - private Builder(Gtr from) { - super(from); - } - - /** - * The rail field. - * - * @param rail the value - * @return this builder - */ - public Builder rail(TravelRuleSettingsDetail.Networks.Gtr.Rail rail) { - return set("rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Gtr.Rail::of), rail); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @param mode the value - * @return this builder - */ - public Builder mode(TravelRuleSettingsDetail.Networks.Gtr.Mode mode) { - return set("mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Gtr.Mode::of), mode); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @param platformAvailable the value - * @return this builder - */ - public Builder platformAvailable(boolean platformAvailable) { - return set("platform_available", Conv.BOOLEAN, platformAvailable); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @param connected the value - * @return this builder - */ - public Builder connected(boolean connected) { - return set("connected", Conv.BOOLEAN, connected); - } - - /** - * Builds the value. - * - * @return a new immutable {@code Gtr} - */ - public Gtr build() { - return new Gtr(buildJson(), buildFiles()); - } - - @Override - protected Builder self() { - return this; - } - } - - /** - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Rail extends StringEnum { - /** The value {@code "GTR"}. */ - public static final Rail GTR = new Rail("GTR"); - - private static final List KNOWN_VALUES = List.of(GTR); - - private Rail(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Rail of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Mode extends StringEnum { - /** The value {@code "own"}. */ - public static final Mode OWN = new Mode("own"); - - /** The value {@code "didit"}. */ - public static final Mode DIDIT = new Mode("didit"); - - /** The value {@code "off"}. */ - public static final Mode OFF = new Mode("off"); - - /** The value {@code "unavailable"}. */ - public static final Mode UNAVAILABLE = new Mode("unavailable"); - - private static final List KNOWN_VALUES = List.of(OWN, DIDIT, OFF, UNAVAILABLE); - - private Mode(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Mode of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - } - - public static final class Trust extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("rail", "mode", "platform_available", "connected"); - - private Trust(ObjectNode json, Map> files) { - super(json, files); - } - - /** - * Reads a {@code Trust} from JSON. Unknown fields are kept and readable - * through {@link #_additionalProperties()}. - * - * @param json a JSON object - * @return the parsed value - */ - public static Trust fromJson(JsonNode json) { - return new Trust(Json.copyObject(json), Map.of()); - } - - /** - * Returns an empty builder. - * - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Returns a builder holding a copy of every field of this value. - * - * @return a new builder - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * The rail field. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional rail() { - return Json.optional(json, "rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Trust.Rail::of)); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional mode() { - return Json.optional(json, "mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Trust.Mode::of)); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional platformAvailable() { - return Json.optional(json, "platform_available", Conv.BOOLEAN); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional connected() { - return Json.optional(json, "connected", Conv.BOOLEAN); - } - - @Override - protected Set knownFields() { - return KNOWN_FIELDS; - } - - /** Builds a {@link Trust}. */ - public static final class Builder extends JsonModel.BaseBuilder { - private Builder() {} - - private Builder(Trust from) { - super(from); - } - - /** - * The rail field. - * - * @param rail the value - * @return this builder - */ - public Builder rail(TravelRuleSettingsDetail.Networks.Trust.Rail rail) { - return set("rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Trust.Rail::of), rail); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @param mode the value - * @return this builder - */ - public Builder mode(TravelRuleSettingsDetail.Networks.Trust.Mode mode) { - return set("mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Trust.Mode::of), mode); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @param platformAvailable the value - * @return this builder - */ - public Builder platformAvailable(boolean platformAvailable) { - return set("platform_available", Conv.BOOLEAN, platformAvailable); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @param connected the value - * @return this builder - */ - public Builder connected(boolean connected) { - return set("connected", Conv.BOOLEAN, connected); - } - - /** - * Builds the value. - * - * @return a new immutable {@code Trust} - */ - public Trust build() { - return new Trust(buildJson(), buildFiles()); - } - - @Override - protected Builder self() { - return this; - } - } - - /** - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Rail extends StringEnum { - /** The value {@code "TRUST"}. */ - public static final Rail TRUST = new Rail("TRUST"); - - private static final List KNOWN_VALUES = List.of(TRUST); - - private Rail(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Rail of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Mode extends StringEnum { - /** The value {@code "own"}. */ - public static final Mode OWN = new Mode("own"); - - /** The value {@code "didit"}. */ - public static final Mode DIDIT = new Mode("didit"); - - /** The value {@code "off"}. */ - public static final Mode OFF = new Mode("off"); - - /** The value {@code "unavailable"}. */ - public static final Mode UNAVAILABLE = new Mode("unavailable"); - - private static final List KNOWN_VALUES = List.of(OWN, DIDIT, OFF, UNAVAILABLE); - - private Mode(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Mode of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - } - - public static final class Verifyvasp extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("rail", "mode", "platform_available", "connected"); - - private Verifyvasp(ObjectNode json, Map> files) { - super(json, files); - } - - /** - * Reads a {@code Verifyvasp} from JSON. Unknown fields are kept and readable - * through {@link #_additionalProperties()}. - * - * @param json a JSON object - * @return the parsed value - */ - public static Verifyvasp fromJson(JsonNode json) { - return new Verifyvasp(Json.copyObject(json), Map.of()); - } - - /** - * Returns an empty builder. - * - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Returns a builder holding a copy of every field of this value. - * - * @return a new builder - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * The rail field. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional rail() { - return Json.optional(json, "rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Verifyvasp.Rail::of)); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional mode() { - return Json.optional(json, "mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Verifyvasp.Mode::of)); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional platformAvailable() { - return Json.optional(json, "platform_available", Conv.BOOLEAN); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional connected() { - return Json.optional(json, "connected", Conv.BOOLEAN); - } - - @Override - protected Set knownFields() { - return KNOWN_FIELDS; - } - - /** Builds a {@link Verifyvasp}. */ - public static final class Builder extends JsonModel.BaseBuilder { - private Builder() {} - - private Builder(Verifyvasp from) { - super(from); - } - - /** - * The rail field. - * - * @param rail the value - * @return this builder - */ - public Builder rail(TravelRuleSettingsDetail.Networks.Verifyvasp.Rail rail) { - return set("rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Verifyvasp.Rail::of), rail); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @param mode the value - * @return this builder - */ - public Builder mode(TravelRuleSettingsDetail.Networks.Verifyvasp.Mode mode) { - return set("mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Verifyvasp.Mode::of), mode); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @param platformAvailable the value - * @return this builder - */ - public Builder platformAvailable(boolean platformAvailable) { - return set("platform_available", Conv.BOOLEAN, platformAvailable); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @param connected the value - * @return this builder - */ - public Builder connected(boolean connected) { - return set("connected", Conv.BOOLEAN, connected); - } - - /** - * Builds the value. - * - * @return a new immutable {@code Verifyvasp} - */ - public Verifyvasp build() { - return new Verifyvasp(buildJson(), buildFiles()); - } - - @Override - protected Builder self() { - return this; - } - } - - /** - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Rail extends StringEnum { - /** The value {@code "VERIFYVASP"}. */ - public static final Rail VERIFYVASP = new Rail("VERIFYVASP"); - - private static final List KNOWN_VALUES = List.of(VERIFYVASP); - - private Rail(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Rail of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Mode extends StringEnum { - /** The value {@code "own"}. */ - public static final Mode OWN = new Mode("own"); - - /** The value {@code "didit"}. */ - public static final Mode DIDIT = new Mode("didit"); - - /** The value {@code "off"}. */ - public static final Mode OFF = new Mode("off"); - - /** The value {@code "unavailable"}. */ - public static final Mode UNAVAILABLE = new Mode("unavailable"); - - private static final List KNOWN_VALUES = List.of(OWN, DIDIT, OFF, UNAVAILABLE); - - private Mode(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Mode of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - } - - public static final class Sygna extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("rail", "mode", "platform_available", "connected"); - - private Sygna(ObjectNode json, Map> files) { - super(json, files); - } - - /** - * Reads a {@code Sygna} from JSON. Unknown fields are kept and readable - * through {@link #_additionalProperties()}. - * - * @param json a JSON object - * @return the parsed value - */ - public static Sygna fromJson(JsonNode json) { - return new Sygna(Json.copyObject(json), Map.of()); - } - - /** - * Returns an empty builder. - * - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Returns a builder holding a copy of every field of this value. - * - * @return a new builder - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * The rail field. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional rail() { - return Json.optional(json, "rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Sygna.Rail::of)); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional mode() { - return Json.optional(json, "mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Sygna.Mode::of)); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional platformAvailable() { - return Json.optional(json, "platform_available", Conv.BOOLEAN); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @return the value, or empty when the field is absent or null - * @throws me.didit.errors.UnexpectedResponseException when the field has another type - */ - public Optional connected() { - return Json.optional(json, "connected", Conv.BOOLEAN); - } - - @Override - protected Set knownFields() { - return KNOWN_FIELDS; - } - - /** Builds a {@link Sygna}. */ - public static final class Builder extends JsonModel.BaseBuilder { - private Builder() {} - - private Builder(Sygna from) { - super(from); - } - - /** - * The rail field. - * - * @param rail the value - * @return this builder - */ - public Builder rail(TravelRuleSettingsDetail.Networks.Sygna.Rail rail) { - return set("rail", Conv.enumeration(TravelRuleSettingsDetail.Networks.Sygna.Rail::of), rail); - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - * @param mode the value - * @return this builder - */ - public Builder mode(TravelRuleSettingsDetail.Networks.Sygna.Mode mode) { - return set("mode", Conv.enumeration(TravelRuleSettingsDetail.Networks.Sygna.Mode::of), mode); - } - - /** - * Whether platform membership is configured; not a live health check. - * - * @param platformAvailable the value - * @return this builder - */ - public Builder platformAvailable(boolean platformAvailable) { - return set("platform_available", Conv.BOOLEAN, platformAvailable); - } - - /** - * Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange. - * - * @param connected the value - * @return this builder - */ - public Builder connected(boolean connected) { - return set("connected", Conv.BOOLEAN, connected); - } - - /** - * Builds the value. - * - * @return a new immutable {@code Sygna} - */ - public Sygna build() { - return new Sygna(buildJson(), buildFiles()); - } - - @Override - protected Builder self() { - return this; - } - } - - /** - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Rail extends StringEnum { - /** The value {@code "SYGNA"}. */ - public static final Rail SYGNA = new Rail("SYGNA"); - - private static final List KNOWN_VALUES = List.of(SYGNA); - - private Rail(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Rail of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Rail::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - - /** - * own: connected customer membership takes precedence; didit: configured platform membership allowed; off: platform routing disabled with no own membership; unavailable: neither membership available. - * - *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. - */ - public static final class Mode extends StringEnum { - /** The value {@code "own"}. */ - public static final Mode OWN = new Mode("own"); - - /** The value {@code "didit"}. */ - public static final Mode DIDIT = new Mode("didit"); - - /** The value {@code "off"}. */ - public static final Mode OFF = new Mode("off"); - - /** The value {@code "unavailable"}. */ - public static final Mode UNAVAILABLE = new Mode("unavailable"); - - private static final List KNOWN_VALUES = List.of(OWN, DIDIT, OFF, UNAVAILABLE); - - private Mode(String value) { - super(value); - } - - /** - * Returns the constant for a known value, or a new instance for any other value. - * - * @param value the string value - * @return the enum value - */ - public static Mode of(String value) { - return StringEnum.lookup(KNOWN_VALUES, value, Mode::new); - } - - /** - * Returns the values known to this SDK version. - * - * @return the known values - */ - public static List knownValues() { - return KNOWN_VALUES; - } - - @Override - public boolean isKnown() { - return KNOWN_VALUES.contains(this); - } - } - } } /** diff --git a/src/main/java/me/didit/types/TravelRuleTransferPatchRequest.java b/src/main/java/me/didit/types/TravelRuleTransferPatchRequest.java index 68781ab..17dbb5e 100644 --- a/src/main/java/me/didit/types/TravelRuleTransferPatchRequest.java +++ b/src/main/java/me/didit/types/TravelRuleTransferPatchRequest.java @@ -14,10 +14,10 @@ import me.didit.core.StringEnum; /** - * Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, or action=update_originator_wallet with wallet_address. + * 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. */ public final class TravelRuleTransferPatchRequest extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("payment_txn_id", "action", "compliance_email", "wallet_address", "memo"); + private static final Set KNOWN_FIELDS = Set.of("payment_txn_id", "action", "compliance_email", "wallet_address", "memo", "beneficiary_vasp"); private TravelRuleTransferPatchRequest(ObjectNode json, Map> files) { super(json, files); @@ -63,7 +63,7 @@ public Optional paymentTxnId() { } /** - * "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). + * "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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -102,6 +102,16 @@ public Optional memo() { return Json.optional(json, "memo", Conv.STRING); } + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional beneficiaryVasp() { + return Json.optional(json, "beneficiary_vasp", Conv.STRING); + } + @Override protected Set knownFields() { return KNOWN_FIELDS; @@ -126,7 +136,7 @@ public Builder paymentTxnId(String paymentTxnId) { } /** - * "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). + * "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. * * @param action the value * @return this builder @@ -165,6 +175,16 @@ public Builder memo(String memo) { return set("memo", Conv.STRING, memo); } + /** + * 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. + * + * @param beneficiaryVasp the value + * @return this builder + */ + public Builder beneficiaryVasp(String beneficiaryVasp) { + return set("beneficiary_vasp", Conv.STRING, beneficiaryVasp); + } + /** * Builds the value. * @@ -181,7 +201,7 @@ protected Builder self() { } /** - * "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). + * "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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ @@ -198,7 +218,10 @@ public static final class Action extends StringEnum { /** The value {@code "update_originator_wallet"}. */ public static final Action UPDATE_ORIGINATOR_WALLET = new Action("update_originator_wallet"); - private static final List KNOWN_VALUES = List.of(CANCEL, RESEND, UPDATE_COMPLIANCE_EMAIL, UPDATE_ORIGINATOR_WALLET); + /** The value {@code "beneficiary_vasp"}. */ + public static final Action BENEFICIARY_VASP = new Action("beneficiary_vasp"); + + private static final List KNOWN_VALUES = List.of(CANCEL, RESEND, UPDATE_COMPLIANCE_EMAIL, UPDATE_ORIGINATOR_WALLET, BENEFICIARY_VASP); private Action(String value) { super(value); diff --git a/src/main/java/me/didit/types/TravelRuleWalletProofDecision.java b/src/main/java/me/didit/types/TravelRuleWalletProofDecision.java new file mode 100644 index 0000000..967e989 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWalletProofDecision.java @@ -0,0 +1,163 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +public final class TravelRuleWalletProofDecision extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("decision", "note"); + + private TravelRuleWalletProofDecision(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWalletProofDecision} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWalletProofDecision fromJson(JsonNode json) { + return new TravelRuleWalletProofDecision(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The decision field. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public TravelRuleWalletProofDecision.Decision decision() { + return Json.required(json, "decision", Conv.enumeration(TravelRuleWalletProofDecision.Decision::of)); + } + + /** + * Optional. Kept with the proof and returned as review_note. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional note() { + return Json.optional(json, "note", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWalletProofDecision}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWalletProofDecision from) { + super(from); + } + + /** + * The decision field. + * + * @param decision the value + * @return this builder + */ + public Builder decision(TravelRuleWalletProofDecision.Decision decision) { + return set("decision", Conv.enumeration(TravelRuleWalletProofDecision.Decision::of), decision); + } + + /** + * Optional. Kept with the proof and returned as review_note. + * + * @param note the value + * @return this builder + */ + public Builder note(String note) { + return set("note", Conv.STRING, note); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWalletProofDecision} + * @throws IllegalStateException when a required field is missing + */ + public TravelRuleWalletProofDecision build() { + require("decision", false); + return new TravelRuleWalletProofDecision(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Decision extends StringEnum { + /** The value {@code "approve"}. */ + public static final Decision APPROVE = new Decision("approve"); + + /** The value {@code "reject"}. */ + public static final Decision REJECT = new Decision("reject"); + + private static final List KNOWN_VALUES = List.of(APPROVE, REJECT); + + private Decision(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Decision of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Decision::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWalletProofFailureReason.java b/src/main/java/me/didit/types/TravelRuleWalletProofFailureReason.java new file mode 100644 index 0000000..3867721 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWalletProofFailureReason.java @@ -0,0 +1,87 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import java.util.List; +import me.didit.core.StringEnum; + +/** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ +public final class TravelRuleWalletProofFailureReason extends StringEnum { + /** The value {@code ""}. */ + public static final TravelRuleWalletProofFailureReason EMPTY = new TravelRuleWalletProofFailureReason(""); + + /** The value {@code "SIGNATURE_INVALID"}. */ + public static final TravelRuleWalletProofFailureReason SIGNATURE_INVALID = new TravelRuleWalletProofFailureReason("SIGNATURE_INVALID"); + + /** The value {@code "SIGNATURE_UNSUPPORTED_SCHEME"}. */ + public static final TravelRuleWalletProofFailureReason SIGNATURE_UNSUPPORTED_SCHEME = new TravelRuleWalletProofFailureReason("SIGNATURE_UNSUPPORTED_SCHEME"); + + /** The value {@code "DEPOSIT_NOT_FOUND"}. */ + public static final TravelRuleWalletProofFailureReason DEPOSIT_NOT_FOUND = new TravelRuleWalletProofFailureReason("DEPOSIT_NOT_FOUND"); + + /** The value {@code "DEPOSIT_UNCONFIRMED"}. */ + public static final TravelRuleWalletProofFailureReason DEPOSIT_UNCONFIRMED = new TravelRuleWalletProofFailureReason("DEPOSIT_UNCONFIRMED"); + + /** The value {@code "DEPOSIT_LOOKUP_FAILED"}. */ + public static final TravelRuleWalletProofFailureReason DEPOSIT_LOOKUP_FAILED = new TravelRuleWalletProofFailureReason("DEPOSIT_LOOKUP_FAILED"); + + /** The value {@code "DEPOSIT_MISMATCH"}. */ + public static final TravelRuleWalletProofFailureReason DEPOSIT_MISMATCH = new TravelRuleWalletProofFailureReason("DEPOSIT_MISMATCH"); + + /** The value {@code "DEPOSIT_STALE"}. */ + public static final TravelRuleWalletProofFailureReason DEPOSIT_STALE = new TravelRuleWalletProofFailureReason("DEPOSIT_STALE"); + + /** The value {@code "TXID_REUSED"}. */ + public static final TravelRuleWalletProofFailureReason TXID_REUSED = new TravelRuleWalletProofFailureReason("TXID_REUSED"); + + /** The value {@code "CHAIN_UNSUPPORTED"}. */ + public static final TravelRuleWalletProofFailureReason CHAIN_UNSUPPORTED = new TravelRuleWalletProofFailureReason("CHAIN_UNSUPPORTED"); + + /** The value {@code "WINDOW_EXPIRED"}. */ + public static final TravelRuleWalletProofFailureReason WINDOW_EXPIRED = new TravelRuleWalletProofFailureReason("WINDOW_EXPIRED"); + + /** The value {@code "OWNERSHIP_CHANGED"}. */ + public static final TravelRuleWalletProofFailureReason OWNERSHIP_CHANGED = new TravelRuleWalletProofFailureReason("OWNERSHIP_CHANGED"); + + /** The value {@code "METHOD_SWITCHED"}. */ + public static final TravelRuleWalletProofFailureReason METHOD_SWITCHED = new TravelRuleWalletProofFailureReason("METHOD_SWITCHED"); + + /** The value {@code "REVIEW_REJECTED"}. */ + public static final TravelRuleWalletProofFailureReason REVIEW_REJECTED = new TravelRuleWalletProofFailureReason("REVIEW_REJECTED"); + + /** The value {@code "ABANDONED"}. */ + public static final TravelRuleWalletProofFailureReason ABANDONED = new TravelRuleWalletProofFailureReason("ABANDONED"); + + private static final List KNOWN_VALUES = List.of(EMPTY, 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); + + private TravelRuleWalletProofFailureReason(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static TravelRuleWalletProofFailureReason of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, TravelRuleWalletProofFailureReason::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWalletProofReview.java b/src/main/java/me/didit/types/TravelRuleWalletProofReview.java new file mode 100644 index 0000000..5b2dc90 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWalletProofReview.java @@ -0,0 +1,528 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +public final class TravelRuleWalletProofReview extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("proof_id", "method", "status", "wallet_address", "chain", "holder_name", "is_ownership_verified", "transaction_id", "file_name", "content_type", "screenshot_url", "attempts", "failure_reason", "method_chosen_at", "decided_at", "decided_by", "review_note", "created_at"); + + private TravelRuleWalletProofReview(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWalletProofReview} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWalletProofReview fromJson(JsonNode json) { + return new TravelRuleWalletProofReview(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The proof_id field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional proofId() { + return Json.optional(json, "proof_id", Conv.STRING); + } + + /** + * The method field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional method() { + return Json.optional(json, "method", Conv.enumeration(TravelRuleWalletProofReview.Method::of)); + } + + /** + * The status field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleWalletProofReview.Status::of)); + } + + /** + * The wallet_address field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional walletAddress() { + return Json.optional(json, "wallet_address", Conv.STRING); + } + + /** + * Canonical chain identifier, e.g. ETH, BTC, SOL, TRX. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional chain() { + return Json.optional(json, "chain", Conv.STRING); + } + + /** + * The holder_name field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional holderName() { + return Json.optional(json, "holder_name", Conv.STRING); + } + + /** + * Whether the address book entry is ownership-verified, by this proof or another one. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional isOwnershipVerified() { + return Json.optional(json, "is_ownership_verified", Conv.BOOLEAN); + } + + /** + * The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional transactionId() { + return Json.optional(json, "transaction_id", Conv.STRING); + } + + /** + * The file_name field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional fileName() { + return Json.optional(json, "file_name", Conv.STRING); + } + + /** + * The content_type field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional contentType() { + return Json.optional(json, "content_type", Conv.STRING); + } + + /** + * Time-limited link to the uploaded screenshot. List the queue again for a fresh link. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional screenshotUrl() { + return Json.optional(json, "screenshot_url", Conv.STRING); + } + + /** + * How many files the end user uploaded for this proof. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional attempts() { + return Json.optional(json, "attempts", Conv.LONG); + } + + /** + * REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional failureReason() { + return Json.optional(json, "failure_reason", Conv.enumeration(TravelRuleWalletProofFailureReason::of)); + } + + /** + * When the end user picked the screenshot method. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional methodChosenAt() { + return Json.optional(json, "method_chosen_at", Conv.DATE_TIME); + } + + /** + * The decided_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional decidedAt() { + return Json.optional(json, "decided_at", Conv.DATE_TIME); + } + + /** + * Who closed the proof: review:<user identifier> for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional decidedBy() { + return Json.optional(json, "decided_by", Conv.STRING); + } + + /** + * The note sent with the decision. Empty when none. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional reviewNote() { + return Json.optional(json, "review_note", Conv.STRING); + } + + /** + * The created_at field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional createdAt() { + return Json.optional(json, "created_at", Conv.DATE_TIME); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWalletProofReview}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWalletProofReview from) { + super(from); + } + + /** + * The proof_id field. + * + * @param proofId the value + * @return this builder + */ + public Builder proofId(String proofId) { + return set("proof_id", Conv.STRING, proofId); + } + + /** + * The method field. + * + * @param method the value + * @return this builder + */ + public Builder method(TravelRuleWalletProofReview.Method method) { + return set("method", Conv.enumeration(TravelRuleWalletProofReview.Method::of), method); + } + + /** + * The status field. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleWalletProofReview.Status status) { + return set("status", Conv.enumeration(TravelRuleWalletProofReview.Status::of), status); + } + + /** + * The wallet_address field. + * + * @param walletAddress the value + * @return this builder + */ + public Builder walletAddress(String walletAddress) { + return set("wallet_address", Conv.STRING, walletAddress); + } + + /** + * Canonical chain identifier, e.g. ETH, BTC, SOL, TRX. + * + * @param chain the value + * @return this builder + */ + public Builder chain(String chain) { + return set("chain", Conv.STRING, chain); + } + + /** + * The holder_name field. + * + * @param holderName the value + * @return this builder + */ + public Builder holderName(String holderName) { + return set("holder_name", Conv.STRING, holderName); + } + + /** + * Whether the address book entry is ownership-verified, by this proof or another one. + * + * @param isOwnershipVerified the value + * @return this builder + */ + public Builder isOwnershipVerified(boolean isOwnershipVerified) { + return set("is_ownership_verified", Conv.BOOLEAN, isOwnershipVerified); + } + + /** + * The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id. + * + * @param transactionId the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder transactionId(@Nullable String transactionId) { + return setNullable("transaction_id", Conv.STRING, transactionId); + } + + /** + * The file_name field. + * + * @param fileName the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder fileName(@Nullable String fileName) { + return setNullable("file_name", Conv.STRING, fileName); + } + + /** + * The content_type field. + * + * @param contentType the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder contentType(@Nullable String contentType) { + return setNullable("content_type", Conv.STRING, contentType); + } + + /** + * Time-limited link to the uploaded screenshot. List the queue again for a fresh link. + * + * @param screenshotUrl the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder screenshotUrl(@Nullable String screenshotUrl) { + return setNullable("screenshot_url", Conv.STRING, screenshotUrl); + } + + /** + * How many files the end user uploaded for this proof. + * + * @param attempts the value + * @return this builder + */ + public Builder attempts(long attempts) { + return set("attempts", Conv.LONG, attempts); + } + + /** + * REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise. + * + * @param failureReason the value + * @return this builder + */ + public Builder failureReason(TravelRuleWalletProofFailureReason failureReason) { + return set("failure_reason", Conv.enumeration(TravelRuleWalletProofFailureReason::of), failureReason); + } + + /** + * When the end user picked the screenshot method. + * + * @param methodChosenAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder methodChosenAt(@Nullable OffsetDateTime methodChosenAt) { + return setNullable("method_chosen_at", Conv.DATE_TIME, methodChosenAt); + } + + /** + * The decided_at field. + * + * @param decidedAt the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder decidedAt(@Nullable OffsetDateTime decidedAt) { + return setNullable("decided_at", Conv.DATE_TIME, decidedAt); + } + + /** + * Who closed the proof: review:<user identifier> for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING. + * + * @param decidedBy the value + * @return this builder + */ + public Builder decidedBy(String decidedBy) { + return set("decided_by", Conv.STRING, decidedBy); + } + + /** + * The note sent with the decision. Empty when none. + * + * @param reviewNote the value + * @return this builder + */ + public Builder reviewNote(String reviewNote) { + return set("review_note", Conv.STRING, reviewNote); + } + + /** + * The created_at field. + * + * @param createdAt the value + * @return this builder + */ + public Builder createdAt(OffsetDateTime createdAt) { + return set("created_at", Conv.DATE_TIME, createdAt); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWalletProofReview} + */ + public TravelRuleWalletProofReview build() { + return new TravelRuleWalletProofReview(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Method extends StringEnum { + /** The value {@code "SCREENSHOT"}. */ + public static final Method SCREENSHOT = new Method("SCREENSHOT"); + + private static final List KNOWN_VALUES = List.of(SCREENSHOT); + + private Method(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Method of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Method::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "PENDING"}. */ + public static final Status PENDING = new Status("PENDING"); + + /** The value {@code "VERIFIED"}. */ + public static final Status VERIFIED = new Status("VERIFIED"); + + /** The value {@code "REJECTED"}. */ + public static final Status REJECTED = new Status("REJECTED"); + + /** The value {@code "EXPIRED"}. */ + public static final Status EXPIRED = new Status("EXPIRED"); + + private static final List KNOWN_VALUES = List.of(PENDING, VERIFIED, REJECTED, EXPIRED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWalletProofsListParams.java b/src/main/java/me/didit/types/TravelRuleWalletProofsListParams.java new file mode 100644 index 0000000..13d27fe --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWalletProofsListParams.java @@ -0,0 +1,188 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +/** Parameters for {@code travel_rule.wallet_proofs.list}. */ +public final class TravelRuleWalletProofsListParams extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("status", "limit", "offset"); + + private TravelRuleWalletProofsListParams(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWalletProofsListParams} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWalletProofsListParams fromJson(JsonNode json) { + return new TravelRuleWalletProofsListParams(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Which proofs to list. Defaults to PENDING, the ones still to decide. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleWalletProofsListParams.Status::of)); + } + + /** + * The limit field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional limit() { + return Json.optional(json, "limit", Conv.LONG); + } + + /** + * The offset field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional offset() { + return Json.optional(json, "offset", Conv.LONG); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWalletProofsListParams}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWalletProofsListParams from) { + super(from); + } + + /** + * Which proofs to list. Defaults to PENDING, the ones still to decide. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleWalletProofsListParams.Status status) { + return set("status", Conv.enumeration(TravelRuleWalletProofsListParams.Status::of), status); + } + + /** + * The limit field. + * + * @param limit the value + * @return this builder + */ + public Builder limit(long limit) { + return set("limit", Conv.LONG, limit); + } + + /** + * The offset field. + * + * @param offset the value + * @return this builder + */ + public Builder offset(long offset) { + return set("offset", Conv.LONG, offset); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWalletProofsListParams} + */ + public TravelRuleWalletProofsListParams build() { + return new TravelRuleWalletProofsListParams(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "PENDING"}. */ + public static final Status PENDING = new Status("PENDING"); + + /** The value {@code "VERIFIED"}. */ + public static final Status VERIFIED = new Status("VERIFIED"); + + /** The value {@code "REJECTED"}. */ + public static final Status REJECTED = new Status("REJECTED"); + + /** The value {@code "EXPIRED"}. */ + public static final Status EXPIRED = new Status("EXPIRED"); + + private static final List KNOWN_VALUES = List.of(PENDING, VERIFIED, REJECTED, EXPIRED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWalletProofsListResponse.java b/src/main/java/me/didit/types/TravelRuleWalletProofsListResponse.java new file mode 100644 index 0000000..53937a0 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWalletProofsListResponse.java @@ -0,0 +1,159 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import org.jspecify.annotations.Nullable; + +public final class TravelRuleWalletProofsListResponse extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("count", "next", "previous", "results"); + + private TravelRuleWalletProofsListResponse(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWalletProofsListResponse} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWalletProofsListResponse fromJson(JsonNode json) { + return new TravelRuleWalletProofsListResponse(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The count field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional count() { + return Json.optional(json, "count", Conv.LONG); + } + + /** + * The next field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional next() { + return Json.optional(json, "next", Conv.STRING); + } + + /** + * The previous field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional previous() { + return Json.optional(json, "previous", Conv.STRING); + } + + /** + * The results field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional> results() { + return Json.optional(json, "results", Conv.list(Conv.model(TravelRuleWalletProofReview::fromJson))); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWalletProofsListResponse}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWalletProofsListResponse from) { + super(from); + } + + /** + * The count field. + * + * @param count the value + * @return this builder + */ + public Builder count(long count) { + return set("count", Conv.LONG, count); + } + + /** + * The next field. + * + * @param next the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder next(@Nullable String next) { + return setNullable("next", Conv.STRING, next); + } + + /** + * The previous field. + * + * @param previous the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder previous(@Nullable String previous) { + return setNullable("previous", Conv.STRING, previous); + } + + /** + * The results field. + * + * @param results the value + * @return this builder + */ + public Builder results(List results) { + return set("results", Conv.list(Conv.model(TravelRuleWalletProofReview::fromJson)), results); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWalletProofsListResponse} + */ + public TravelRuleWalletProofsListResponse build() { + return new TravelRuleWalletProofsListResponse(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWidgetContext.java b/src/main/java/me/didit/types/TravelRuleWidgetContext.java index 0bef6bb..6897b56 100644 --- a/src/main/java/me/didit/types/TravelRuleWidgetContext.java +++ b/src/main/java/me/didit/types/TravelRuleWidgetContext.java @@ -16,7 +16,7 @@ import org.jspecify.annotations.Nullable; public final class TravelRuleWidgetContext extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("vasp_name", "wallet_address", "chain", "holder_name", "allowed_methods", "transfer_status", "callback_url", "expires_at", "completed_at", "proofs", "max_attempts", "attempts_used", "attempts_remaining", "customization"); + private static final Set KNOWN_FIELDS = Set.of("vasp_name", "wallet_address", "chain", "chain_family", "holder_name", "allowed_methods", "transfer_status", "callback_url", "expires_at", "completed_at", "proofs", "max_attempts", "attempts_used", "attempts_remaining", "customization"); private TravelRuleWidgetContext(ObjectNode json, Map> files) { super(json, files); @@ -72,7 +72,7 @@ public Optional walletAddress() { } /** - * The chain field. + * The chain the session was minted with, as you sent it. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -81,6 +81,16 @@ public Optional chain() { return Json.optional(json, "chain", Conv.STRING); } + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional chainFamily() { + return Json.optional(json, "chain_family", Conv.enumeration(TravelRuleWidgetContext.ChainFamily::of)); + } + /** * The holder_name field. * @@ -92,7 +102,7 @@ public Optional holderName() { } /** - * Proof methods available for this session. MESSAGE_SIGNING requires a supported chain family, SATOSHI_TEST requires a satoshi_deposit_address on the session, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting and an ownership_validation_mode that accepts a declaration for the linked transfer (never in FULL; in SIMPLIFIED only below simplified_validation_threshold). Initiating a SELF_DECLARATION the mode does not accept answers 403. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -225,7 +235,7 @@ public Builder walletAddress(String walletAddress) { } /** - * The chain field. + * The chain the session was minted with, as you sent it. * * @param chain the value * @return this builder @@ -234,6 +244,16 @@ public Builder chain(String chain) { return set("chain", Conv.STRING, chain); } + /** + * 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. + * + * @param chainFamily the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder chainFamily(TravelRuleWidgetContext.@Nullable ChainFamily chainFamily) { + return setNullable("chain_family", Conv.enumeration(TravelRuleWidgetContext.ChainFamily::of), chainFamily); + } + /** * The holder_name field. * @@ -245,7 +265,7 @@ public Builder holderName(String holderName) { } /** - * Proof methods available for this session. MESSAGE_SIGNING requires a supported chain family, SATOSHI_TEST requires a satoshi_deposit_address on the session, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting and an ownership_validation_mode that accepts a declaration for the linked transfer (never in FULL; in SIMPLIFIED only below simplified_validation_threshold). Initiating a SELF_DECLARATION the mode does not accept answers 403. + * 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. * * @param allowedMethods the value * @return this builder @@ -359,6 +379,55 @@ protected Builder self() { } } + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class ChainFamily extends StringEnum { + /** The value {@code "EVM"}. */ + public static final ChainFamily EVM = new ChainFamily("EVM"); + + /** The value {@code "BTC"}. */ + public static final ChainFamily BTC = new ChainFamily("BTC"); + + /** The value {@code "SOL"}. */ + public static final ChainFamily SOL = new ChainFamily("SOL"); + + /** The value {@code "TRON"}. */ + public static final ChainFamily TRON = new ChainFamily("TRON"); + + private static final List KNOWN_VALUES = List.of(EVM, BTC, SOL, TRON); + + private ChainFamily(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static ChainFamily of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, ChainFamily::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + /** * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. diff --git a/src/main/java/me/didit/types/TravelRuleWidgetProofCreate.java b/src/main/java/me/didit/types/TravelRuleWidgetProofCreate.java new file mode 100644 index 0000000..92c961a --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWidgetProofCreate.java @@ -0,0 +1,190 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +public final class TravelRuleWidgetProofCreate extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("method", "wallet_address", "chain"); + + private TravelRuleWidgetProofCreate(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWidgetProofCreate} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWidgetProofCreate fromJson(JsonNode json) { + return new TravelRuleWidgetProofCreate(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The proof method the end user picked. It must be one of the context's allowed_methods. + * + * @return the value + * @throws me.didit.errors.UnexpectedResponseException when the field is absent or has another type + */ + public TravelRuleWidgetProofCreate.Method method() { + return Json.required(json, "method", Conv.enumeration(TravelRuleWidgetProofCreate.Method::of)); + } + + /** + * Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional walletAddress() { + return Json.optional(json, "wallet_address", Conv.STRING); + } + + /** + * Defaults to the session's chain. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional chain() { + return Json.optional(json, "chain", Conv.STRING); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWidgetProofCreate}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWidgetProofCreate from) { + super(from); + } + + /** + * The proof method the end user picked. It must be one of the context's allowed_methods. + * + * @param method the value + * @return this builder + */ + public Builder method(TravelRuleWidgetProofCreate.Method method) { + return set("method", Conv.enumeration(TravelRuleWidgetProofCreate.Method::of), method); + } + + /** + * Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer. + * + * @param walletAddress the value + * @return this builder + */ + public Builder walletAddress(String walletAddress) { + return set("wallet_address", Conv.STRING, walletAddress); + } + + /** + * Defaults to the session's chain. + * + * @param chain the value + * @return this builder + */ + public Builder chain(String chain) { + return set("chain", Conv.STRING, chain); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWidgetProofCreate} + * @throws IllegalStateException when a required field is missing + */ + public TravelRuleWidgetProofCreate build() { + require("method", false); + return new TravelRuleWidgetProofCreate(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * The proof method the end user picked. It must be one of the context's allowed_methods. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Method extends StringEnum { + /** The value {@code "MESSAGE_SIGNING"}. */ + public static final Method MESSAGE_SIGNING = new Method("MESSAGE_SIGNING"); + + /** The value {@code "SATOSHI_TEST"}. */ + public static final Method SATOSHI_TEST = new Method("SATOSHI_TEST"); + + /** The value {@code "SCREENSHOT"}. */ + public static final Method SCREENSHOT = new Method("SCREENSHOT"); + + /** The value {@code "SELF_DECLARATION"}. */ + public static final Method SELF_DECLARATION = new Method("SELF_DECLARATION"); + + private static final List KNOWN_VALUES = List.of(MESSAGE_SIGNING, SATOSHI_TEST, SCREENSHOT, SELF_DECLARATION); + + private Method(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Method of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Method::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWidgetProofError.java b/src/main/java/me/didit/types/TravelRuleWidgetProofError.java new file mode 100644 index 0000000..5496cc1 --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWidgetProofError.java @@ -0,0 +1,337 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; + +public final class TravelRuleWidgetProofError extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("detail", "code", "status", "proof_id", "method", "max_attempts", "attempts_used", "attempts_remaining"); + + private TravelRuleWidgetProofError(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWidgetProofError} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWidgetProofError fromJson(JsonNode json) { + return new TravelRuleWidgetProofError(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Human-readable message in English. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detail() { + return Json.optional(json, "detail", Conv.STRING); + } + + /** + * Machine-readable reason, so a client can show its own translated message. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional code() { + return Json.optional(json, "code", Conv.enumeration(TravelRuleWalletProofFailureReason::of)); + } + + /** + * 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). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleWidgetProofError.Status::of)); + } + + /** + * Present with status: the closed proof. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional proofId() { + return Json.optional(json, "proof_id", Conv.STRING); + } + + /** + * Present with status: the closed proof's method. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional method() { + return Json.optional(json, "method", Conv.enumeration(TravelRuleWidgetProofError.Method::of)); + } + + /** + * Present with status: rejected wallet proofs this session allows. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional maxAttempts() { + return Json.optional(json, "max_attempts", Conv.LONG); + } + + /** + * Present with status: rejected proofs so far. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional attemptsUsed() { + return Json.optional(json, "attempts_used", Conv.LONG); + } + + /** + * Present with status: when 0, starting another proof answers 409 and you mint a new widget session. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional attemptsRemaining() { + return Json.optional(json, "attempts_remaining", Conv.LONG); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWidgetProofError}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWidgetProofError from) { + super(from); + } + + /** + * Human-readable message in English. + * + * @param detail the value + * @return this builder + */ + public Builder detail(String detail) { + return set("detail", Conv.STRING, detail); + } + + /** + * Machine-readable reason, so a client can show its own translated message. + * + * @param code the value + * @return this builder + */ + public Builder code(TravelRuleWalletProofFailureReason code) { + return set("code", Conv.enumeration(TravelRuleWalletProofFailureReason::of), code); + } + + /** + * 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). + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleWidgetProofError.Status status) { + return set("status", Conv.enumeration(TravelRuleWidgetProofError.Status::of), status); + } + + /** + * Present with status: the closed proof. + * + * @param proofId the value + * @return this builder + */ + public Builder proofId(String proofId) { + return set("proof_id", Conv.STRING, proofId); + } + + /** + * Present with status: the closed proof's method. + * + * @param method the value + * @return this builder + */ + public Builder method(TravelRuleWidgetProofError.Method method) { + return set("method", Conv.enumeration(TravelRuleWidgetProofError.Method::of), method); + } + + /** + * Present with status: rejected wallet proofs this session allows. + * + * @param maxAttempts the value + * @return this builder + */ + public Builder maxAttempts(long maxAttempts) { + return set("max_attempts", Conv.LONG, maxAttempts); + } + + /** + * Present with status: rejected proofs so far. + * + * @param attemptsUsed the value + * @return this builder + */ + public Builder attemptsUsed(long attemptsUsed) { + return set("attempts_used", Conv.LONG, attemptsUsed); + } + + /** + * Present with status: when 0, starting another proof answers 409 and you mint a new widget session. + * + * @param attemptsRemaining the value + * @return this builder + */ + public Builder attemptsRemaining(long attemptsRemaining) { + return set("attempts_remaining", Conv.LONG, attemptsRemaining); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWidgetProofError} + */ + public TravelRuleWidgetProofError build() { + return new TravelRuleWidgetProofError(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * 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). + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "PENDING"}. */ + public static final Status PENDING = new Status("PENDING"); + + /** The value {@code "VERIFIED"}. */ + public static final Status VERIFIED = new Status("VERIFIED"); + + /** The value {@code "REJECTED"}. */ + public static final Status REJECTED = new Status("REJECTED"); + + /** The value {@code "EXPIRED"}. */ + public static final Status EXPIRED = new Status("EXPIRED"); + + private static final List KNOWN_VALUES = List.of(PENDING, VERIFIED, REJECTED, EXPIRED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * Present with status: the closed proof's method. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Method extends StringEnum { + /** The value {@code "MESSAGE_SIGNING"}. */ + public static final Method MESSAGE_SIGNING = new Method("MESSAGE_SIGNING"); + + /** The value {@code "SATOSHI_TEST"}. */ + public static final Method SATOSHI_TEST = new Method("SATOSHI_TEST"); + + /** The value {@code "SCREENSHOT"}. */ + public static final Method SCREENSHOT = new Method("SCREENSHOT"); + + /** The value {@code "SELF_DECLARATION"}. */ + public static final Method SELF_DECLARATION = new Method("SELF_DECLARATION"); + + private static final List KNOWN_VALUES = List.of(MESSAGE_SIGNING, SATOSHI_TEST, SCREENSHOT, SELF_DECLARATION); + + private Method(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Method of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Method::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWidgetProofSummary.java b/src/main/java/me/didit/types/TravelRuleWidgetProofSummary.java index 89684e3..73535f3 100644 --- a/src/main/java/me/didit/types/TravelRuleWidgetProofSummary.java +++ b/src/main/java/me/didit/types/TravelRuleWidgetProofSummary.java @@ -16,7 +16,7 @@ import org.jspecify.annotations.Nullable; public final class TravelRuleWidgetProofSummary extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("proof_id", "method", "status", "wallet_address", "chain", "deposit_address", "expected_amount", "expires_at", "challenge", "rejection_reason", "created_at"); + private static final Set KNOWN_FIELDS = Set.of("proof_id", "method", "status", "wallet_address", "chain", "deposit_address", "expected_amount", "expires_at", "challenge", "rejection_reason", "created_at", "attempts", "failure_reason"); private TravelRuleWidgetProofSummary(ObjectNode json, Map> files) { super(json, files); @@ -72,7 +72,7 @@ public Optional method() { } /** - * PENDING on a SATOSHI_TEST proof means the deposit has been seen on-chain but has not yet reached enough confirmations; a background job automatically rechecks it and moves it to VERIFIED once it confirms, with no resubmission needed. REJECTED means a genuinely wrong deposit was submitted (absent, wrong sender, or wrong amount). EXPIRED means the proof's own test window closed before a valid deposit was seen. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -92,7 +92,7 @@ public Optional walletAddress() { } /** - * The chain field. + * Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -142,7 +142,7 @@ public Optional challenge() { } /** - * Why the proof was rejected. Present only when status is REJECTED. + * 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). * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -161,6 +161,26 @@ public Optional createdAt() { return Json.optional(json, "created_at", Conv.DATE_TIME); } + /** + * How many times evidence was submitted for this proof (signatures, transaction ids, uploads). + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional attempts() { + return Json.optional(json, "attempts", Conv.LONG); + } + + /** + * Why the last attempt fell short, or why the proof closed. Empty when nothing failed. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional failureReason() { + return Json.optional(json, "failure_reason", Conv.enumeration(TravelRuleWalletProofFailureReason::of)); + } + @Override protected Set knownFields() { return KNOWN_FIELDS; @@ -195,7 +215,7 @@ public Builder method(TravelRuleWidgetProofSummary.Method method) { } /** - * PENDING on a SATOSHI_TEST proof means the deposit has been seen on-chain but has not yet reached enough confirmations; a background job automatically rechecks it and moves it to VERIFIED once it confirms, with no resubmission needed. REJECTED means a genuinely wrong deposit was submitted (absent, wrong sender, or wrong amount). EXPIRED means the proof's own test window closed before a valid deposit was seen. + * 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. * * @param status the value * @return this builder @@ -215,7 +235,7 @@ public Builder walletAddress(String walletAddress) { } /** - * The chain field. + * Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX. * * @param chain the value * @return this builder @@ -265,7 +285,7 @@ public Builder challenge(@Nullable String challenge) { } /** - * Why the proof was rejected. Present only when status is REJECTED. + * 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). * * @param rejectionReason the value * @return this builder @@ -284,6 +304,26 @@ public Builder createdAt(OffsetDateTime createdAt) { return set("created_at", Conv.DATE_TIME, createdAt); } + /** + * How many times evidence was submitted for this proof (signatures, transaction ids, uploads). + * + * @param attempts the value + * @return this builder + */ + public Builder attempts(long attempts) { + return set("attempts", Conv.LONG, attempts); + } + + /** + * Why the last attempt fell short, or why the proof closed. Empty when nothing failed. + * + * @param failureReason the value + * @return this builder + */ + public Builder failureReason(TravelRuleWalletProofFailureReason failureReason) { + return set("failure_reason", Conv.enumeration(TravelRuleWalletProofFailureReason::of), failureReason); + } + /** * Builds the value. * @@ -348,7 +388,7 @@ public boolean isKnown() { } /** - * PENDING on a SATOSHI_TEST proof means the deposit has been seen on-chain but has not yet reached enough confirmations; a background job automatically rechecks it and moves it to VERIFIED once it confirms, with no resubmission needed. REJECTED means a genuinely wrong deposit was submitted (absent, wrong sender, or wrong amount). EXPIRED means the proof's own test window closed before a valid deposit was seen. + * 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. * *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. */ diff --git a/src/main/java/me/didit/types/TravelRuleWidgetProofVerifyResult.java b/src/main/java/me/didit/types/TravelRuleWidgetProofVerifyResult.java new file mode 100644 index 0000000..4291b1e --- /dev/null +++ b/src/main/java/me/didit/types/TravelRuleWidgetProofVerifyResult.java @@ -0,0 +1,316 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import me.didit.core.Conv; +import me.didit.core.FileUpload; +import me.didit.core.Json; +import me.didit.core.JsonModel; +import me.didit.core.StringEnum; +import org.jspecify.annotations.Nullable; + +public final class TravelRuleWidgetProofVerifyResult extends JsonModel { + private static final Set KNOWN_FIELDS = Set.of("proof_id", "method", "status", "is_ownership_verified", "transfer_status", "detail", "code"); + + private TravelRuleWidgetProofVerifyResult(ObjectNode json, Map> files) { + super(json, files); + } + + /** + * Reads a {@code TravelRuleWidgetProofVerifyResult} from JSON. Unknown fields are kept and readable + * through {@link #_additionalProperties()}. + * + * @param json a JSON object + * @return the parsed value + */ + public static TravelRuleWidgetProofVerifyResult fromJson(JsonNode json) { + return new TravelRuleWidgetProofVerifyResult(Json.copyObject(json), Map.of()); + } + + /** + * Returns an empty builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a builder holding a copy of every field of this value. + * + * @return a new builder + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * The proof_id field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional proofId() { + return Json.optional(json, "proof_id", Conv.STRING); + } + + /** + * The method field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional method() { + return Json.optional(json, "method", Conv.enumeration(TravelRuleWidgetProofVerifyResult.Method::of)); + } + + /** + * The status field. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional status() { + return Json.optional(json, "status", Conv.enumeration(TravelRuleWidgetProofVerifyResult.Status::of)); + } + + /** + * Present when the proof is VERIFIED: the address book entry is now ownership-verified. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional isOwnershipVerified() { + return Json.optional(json, "is_ownership_verified", Conv.BOOLEAN); + } + + /** + * Present when the proof is VERIFIED: the linked transfer's status, null without one. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional transferStatus() { + return Json.optional(json, "transfer_status", Conv.STRING); + } + + /** + * Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional detail() { + return Json.optional(json, "detail", Conv.STRING); + } + + /** + * Present on a pending test deposit: why it is not confirmed yet. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional code() { + return Json.optional(json, "code", Conv.enumeration(TravelRuleWalletProofFailureReason::of)); + } + + @Override + protected Set knownFields() { + return KNOWN_FIELDS; + } + + /** Builds a {@link TravelRuleWidgetProofVerifyResult}. */ + public static final class Builder extends JsonModel.BaseBuilder { + private Builder() {} + + private Builder(TravelRuleWidgetProofVerifyResult from) { + super(from); + } + + /** + * The proof_id field. + * + * @param proofId the value + * @return this builder + */ + public Builder proofId(String proofId) { + return set("proof_id", Conv.STRING, proofId); + } + + /** + * The method field. + * + * @param method the value + * @return this builder + */ + public Builder method(TravelRuleWidgetProofVerifyResult.Method method) { + return set("method", Conv.enumeration(TravelRuleWidgetProofVerifyResult.Method::of), method); + } + + /** + * The status field. + * + * @param status the value + * @return this builder + */ + public Builder status(TravelRuleWidgetProofVerifyResult.Status status) { + return set("status", Conv.enumeration(TravelRuleWidgetProofVerifyResult.Status::of), status); + } + + /** + * Present when the proof is VERIFIED: the address book entry is now ownership-verified. + * + * @param isOwnershipVerified the value + * @return this builder + */ + public Builder isOwnershipVerified(boolean isOwnershipVerified) { + return set("is_ownership_verified", Conv.BOOLEAN, isOwnershipVerified); + } + + /** + * Present when the proof is VERIFIED: the linked transfer's status, null without one. + * + * @param transferStatus the value, or {@code null} to send JSON null + * @return this builder + */ + public Builder transferStatus(@Nullable String transferStatus) { + return setNullable("transfer_status", Conv.STRING, transferStatus); + } + + /** + * Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet. + * + * @param detail the value + * @return this builder + */ + public Builder detail(String detail) { + return set("detail", Conv.STRING, detail); + } + + /** + * Present on a pending test deposit: why it is not confirmed yet. + * + * @param code the value + * @return this builder + */ + public Builder code(TravelRuleWalletProofFailureReason code) { + return set("code", Conv.enumeration(TravelRuleWalletProofFailureReason::of), code); + } + + /** + * Builds the value. + * + * @return a new immutable {@code TravelRuleWidgetProofVerifyResult} + */ + public TravelRuleWidgetProofVerifyResult build() { + return new TravelRuleWidgetProofVerifyResult(buildJson(), buildFiles()); + } + + @Override + protected Builder self() { + return this; + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Method extends StringEnum { + /** The value {@code "MESSAGE_SIGNING"}. */ + public static final Method MESSAGE_SIGNING = new Method("MESSAGE_SIGNING"); + + /** The value {@code "SATOSHI_TEST"}. */ + public static final Method SATOSHI_TEST = new Method("SATOSHI_TEST"); + + /** The value {@code "SCREENSHOT"}. */ + public static final Method SCREENSHOT = new Method("SCREENSHOT"); + + /** The value {@code "SELF_DECLARATION"}. */ + public static final Method SELF_DECLARATION = new Method("SELF_DECLARATION"); + + private static final List KNOWN_VALUES = List.of(MESSAGE_SIGNING, SATOSHI_TEST, SCREENSHOT, SELF_DECLARATION); + + private Method(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Method of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Method::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + + /** + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class Status extends StringEnum { + /** The value {@code "PENDING"}. */ + public static final Status PENDING = new Status("PENDING"); + + /** The value {@code "VERIFIED"}. */ + public static final Status VERIFIED = new Status("VERIFIED"); + + /** The value {@code "REJECTED"}. */ + public static final Status REJECTED = new Status("REJECTED"); + + /** The value {@code "EXPIRED"}. */ + public static final Status EXPIRED = new Status("EXPIRED"); + + private static final List KNOWN_VALUES = List.of(PENDING, VERIFIED, REJECTED, EXPIRED); + + private Status(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static Status of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, Status::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } +} diff --git a/src/main/java/me/didit/types/TravelRuleWidgetSessionCreate.java b/src/main/java/me/didit/types/TravelRuleWidgetSessionCreate.java index 3d547aa..04f0c20 100644 --- a/src/main/java/me/didit/types/TravelRuleWidgetSessionCreate.java +++ b/src/main/java/me/didit/types/TravelRuleWidgetSessionCreate.java @@ -60,7 +60,7 @@ public Optional walletAddress() { } /** - * 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. + * 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). * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -153,7 +153,7 @@ public Builder walletAddress(String walletAddress) { } /** - * 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. + * 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). * * @param chain the value * @return this builder diff --git a/src/main/java/me/didit/types/WalletAddressEntry.java b/src/main/java/me/didit/types/WalletAddressEntry.java index ebfb91c..4bfdaff 100644 --- a/src/main/java/me/didit/types/WalletAddressEntry.java +++ b/src/main/java/me/didit/types/WalletAddressEntry.java @@ -70,7 +70,7 @@ public Optional address() { } /** - * 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -203,7 +203,7 @@ public Builder address(String address) { } /** - * 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. + * 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. * * @param chain the value * @return this builder diff --git a/src/main/java/me/didit/types/WalletAddressEntryCreate.java b/src/main/java/me/didit/types/WalletAddressEntryCreate.java index 5034fe5..0d0ae62 100644 --- a/src/main/java/me/didit/types/WalletAddressEntryCreate.java +++ b/src/main/java/me/didit/types/WalletAddressEntryCreate.java @@ -59,7 +59,7 @@ public String address() { } /** - * 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. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -152,7 +152,7 @@ public Builder address(String address) { } /** - * 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. + * 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. * * @param chain the value * @return this builder diff --git a/src/main/java/me/didit/types/WebhookConfig.java b/src/main/java/me/didit/types/WebhookConfig.java index a017f0b..10da598 100644 --- a/src/main/java/me/didit/types/WebhookConfig.java +++ b/src/main/java/me/didit/types/WebhookConfig.java @@ -18,7 +18,7 @@ * Application-level webhook configuration, retention policy and Digital ID wallet return hosts, with every webhook destination. */ public final class WebhookConfig extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("webhook_url", "webhook_version", "secret_shared_key", "capture_method", "data_retention_months", "face_retention_policy", "face_retention_days", "available_events", "allowed_return_hosts", "endpoints"); + private static final Set KNOWN_FIELDS = Set.of("webhook_url", "webhook_version", "secret_shared_key", "capture_method", "data_retention_months", "face_retention_policy", "face_retention_days", "biometric_consent_mode", "available_events", "allowed_return_hosts", "endpoints"); private WebhookConfig(ObjectNode json, Map> files) { super(json, files); @@ -123,6 +123,16 @@ public Optional faceRetentionDays() { return Json.optional(json, "face_retention_days", Conv.LONG); } + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional biometricConsentMode() { + return Json.optional(json, "biometric_consent_mode", Conv.enumeration(WebhookConfig.BiometricConsentMode::of)); + } + /** * Every event type a destination can subscribe to, with a display label. * @@ -236,6 +246,16 @@ public Builder faceRetentionDays(@Nullable Long faceRetentionDays) { return setNullable("face_retention_days", Conv.LONG, faceRetentionDays); } + /** + * 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. + * + * @param biometricConsentMode the value + * @return this builder + */ + public Builder biometricConsentMode(WebhookConfig.BiometricConsentMode biometricConsentMode) { + return set("biometric_consent_mode", Conv.enumeration(WebhookConfig.BiometricConsentMode::of), biometricConsentMode); + } + /** * Every event type a destination can subscribe to, with a display label. * @@ -424,6 +444,52 @@ public boolean isKnown() { } } + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class BiometricConsentMode extends StringEnum { + /** The value {@code "auto"}. */ + public static final BiometricConsentMode AUTO = new BiometricConsentMode("auto"); + + /** The value {@code "always"}. */ + public static final BiometricConsentMode ALWAYS = new BiometricConsentMode("always"); + + /** The value {@code "never"}. */ + public static final BiometricConsentMode NEVER = new BiometricConsentMode("never"); + + private static final List KNOWN_VALUES = List.of(AUTO, ALWAYS, NEVER); + + private BiometricConsentMode(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static BiometricConsentMode of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, BiometricConsentMode::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } + public static final class AvailableEvents extends JsonModel { private static final Set KNOWN_FIELDS = Set.of("value", "label"); diff --git a/src/main/java/me/didit/types/WebhooksUpdateConfigParams.java b/src/main/java/me/didit/types/WebhooksUpdateConfigParams.java index c930a06..c948e1b 100644 --- a/src/main/java/me/didit/types/WebhooksUpdateConfigParams.java +++ b/src/main/java/me/didit/types/WebhooksUpdateConfigParams.java @@ -16,7 +16,7 @@ /** Parameters for {@code webhooks.update_config}. */ public final class WebhooksUpdateConfigParams extends JsonModel { - private static final Set KNOWN_FIELDS = Set.of("webhook_url", "webhook_version", "rotate_secret_key", "capture_method", "data_retention_months", "face_retention_policy", "face_retention_days", "allowed_return_hosts"); + private static final Set KNOWN_FIELDS = Set.of("webhook_url", "webhook_version", "rotate_secret_key", "capture_method", "data_retention_months", "face_retention_policy", "face_retention_days", "biometric_consent_mode", "allowed_return_hosts"); private WebhooksUpdateConfigParams(ObjectNode json, Map> files) { super(json, files); @@ -121,6 +121,16 @@ public Optional faceRetentionDays() { return Json.optional(json, "face_retention_days", Conv.LONG); } + /** + * 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. + * + * @return the value, or empty when the field is absent or null + * @throws me.didit.errors.UnexpectedResponseException when the field has another type + */ + public Optional biometricConsentMode() { + return Json.optional(json, "biometric_consent_mode", Conv.enumeration(WebhooksUpdateConfigParams.BiometricConsentMode::of)); + } + /** * 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. * @@ -214,6 +224,16 @@ public Builder faceRetentionDays(@Nullable Long faceRetentionDays) { return setNullable("face_retention_days", Conv.LONG, faceRetentionDays); } + /** + * 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. + * + * @param biometricConsentMode the value + * @return this builder + */ + public Builder biometricConsentMode(WebhooksUpdateConfigParams.BiometricConsentMode biometricConsentMode) { + return set("biometric_consent_mode", Conv.enumeration(WebhooksUpdateConfigParams.BiometricConsentMode::of), biometricConsentMode); + } + /** * 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. * @@ -370,4 +390,50 @@ public boolean isKnown() { return KNOWN_VALUES.contains(this); } } + + /** + * 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. + * + *

An open enum: {@link #of(String)} accepts values this SDK version does not know yet. + */ + public static final class BiometricConsentMode extends StringEnum { + /** The value {@code "auto"}. */ + public static final BiometricConsentMode AUTO = new BiometricConsentMode("auto"); + + /** The value {@code "always"}. */ + public static final BiometricConsentMode ALWAYS = new BiometricConsentMode("always"); + + /** The value {@code "never"}. */ + public static final BiometricConsentMode NEVER = new BiometricConsentMode("never"); + + private static final List KNOWN_VALUES = List.of(AUTO, ALWAYS, NEVER); + + private BiometricConsentMode(String value) { + super(value); + } + + /** + * Returns the constant for a known value, or a new instance for any other value. + * + * @param value the string value + * @return the enum value + */ + public static BiometricConsentMode of(String value) { + return StringEnum.lookup(KNOWN_VALUES, value, BiometricConsentMode::new); + } + + /** + * Returns the values known to this SDK version. + * + * @return the known values + */ + public static List knownValues() { + return KNOWN_VALUES; + } + + @Override + public boolean isKnown() { + return KNOWN_VALUES.contains(this); + } + } } diff --git a/src/main/java/me/didit/types/WorkflowDetail.java b/src/main/java/me/didit/types/WorkflowDetail.java index 15db478..27a5662 100644 --- a/src/main/java/me/didit/types/WorkflowDetail.java +++ b/src/main/java/me/didit/types/WorkflowDetail.java @@ -305,7 +305,7 @@ public Optional updatedAt() { } /** - * Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps and before database validation, AML and IP analysis. Set in the console. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -315,7 +315,7 @@ public Optional isBankVerificationEnabled() { } /** - * Bank verification settings of a simple workflow, with the same keys a BANK_VERIFICATION node takes (for example bank_countries and bank_method_order). 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). + * 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). * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -325,7 +325,7 @@ public Optional> bankVerificationConfig() { } /** - * Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification. Never on KYB workflows. Set in the console. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -335,7 +335,7 @@ public Optional isGeolocationEnabled() { } /** - * 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). Every key is optional and an absent key keeps its default, so {} means all defaults. + * 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. * * @return the value, or empty when the field is absent or null * @throws me.didit.errors.UnexpectedResponseException when the field has another type @@ -608,7 +608,7 @@ public Builder updatedAt(OffsetDateTime updatedAt) { } /** - * Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps and before database validation, AML and IP analysis. Set in the console. + * 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. * * @param isBankVerificationEnabled the value * @return this builder @@ -618,7 +618,7 @@ public Builder isBankVerificationEnabled(boolean isBankVerificationEnabled) { } /** - * Bank verification settings of a simple workflow, with the same keys a BANK_VERIFICATION node takes (for example bank_countries and bank_method_order). 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). + * 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). * * @param bankVerificationConfig the value * @return this builder @@ -628,7 +628,7 @@ public Builder bankVerificationConfig(Map bankVerificationConfig) { } /** - * Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification. Never on KYB workflows. Set in the console. + * 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. * * @param isGeolocationEnabled the value * @return this builder @@ -638,7 +638,7 @@ public Builder isGeolocationEnabled(boolean isGeolocationEnabled) { } /** - * 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). Every key is optional and an absent key keeps its default, so {} means all defaults. + * 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. * * @param geolocationConfig the value * @return this builder diff --git a/src/test/java/me/didit/resources/AddressValidationResourceTest.java b/src/test/java/me/didit/resources/AddressValidationResourceTest.java new file mode 100644 index 0000000..5280c63 --- /dev/null +++ b/src/test/java/me/didit/resources/AddressValidationResourceTest.java @@ -0,0 +1,42 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Map; +import me.didit.Didit; +import me.didit.core.Json; +import me.didit.testing.MockTransport; +import me.didit.testing.RecordedRequest; +import me.didit.testing.SpecExamples; +import me.didit.testing.TestJson; +import me.didit.testing.TestModels; +import me.didit.types.AddressValidationRequest; +import org.junit.jupiter.api.Test; + +/** One test per operation of {@code address_validation}, against a stubbed HTTP layer. */ +@SuppressWarnings({"deprecation", "unused"}) +final class AddressValidationResourceTest { + private final MockTransport transport = new MockTransport(); + private final Didit client = transport.client(); + + @Test + void validate() { + AddressValidationRequest params = AddressValidationRequest.builder().address("value-address").addressLine1("value-address-line-1").addressLine2("value-address-line-2").city("value-city").region("value-region").postalCode("value-postal-code").country("value-country").build(); + JsonNode example = SpecExamples.response("verification.json", "POST", "/v3/address-validation/", "200"); + transport.respondJson(200, example); + var result = client.addressValidation().validate(params); + RecordedRequest request = transport.only(); + assertEquals("POST", request.method()); + assertEquals("/v3/address-validation/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + TestJson.assertJsonEquals("{\"address\":\"value-address\",\"address_line_1\":\"value-address-line-1\",\"address_line_2\":\"value-address-line-2\",\"city\":\"value-city\",\"region\":\"value-region\",\"postal_code\":\"value-postal-code\",\"country\":\"value-country\"}", request.body()); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } +} diff --git a/src/test/java/me/didit/resources/RegulatoryReportsResourceTest.java b/src/test/java/me/didit/resources/RegulatoryReportsResourceTest.java index b84d94e..fadd5eb 100644 --- a/src/test/java/me/didit/resources/RegulatoryReportsResourceTest.java +++ b/src/test/java/me/didit/resources/RegulatoryReportsResourceTest.java @@ -16,6 +16,7 @@ import me.didit.testing.TestJson; import me.didit.testing.TestModels; import me.didit.types.RegulatoryReportsCreateParams; +import me.didit.types.RegulatoryReportsDeleteParams; import me.didit.types.RegulatoryReportsListParams; import me.didit.types.RegulatoryReportsRetrieveDownloadUrlParams; import me.didit.types.RegulatoryReportsUpdateParams; @@ -97,15 +98,16 @@ void update() { @Test void delete() { + RegulatoryReportsDeleteParams params = RegulatoryReportsDeleteParams.builder().confirmDelete(true).build(); transport.respond(204, ""); - client.regulatoryReports().delete("00000000-0000-4000-8000-00000001161b"); + client.regulatoryReports().delete("00000000-0000-4000-8000-00000001161b", params); RecordedRequest request = transport.only(); assertEquals("DELETE", request.method()); assertEquals("/v3/organization/00000000-0000-4000-8000-0000000000a1/application/00000000-0000-4000-8000-0000000000a2/regulatory-reports/00000000-0000-4000-8000-00000001161b/", request.path()); assertEquals(Map.of(), request.query()); assertEquals("test-api-key", request.header("x-api-key")); assertEquals("https://verification.didit.me", request.origin()); - assertEquals(0, request.body().length); + TestJson.assertJsonEquals("{\"confirm_delete\":true}", request.body()); } @Test diff --git a/src/test/java/me/didit/resources/SessionsResourceTest.java b/src/test/java/me/didit/resources/SessionsResourceTest.java index 954d892..9196855 100644 --- a/src/test/java/me/didit/resources/SessionsResourceTest.java +++ b/src/test/java/me/didit/resources/SessionsResourceTest.java @@ -53,7 +53,7 @@ final class SessionsResourceTest { @Test void create() { - SessionsCreateParams params = SessionsCreateParams.builder().workflowId("00000000-0000-4000-8000-000000001b5b").vendorData("value-vendor-data").callback("value-callback").callbackMethod(SessionsCreateParams.CallbackMethod.INITIATOR).metadata("value").language(SessionsCreateParams.Language.EN).contactDetails(SessionsCreateParams.ContactDetails.builder().email("user@example.com").sendNotificationEmails(true).build()).expectedDetails(SessionsCreateParams.ExpectedDetails.builder().firstName("value-first-name").lastName("value-last-name").build()).portraitImage("value-portrait-image").sandboxScenario("value-sandbox-scenario").sign(SessionsCreateParams.Sign.builder().documents(List.of(SessionSignDocument.builder().title("value-title").fileName("value-file-name").contentBase64("value-content-base64").fields(List.of(SignatureField.builder().key("value-key").type(SignatureField.Type.SIGNATURE).role(SignatureField.Role.VERIFIED_PERSON).page(1L).build())).build())).fieldValues(Map.of("key", SessionsCreateParams.Sign.FieldValuesValue.ofString("value-field-values"))).build()).build(); + SessionsCreateParams params = SessionsCreateParams.builder().workflowId("00000000-0000-4000-8000-000000001b5b").vendorData("value-vendor-data").callback("value-callback").callbackMethod(SessionsCreateParams.CallbackMethod.INITIATOR).metadata("value").tags(List.of("value-tags")).language(SessionsCreateParams.Language.EN).contactDetails(SessionsCreateParams.ContactDetails.builder().email("user@example.com").sendNotificationEmails(true).build()).expectedDetails(SessionsCreateParams.ExpectedDetails.builder().firstName("value-first-name").lastName("value-last-name").build()).portraitImage("value-portrait-image").sandboxScenario("value-sandbox-scenario").sign(SessionsCreateParams.Sign.builder().documents(List.of(SessionSignDocument.builder().title("value-title").fileName("value-file-name").contentBase64("value-content-base64").fields(List.of(SignatureField.builder().key("value-key").type(SignatureField.Type.SIGNATURE).role(SignatureField.Role.VERIFIED_PERSON).page(1L).build())).build())).fieldValues(Map.of("key", SessionsCreateParams.Sign.FieldValuesValue.ofString("value-field-values"))).build()).userState("value-user-state").biometricConsent(SessionsCreateParams.BiometricConsent.builder().givenAt(OffsetDateTime.parse("2026-11-01T10:15:30Z")).textVersion("value-text-version").build()).build(); JsonNode example = SpecExamples.response("verification.json", "POST", "/v3/session/", "201"); transport.respondJson(201, example); var result = client.sessions().create(params); @@ -63,7 +63,7 @@ void create() { assertEquals(Map.of(), request.query()); assertEquals("test-api-key", request.header("x-api-key")); assertEquals("https://verification.didit.me", request.origin()); - TestJson.assertJsonEquals("{\"workflow_id\":\"00000000-0000-4000-8000-000000001b5b\",\"vendor_data\":\"value-vendor-data\",\"callback\":\"value-callback\",\"callback_method\":\"initiator\",\"metadata\":\"value\",\"language\":\"en\",\"contact_details\":{\"email\":\"user@example.com\",\"send_notification_emails\":true},\"expected_details\":{\"first_name\":\"value-first-name\",\"last_name\":\"value-last-name\"},\"portrait_image\":\"value-portrait-image\",\"sandbox_scenario\":\"value-sandbox-scenario\",\"sign\":{\"documents\":[{\"title\":\"value-title\",\"file_name\":\"value-file-name\",\"content_base64\":\"value-content-base64\",\"fields\":[{\"key\":\"value-key\",\"type\":\"signature\",\"role\":\"verified_person\",\"page\":1}]}],\"field_values\":{\"key\":\"value-field-values\"}}}", request.body()); + TestJson.assertJsonEquals("{\"workflow_id\":\"00000000-0000-4000-8000-000000001b5b\",\"vendor_data\":\"value-vendor-data\",\"callback\":\"value-callback\",\"callback_method\":\"initiator\",\"metadata\":\"value\",\"tags\":[\"value-tags\"],\"language\":\"en\",\"contact_details\":{\"email\":\"user@example.com\",\"send_notification_emails\":true},\"expected_details\":{\"first_name\":\"value-first-name\",\"last_name\":\"value-last-name\"},\"portrait_image\":\"value-portrait-image\",\"sandbox_scenario\":\"value-sandbox-scenario\",\"sign\":{\"documents\":[{\"title\":\"value-title\",\"file_name\":\"value-file-name\",\"content_base64\":\"value-content-base64\",\"fields\":[{\"key\":\"value-key\",\"type\":\"signature\",\"role\":\"verified_person\",\"page\":1}]}],\"field_values\":{\"key\":\"value-field-values\"}},\"user_state\":\"value-user-state\",\"biometric_consent\":{\"given_at\":\"2026-11-01T10:15:30Z\",\"text_version\":\"value-text-version\"}}", request.body()); TestJson.assertJsonEquals(example, Json.toNode(result)); TestModels.readAll(result); } diff --git a/src/test/java/me/didit/resources/TransactionsResourceTest.java b/src/test/java/me/didit/resources/TransactionsResourceTest.java index 71e8cd9..dcfeb0d 100644 --- a/src/test/java/me/didit/resources/TransactionsResourceTest.java +++ b/src/test/java/me/didit/resources/TransactionsResourceTest.java @@ -15,6 +15,7 @@ import me.didit.testing.SpecExamples; import me.didit.testing.TestJson; import me.didit.testing.TestModels; +import me.didit.types.TransactionsBatchDeleteParams; import me.didit.types.TransactionsCreateParams; import me.didit.types.TransactionsCreateSdkTokenParams; import me.didit.types.TransactionsListParams; @@ -159,4 +160,18 @@ void generatePdf() { assertEquals(0, request.body().length); assertArrayEquals(new byte[] {37, 80, 68, 70}, result.bytes()); } + + @Test + void batchDelete() { + TransactionsBatchDeleteParams params = TransactionsBatchDeleteParams.builder().build(); + transport.respond(204, ""); + client.transactions().batchDelete(params); + RecordedRequest request = transport.only(); + assertEquals("DELETE", request.method()); + assertEquals("/v3/organization/00000000-0000-4000-8000-0000000000a1/application/00000000-0000-4000-8000-0000000000a2/transactions/delete/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + TestJson.assertJsonEquals("{}", request.body()); + } } diff --git a/src/test/java/me/didit/resources/TransactionsTravelRuleResourceTest.java b/src/test/java/me/didit/resources/TransactionsTravelRuleResourceTest.java index 2cca420..9c49a2a 100644 --- a/src/test/java/me/didit/resources/TransactionsTravelRuleResourceTest.java +++ b/src/test/java/me/didit/resources/TransactionsTravelRuleResourceTest.java @@ -28,7 +28,7 @@ final class TransactionsTravelRuleResourceTest { @Test void updateTransfer() { - TravelRuleTransferPatchRequest params = TravelRuleTransferPatchRequest.builder().paymentTxnId("value-payment-txn-id").action(TravelRuleTransferPatchRequest.Action.CANCEL).complianceEmail("user@example.com").walletAddress("value-wallet-address").memo("value-memo").build(); + TravelRuleTransferPatchRequest params = TravelRuleTransferPatchRequest.builder().paymentTxnId("value-payment-txn-id").action(TravelRuleTransferPatchRequest.Action.CANCEL).complianceEmail("user@example.com").walletAddress("value-wallet-address").memo("value-memo").beneficiaryVasp("00000000-0000-4000-8000-000000003908").build(); JsonNode example = TestJson.parse("{}"); transport.respondJson(200, example); var result = client.transactions().travelRule().updateTransfer("00000000-0000-4000-8000-00000002630a", params); @@ -38,7 +38,7 @@ void updateTransfer() { assertEquals(Map.of(), request.query()); assertEquals("test-api-key", request.header("x-api-key")); assertEquals("https://verification.didit.me", request.origin()); - TestJson.assertJsonEquals("{\"payment_txn_id\":\"value-payment-txn-id\",\"action\":\"cancel\",\"compliance_email\":\"user@example.com\",\"wallet_address\":\"value-wallet-address\",\"memo\":\"value-memo\"}", request.body()); + TestJson.assertJsonEquals("{\"payment_txn_id\":\"value-payment-txn-id\",\"action\":\"cancel\",\"compliance_email\":\"user@example.com\",\"wallet_address\":\"value-wallet-address\",\"memo\":\"value-memo\",\"beneficiary_vasp\":\"00000000-0000-4000-8000-000000003908\"}", request.body()); TestJson.assertJsonEquals(example, Json.toNode(result)); TestModels.readAll(result); } diff --git a/src/test/java/me/didit/resources/TravelRuleNetworksResourceTest.java b/src/test/java/me/didit/resources/TravelRuleNetworksResourceTest.java new file mode 100644 index 0000000..2771992 --- /dev/null +++ b/src/test/java/me/didit/resources/TravelRuleNetworksResourceTest.java @@ -0,0 +1,106 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Map; +import me.didit.Didit; +import me.didit.core.Json; +import me.didit.testing.MockTransport; +import me.didit.testing.RecordedRequest; +import me.didit.testing.SpecExamples; +import me.didit.testing.TestJson; +import me.didit.testing.TestModels; +import me.didit.types.TravelRuleNetworkQuestionnaireAnswers; +import org.junit.jupiter.api.Test; + +/** One test per operation of {@code travel_rule.networks}, against a stubbed HTTP layer. */ +@SuppressWarnings({"deprecation", "unused"}) +final class TravelRuleNetworksResourceTest { + private final MockTransport transport = new MockTransport(); + private final Didit client = transport.client(); + + @Test + void retrieve() { + JsonNode example = SpecExamples.response("verification.json", "GET", "/v3/travel-rule/networks/", "200"); + transport.respondJson(200, example); + var result = client.travelRule().networks().retrieve(); + RecordedRequest request = transport.only(); + assertEquals("GET", request.method()); + assertEquals("/v3/travel-rule/networks/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + assertEquals(0, request.body().length); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } + + @Test + void retrieveQuestionnaire() { + JsonNode example = TestJson.parse("{}"); + transport.respondJson(200, example); + var result = client.travelRule().networks().retrieveQuestionnaire(); + RecordedRequest request = transport.only(); + assertEquals("GET", request.method()); + assertEquals("/v3/travel-rule/networks/questionnaire/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + assertEquals(0, request.body().length); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } + + @Test + void updateQuestionnaire() { + TravelRuleNetworkQuestionnaireAnswers params = TravelRuleNetworkQuestionnaireAnswers.builder().legalName("value-legal-name").registrationNumber("value-registration-number").incorporationCountry("value-incorporation-country").registeredAddress("value-registered-address").website("https://example.com/callback").lei("value-lei").licences(List.of(TravelRuleNetworkQuestionnaireAnswers.Licences.builder().regulator("value-regulator").jurisdiction("value-jurisdiction").licenceType("value-licence-type").licenceNumber("value-licence-number").build())).operatingJurisdictions(List.of("value-operating-jurisdictions")).complianceOfficerName("value-compliance-officer-name").complianceOfficerEmail("user@example.com").amlCtfPolicyAttested(true).sanctionsScreeningAttested(true).dataProtectionAttested(true).dataProtectionContactEmail("user@example.com").expectedMonthlyTransfers(1L).expectedMonthlyVolumeUsd("value-expected-monthly-volume-usd").assets(List.of("value-assets")).build(); + JsonNode example = TestJson.parse("{}"); + transport.respondJson(200, example); + var result = client.travelRule().networks().updateQuestionnaire(params); + RecordedRequest request = transport.only(); + assertEquals("PUT", request.method()); + assertEquals("/v3/travel-rule/networks/questionnaire/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + TestJson.assertJsonEquals("{\"legal_name\":\"value-legal-name\",\"registration_number\":\"value-registration-number\",\"incorporation_country\":\"value-incorporation-country\",\"registered_address\":\"value-registered-address\",\"website\":\"https://example.com/callback\",\"lei\":\"value-lei\",\"licences\":[{\"regulator\":\"value-regulator\",\"jurisdiction\":\"value-jurisdiction\",\"licence_type\":\"value-licence-type\",\"licence_number\":\"value-licence-number\"}],\"operating_jurisdictions\":[\"value-operating-jurisdictions\"],\"compliance_officer_name\":\"value-compliance-officer-name\",\"compliance_officer_email\":\"user@example.com\",\"aml_ctf_policy_attested\":true,\"sanctions_screening_attested\":true,\"data_protection_attested\":true,\"data_protection_contact_email\":\"user@example.com\",\"expected_monthly_transfers\":1,\"expected_monthly_volume_usd\":\"value-expected-monthly-volume-usd\",\"assets\":[\"value-assets\"]}", request.body()); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } + + @Test + void submitQuestionnaire() { + JsonNode example = TestJson.parse("{}"); + transport.respondJson(200, example); + var result = client.travelRule().networks().submitQuestionnaire(); + RecordedRequest request = transport.only(); + assertEquals("POST", request.method()); + assertEquals("/v3/travel-rule/networks/questionnaire/submit/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + assertEquals(0, request.body().length); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } + + @Test + void requestAccess() { + JsonNode example = TestJson.parse("{}"); + transport.respondJson(200, example); + var result = client.travelRule().networks().requestAccess("00000000-0000-4000-8000-00000001784a"); + RecordedRequest request = transport.only(); + assertEquals("POST", request.method()); + assertEquals("/v3/travel-rule/networks/00000000-0000-4000-8000-00000001784a/request/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + assertEquals(0, request.body().length); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } +} diff --git a/src/test/java/me/didit/resources/TravelRuleWalletProofsResourceTest.java b/src/test/java/me/didit/resources/TravelRuleWalletProofsResourceTest.java new file mode 100644 index 0000000..b81c8e9 --- /dev/null +++ b/src/test/java/me/didit/resources/TravelRuleWalletProofsResourceTest.java @@ -0,0 +1,61 @@ +// Code generated by codegen from openapi/*.json. DO NOT EDIT. +package me.didit.resources; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Map; +import me.didit.Didit; +import me.didit.core.Json; +import me.didit.testing.MockTransport; +import me.didit.testing.RecordedRequest; +import me.didit.testing.SpecExamples; +import me.didit.testing.TestJson; +import me.didit.testing.TestModels; +import me.didit.types.TravelRuleWalletProofDecision; +import me.didit.types.TravelRuleWalletProofsListParams; +import org.junit.jupiter.api.Test; + +/** One test per operation of {@code travel_rule.wallet_proofs}, against a stubbed HTTP layer. */ +@SuppressWarnings({"deprecation", "unused"}) +final class TravelRuleWalletProofsResourceTest { + private final MockTransport transport = new MockTransport(); + private final Didit client = transport.client(); + + @Test + void list() { + TravelRuleWalletProofsListParams params = TravelRuleWalletProofsListParams.builder().status(TravelRuleWalletProofsListParams.Status.PENDING).limit(1L).offset(1L).build(); + JsonNode example = TestJson.parse("{\"count\":0,\"next\":null,\"previous\":null,\"results\":[]}"); + transport.respondJson(200, example); + var result = client.travelRule().walletProofs().list(params); + RecordedRequest request = transport.only(); + assertEquals("GET", request.method()); + assertEquals("/v3/travel-rule/wallet-proofs/", request.path()); + assertEquals(Map.ofEntries(Map.entry("status", List.of("PENDING")), Map.entry("limit", List.of("1")), Map.entry("offset", List.of("1"))), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + assertEquals(0, request.body().length); + TestJson.assertJsonEquals(example, result._json()); + assertEquals(example.path("results").size(), result.results().size()); + TestModels.readAll(result.results()); + } + + @Test + void decide() { + TravelRuleWalletProofDecision params = TravelRuleWalletProofDecision.builder().decision(TravelRuleWalletProofDecision.Decision.APPROVE).note("value-note").build(); + JsonNode example = TestJson.parse("{}"); + transport.respondJson(200, example); + var result = client.travelRule().walletProofs().decide("00000000-0000-4000-8000-000000016f9e", params); + RecordedRequest request = transport.only(); + assertEquals("POST", request.method()); + assertEquals("/v3/travel-rule/wallet-proofs/00000000-0000-4000-8000-000000016f9e/decision/", request.path()); + assertEquals(Map.of(), request.query()); + assertEquals("test-api-key", request.header("x-api-key")); + assertEquals("https://verification.didit.me", request.origin()); + TestJson.assertJsonEquals("{\"decision\":\"approve\",\"note\":\"value-note\"}", request.body()); + TestJson.assertJsonEquals(example, Json.toNode(result)); + TestModels.readAll(result); + } +} diff --git a/src/test/java/me/didit/resources/WebhooksResourceTest.java b/src/test/java/me/didit/resources/WebhooksResourceTest.java index 2846e0b..c59c9e4 100644 --- a/src/test/java/me/didit/resources/WebhooksResourceTest.java +++ b/src/test/java/me/didit/resources/WebhooksResourceTest.java @@ -41,7 +41,7 @@ void retrieveConfig() { @Test void updateConfig() { - WebhooksUpdateConfigParams params = WebhooksUpdateConfigParams.builder().webhookUrl("https://example.com/callback").webhookVersion(WebhooksUpdateConfigParams.WebhookVersion.V_1).rotateSecretKey(true).captureMethod(WebhooksUpdateConfigParams.CaptureMethod.MOBILE).dataRetentionMonths(1L).faceRetentionPolicy(WebhooksUpdateConfigParams.FaceRetentionPolicy.DELETE_WITH_SESSION).faceRetentionDays(1L).allowedReturnHosts(List.of("value-allowed-return-hosts")).build(); + WebhooksUpdateConfigParams params = WebhooksUpdateConfigParams.builder().webhookUrl("https://example.com/callback").webhookVersion(WebhooksUpdateConfigParams.WebhookVersion.V_1).rotateSecretKey(true).captureMethod(WebhooksUpdateConfigParams.CaptureMethod.MOBILE).dataRetentionMonths(1L).faceRetentionPolicy(WebhooksUpdateConfigParams.FaceRetentionPolicy.DELETE_WITH_SESSION).faceRetentionDays(1L).biometricConsentMode(WebhooksUpdateConfigParams.BiometricConsentMode.AUTO).allowedReturnHosts(List.of("value-allowed-return-hosts")).build(); JsonNode example = SpecExamples.response("verification.json", "PATCH", "/v3/webhook/", "200"); transport.respondJson(200, example); var result = client.webhooks().updateConfig(params); @@ -51,7 +51,7 @@ void updateConfig() { assertEquals(Map.of(), request.query()); assertEquals("test-api-key", request.header("x-api-key")); assertEquals("https://verification.didit.me", request.origin()); - TestJson.assertJsonEquals("{\"webhook_url\":\"https://example.com/callback\",\"webhook_version\":\"v1\",\"rotate_secret_key\":true,\"capture_method\":\"MOBILE\",\"data_retention_months\":1,\"face_retention_policy\":\"delete_with_session\",\"face_retention_days\":1,\"allowed_return_hosts\":[\"value-allowed-return-hosts\"]}", request.body()); + TestJson.assertJsonEquals("{\"webhook_url\":\"https://example.com/callback\",\"webhook_version\":\"v1\",\"rotate_secret_key\":true,\"capture_method\":\"MOBILE\",\"data_retention_months\":1,\"face_retention_policy\":\"delete_with_session\",\"face_retention_days\":1,\"biometric_consent_mode\":\"auto\",\"allowed_return_hosts\":[\"value-allowed-return-hosts\"]}", request.body()); TestJson.assertJsonEquals(example, Json.toNode(result)); TestModels.readAll(result); }