diff --git a/README.md b/README.md index 69bc901..92d5665 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ client.cases.list() # application scoped, see Configuration client.account.login(email="you@example.com", password="...") ``` -The resource tree covers sessions (with imports and reviews), the standalone APIs (`id_verification`, `face_match`, `face_search`, `passive_liveness`, `age_estimation`, `proof_of_address`, `document_ai`, `aml`, `database_validation`, `phone`, `email`, `ip`, `kyb`), `id_wallets`, `fraud_checks`, `users`, `businesses`, `transactions`, `wallet_screening`, `travel_rule`, `workflows`, `questionnaires`, `webhooks.destinations`, `billing`, `biometric_templates`, `customization`, `cases`, `regulatory_reports`, `report_templates`, `networks` and `account`. The API reference at [docs.didit.me](https://docs.didit.me) documents every endpoint; each SDK method's docstring names the endpoint it calls. +The resource tree covers sessions (with imports and reviews), the standalone APIs (`id_verification`, `face_match`, `face_search`, `passive_liveness`, `age_estimation`, `proof_of_address`, `document_ai`, `aml`, `database_validation`, `phone`, `email`, `ip`, `kyb`), `id_wallets`, `fraud_checks`, `users`, `businesses`, `transactions`, `wallet_screening`, `travel_rule`, `workflows`, `questionnaires`, `webhooks.destinations`, `billing`, `pricing`, `biometric_templates`, `customization`, `cases`, `regulatory_reports`, `report_templates`, `networks` and `account`. The API reference at [docs.didit.me](https://docs.didit.me) documents every endpoint; each SDK method's docstring names the endpoint it calls. Request and response types live in `didit.types`, named after the method (`SessionsCreateResponse`, `SessionsCreateParams`) or after the API schema (`TransactionDetail`). Responses are [pydantic](https://docs.pydantic.dev) models: diff --git a/codegen/didit_codegen/render_tests.py b/codegen/didit_codegen/render_tests.py index 8459698..740c6bd 100644 --- a/codegen/didit_codegen/render_tests.py +++ b/codegen/didit_codegen/render_tests.py @@ -214,6 +214,7 @@ def conforms(spec: Spec, schema: Json, value: Json, *, request: bool, depth: int _SECRET_KEY = re.compile(r"token|secret|api_?key|password|private_?key|credential", re.IGNORECASE) +_TIMESTAMP = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})") _JWT = re.compile(r"^eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$") @@ -227,7 +228,12 @@ def scrub(value: Json, key: str | None = None) -> Json: if isinstance(value, str): if _JWT.match(value): return "sample-token" - if key is not None and _SECRET_KEY.search(key) and value: + if ( + key is not None + and _SECRET_KEY.search(key) + and value + and not _TIMESTAMP.fullmatch(value) + ): return "sample-" + ("token" if "token" in key.lower() else "secret") return value diff --git a/codegen/tests/test_codegen.py b/codegen/tests/test_codegen.py index f3b6bee..57b36c9 100644 --- a/codegen/tests/test_codegen.py +++ b/codegen/tests/test_codegen.py @@ -621,3 +621,24 @@ def test_cli_main_from_dir( assert spec_sync.main(["--from-dir", str(openapi), "--summary", str(summary)]) == 0 assert "changed=false" in output.read_text("utf-8") assert summary.read_text("utf-8") == "No upstream change.\n" + + +def test_scrub_keeps_timestamps_and_replaces_secrets() -> None: + from didit_codegen.render_tests import scrub + + got = scrub( + { + "credentials_expires_at": "2026-10-27T10:00:00Z", + "api_key": "abc", + "password": "2026-10-27-secret", + "token_date": "2026-10-27", + "api_key_at": "2026-10-27T10:00", + } + ) + assert got == { + "credentials_expires_at": "2026-10-27T10:00:00Z", + "api_key": "sample-secret", + "password": "sample-secret", + "token_date": "sample-token", + "api_key_at": "sample-secret", + } diff --git a/openapi/SOURCE.json b/openapi/SOURCE.json index b93e86e..b72340a 100644 --- a/openapi/SOURCE.json +++ b/openapi/SOURCE.json @@ -1,13 +1,13 @@ { "verification.json": { "url": "https://docs.didit.me/openapi-25.json", - "sha256": "6157d174fff4e0093ca7ac8e0f064ffc777d1cfa4a7eeb2b0e88fbf86f4a060b", - "fetched_at": "2026-10-26T07:02:07Z" + "sha256": "b2658eb22e23f868d2df5fe513c8dcdadce3abf2e35d1bbebce0382c911038db", + "fetched_at": "2026-10-27T06:49:57Z" }, "account.json": { "url": "https://docs.didit.me/openapi-auth.json", "sha256": "5fafb88b306ab60c7c105b0bf00fa2a6a49b5d2ca8115eb003b332f073844b39", - "fetched_at": "2026-10-26T07:02:07Z" + "fetched_at": "2026-10-27T06:49:57Z" }, "webhook-vectors.json": { "generator": "product-automations scripts/server-sdks/make_vectors.py (seeded 2026-10-17)", diff --git a/openapi/verification.json b/openapi/verification.json index 0236ac8..1125811 100644 --- a/openapi/verification.json +++ b/openapi/verification.json @@ -1509,7 +1509,7 @@ "/v3/session/imports/": { "post": { "summary": "Create a verification import job", - "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Use this to migrate from providers such as Sumsub, MetaMap, Veriff, Onfido, Persona, Trulioo, Jumio, Incode, iDenfy, or any other provider after transforming the export into Didit's canonical CSV/NDJSON format. Imports are disabled by default and must be enabled per organization by Didit.", + "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit. Imports are disabled by default and must be enabled per organization by Didit.", "operationId": "post_v3_session_imports_create", "x-didit-sdk": { "resource": "sessions.imports", @@ -1560,6 +1560,81 @@ "type": "string", "format": "uri", "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." } } } @@ -1599,6 +1674,81 @@ "type": "string", "format": "uri", "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." } } }, @@ -1650,7 +1800,7 @@ } }, "400": { - "description": "Invalid workflow, file type, file size, source URL, or selector combination.", + "description": "Invalid workflow, file type, file size, source URL, or selector combination. A migration whose key the provider refuses answers `code` (`invalid_credentials`, `missing_permission`, `wrong_region`, `provider_unavailable`, `rate_limited`, `ids_required`, `record_not_found`), `missing_permissions` and `suggested_region`.", "content": { "application/json": { "examples": { @@ -68276,217 +68426,118 @@ } } }, - "/v3/session/{sessionId}/simulate/": { - "post": { - "summary": "Simulate a sandbox session's status", - "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", - "operationId": "post_v3_session_simulate", + "/v3/organization/bank-verification-pricing/": { + "get": { + "summary": "Get bank verification prices (public, unauthenticated)", + "description": "Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed.\n\nA method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet.\n\nThese are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization.", + "operationId": "get_v3_bank_verification_pricing", "tags": [ - "Sessions" + "Pricing" ], "security": [ - { - "ApiKeyAuth": [] - } + {} ], - "parameters": [ + "parameters": [], + "x-codeSamples": [ { - "in": "path", - "name": "sessionId", - "required": true, - "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", - "schema": { - "type": "string", - "format": "uuid", - "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" - } + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/organization/bank-verification-pricing/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/organization/bank-verification-pricing/', timeout=10)\nresp.raise_for_status()\nfeed = resp.json()\nfor iso3, country in feed['countries'].items():\n sign_in = country['methods']['open_banking']\n if sign_in['available']:\n print(iso3, country['name'], sign_in['price_usd'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/organization/bank-verification-pricing/').then((r) => r.json());\nconst gbr = feed.countries.GBR.methods;\nconsole.log(gbr.open_banking.available ? gbr.open_banking.price_usd : 'Not available');\nconsole.log(gbr.account_lookup.available ? gbr.account_lookup.price_usd : 'Not available');" } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxSimulateRequest" - }, - "examples": { - "Scenario Without Webhooks": { - "summary": "Decline through a scenario without sending webhooks", - "value": { - "scenario": "decline_aml_hit", - "send_webhooks": false - } - }, - "Status Only": { - "summary": "Approve and send the usual webhooks", - "value": { - "new_status": "Approved" - } - } - } - } - } + "x-didit-sdk": { + "resource": "pricing", + "method": "bank_verification", + "auth": "none", + "server": true }, "responses": { "200": { - "description": "The status changed.", + "description": "Bank Verification list prices by country.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboxSimulateResponse" + "$ref": "#/components/schemas/BankVerificationPricing" }, "examples": { - "Simulated": { - "summary": "Declined through a scenario, webhooks not sent", - "value": { - "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "previous_status": "In Progress", - "status": "Declined", - "scenario": "decline_aml_hit", - "risk": "POSSIBLE_MATCH_FOUND", - "send_webhooks": false - } - } - } - } - } - }, - "400": { - "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", - "content": { - "application/json": { - "examples": { - "Unknown Scenario": { - "summary": "scenario is not in the catalog", - "value": { - "scenario": [ - "Unknown sandbox scenario: 'not-a-scenario'." - ] - } - }, - "Unknown Risk": { - "summary": "risk is not in the catalog", - "value": { - "risk": [ - "Unknown sandbox risk case: 'NOT_A_RISK'." - ] - } - }, - "Missing new_status": { - "summary": "No new_status, scenario or risk", - "value": { - "new_status": [ - "This field is required." - ] - } - }, - "Invalid new_status": { - "summary": "A status the simulation does not set", - "value": { - "new_status": [ - "\"Resubmitted\" is not a valid choice." - ] - } - }, - "Same Status": { - "summary": "The session already has this status", - "value": [ - "The new status is the same as the current status." - ] - } - } - } - } - }, - "401": { - "description": "The scoped API key has expired.", - "content": { - "application/json": { - "examples": { - "Expired API Key": { - "summary": "Scoped API key past its expiry date", + "Two countries": { + "summary": "One country with both methods, one with neither", "value": { - "detail": "This API key has expired." - } - } - } - } - } - }, - "403": { - "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", - "content": { - "application/json": { - "examples": { - "Invalid Credentials": { - "summary": "Missing or invalid x-api-key", - "value": { - "detail": "Authentication credentials were not provided or are invalid." - } - }, - "Missing Privilege": { - "summary": "Caller lacks write:sessions", - "value": { - "detail": "You do not have permission to perform this action." - } - }, - "Scoped Key Without Access": { - "summary": "Scoped API key without this access", - "value": { - "detail": "This API key has no write access to sessions." - } - } - } - } - } - }, - "404": { - "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", - "content": { - "application/json": { - "examples": { - "Not Found": { - "summary": "Unknown or live session", - "value": { - "detail": "Not found." + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": true, + "standalone_available": false + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": true, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": { + "balance": "0.3500", + "transactions": "1.3000" + } + }, + "account_lookup": { + "available": true, + "price_usd": "0.4000" + } + } + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": false, + "market": null, + "price_usd": null, + "add_ons": {} + }, + "account_lookup": { + "available": false, + "price_usd": null + } + } + } + } } } } } } - } - }, - "x-codeSamples": [ - { - "lang": "curl", - "label": "curl", - "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" }, - { - "lang": "python", - "label": "Python", - "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"scenario\": \"decline_aml_hit\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + "304": { + "description": "Not modified: the `ETag` you sent in `If-None-Match` still matches. No body." } - ], - "x-didit-sdk": { - "resource": "sessions", - "method": "simulate", - "auth": "api_key", - "server": true } } }, - "/v3/session/{sessionId}/sandbox/simulate/": { + "/v3/session/{sessionId}/simulate/": { "post": { - "summary": "Simulate a sandbox session's status from the client", - "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", - "operationId": "post_v3_session_sandbox_simulate", + "summary": "Simulate a sandbox session's status", + "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", + "operationId": "post_v3_session_simulate", "tags": [ "Sessions" ], "security": [ { - "SessionTokenAuth": [] + "ApiKeyAuth": [] } ], "parameters": [ @@ -68494,7 +68545,207 @@ "in": "path", "name": "sessionId", "required": true, - "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", + "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown or live session", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"scenario\": \"decline_aml_hit\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/sandbox/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status from the client", + "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", + "operationId": "post_v3_session_sandbox_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", "schema": { "type": "string", "format": "uuid", @@ -72665,6 +72916,717 @@ } } } + }, + "/v3/session/imports/providers/": { + "get": { + "summary": "List migration providers", + "description": "The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping.", + "operationId": "get_v3_session_imports_providers_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_providers", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Migration providers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportProvider" + } + }, + "mapping_targets": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_statuses": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/providers/check/": { + "post": { + "summary": "Check a provider key", + "description": "Read the provider with your key before creating a migration. Nothing is stored.", + "operationId": "post_v3_session_imports_providers_check", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "check_provider", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "record_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For `api_ids` providers: the first record ID of your export." + } + }, + "required": [ + "provider", + "credentials" + ] + } + } + } + }, + "responses": { + "200": { + "description": "The key can read records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "provider": { + "type": "string" + }, + "credential_hint": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "The provider refused the key: `code`, `detail`, `missing_permissions`, `suggested_region`." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/start/": { + "post": { + "summary": "Start a dry run", + "description": "Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now.", + "operationId": "post_v3_session_imports_start", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "start", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/pause/": { + "post": { + "summary": "Pause an import job", + "description": "Pause a queued or running import.", + "operationId": "post_v3_session_imports_pause", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "pause", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/resume/": { + "post": { + "summary": "Resume an import job", + "description": "Resume an import you paused. A job paused by the provider's rate limit resumes by itself.", + "operationId": "post_v3_session_imports_resume", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "resume", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/cancel/": { + "post": { + "summary": "Cancel an import job", + "description": "Cancel an import. What was imported stays; the uploaded file and the key are erased.", + "operationId": "post_v3_session_imports_cancel", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "cancel", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/retry/": { + "post": { + "summary": "Retry a migration", + "description": "Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped.", + "operationId": "post_v3_session_imports_retry", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retry", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "file": { + "type": "string", + "format": "binary", + "description": "The record IDs or export file again, for `api_ids` and `export_file` providers." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The new import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "Not a migration, not ended, or the provider refused the key." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/import-jobs/": { + "get": { + "summary": "List import jobs", + "description": "Every import of the application in one shape: verification imports and migrations, people and businesses.", + "operationId": "get_v3_import_jobs_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "sessions", + "users", + "businesses" + ], + "default": "all" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Import jobs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "counts": { + "type": "object", + "properties": { + "all": { + "type": "integer" + }, + "running_and_waiting": { + "type": "integer" + }, + "needs_attention": { + "type": "integer" + }, + "done": { + "type": "integer" + } + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/import-jobs/active/": { + "get": { + "summary": "List active import jobs", + "description": "What is running or waiting, plus what just ended, for a progress indicator.", + "operationId": "get_v3_import_jobs_active", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_active_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Active import jobs.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "running": { + "type": "integer" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } } }, "components": { @@ -72774,7 +73736,8 @@ "COMPLETED", "FAILED", "PAUSED", - "CANCELLED" + "CANCELLED", + "READY" ] }, "import_type": { @@ -72849,6 +73812,99 @@ "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 } } }, @@ -92295,7 +93351,7 @@ "bank_verifications": { "type": "array", "nullable": true, - "description": "Bank Verification results, one item per Bank Verification step, for User and Business Verification. `null` until a step produces one. Each item's `method` says how the account was verified: `open_banking` (bank sign-in) or `account_lookup` (account check), or a `microdeposits` or `statement` fallback. See [Bank verification methods](/core-technology/bank-verification/verification-methods).", + "description": "Bank Verification results, one item per Bank Verification step, for User and Business Verification. `null` until a step produces one. Each item's `method` says how the account was verified: `open_banking` (bank sign-in) or `account_lookup` (account check), or a `statement` or `microdeposits` fallback, and `none` when the person continued without a bank before any attempt was evaluated (`continued_without_bank`). See [Bank verification methods](/core-technology/bank-verification/verification-methods).", "items": { "$ref": "#/components/schemas/BankVerificationResult" } @@ -94735,6 +95791,26 @@ "updated_at": { "type": "string", "format": "date-time" + }, + "is_bank_verification_enabled": { + "type": "boolean", + "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps and before database validation, AML and IP analysis. Set in the console." + }, + "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).", + "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." + }, + "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.", + "example": {} } }, "additionalProperties": true @@ -104141,7 +105217,7 @@ }, "BankVerificationResult": { "type": "object", - "description": "One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified. See [Bank verification methods](/core-technology/bank-verification/verification-methods).", + "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", @@ -104179,9 +105255,10 @@ "open_banking", "account_lookup", "microdeposits", - "statement" + "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). `null` until an account is evaluated. Workflow rules read the same value as `bank.method`." + "description": "How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`." }, "provider": { "type": "string", @@ -104552,7 +105629,7 @@ "usage_type": { "type": "string", "nullable": true, - "description": "The usage line the charge was booked on." + "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." } } }, @@ -104574,7 +105651,61 @@ "not_covered": { "type": "boolean", "nullable": true, - "description": "Whether the person's bank or country could not be reached on this step." + "description": "Whether the person's bank or country could not be reached on this step. Also `true` when the person continued without a bank because no method could verify their country or bank." + }, + "continued_without_bank": { + "type": "boolean", + "description": "Whether the person left the step with **Continue without your bank**. `continuation` then says why. `method` is `none` when no attempt was evaluated before they continued. After an evaluated attempt, such as a holder that does not match, `method` stays the method of that attempt and its verdict and reason codes stand. Returned even when the workflow narrows the data it returns." + }, + "continuation": { + "type": "object", + "nullable": true, + "description": "How the person continued without a bank. `null` when they did not. When the workflow narrows the data it returns, `reason` is still returned and every other key is `null`.", + "properties": { + "reason": { + "type": "string", + "enum": [ + "no_route", + "institution_unsupported", + "attempts_exhausted", + "person_choice" + ], + "description": "Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step." + }, + "country": { + "type": "string", + "nullable": true, + "description": "The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one." + }, + "offered": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "statement", + "microdeposits" + ] + }, + "nullable": true, + "description": "The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none." + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason." + }, + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." + } + } }, "evidence_purged_at": { "type": "string", @@ -104594,7 +105725,7 @@ "items": { "type": "string" }, - "description": "Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply." + "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", @@ -104634,6 +105765,416 @@ "description": "The name of the company that should hold the account. Checks a business account." } } + }, + "BankVerificationPricing": { + "type": "object", + "description": "Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.", + "required": [ + "currency", + "statement_upload", + "own_provider_fee_usd", + "countries" + ], + "properties": { + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" + }, + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" + }, + "own_provider_fee_usd": { + "type": "string", + "description": "Fee for each billable ownership check on a route that uses your own provider contract instead of Didit's, charged whatever the result. A cancelled, timed out or abandoned connection is not billed. Decimal string with 4 decimals, in USD.", + "example": "0.0200" + }, + "countries": { + "type": "object", + "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", + "additionalProperties": { + "$ref": "#/components/schemas/BankVerificationPricingCountry" + } + } + } + }, + "BankStatementUploadPricing": { + "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], + "properties": { + "price_usd": { + "type": "string", + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" + }, + "included_after_charged_bank_attempt": { + "type": "boolean", + "description": "`true` when the upload is included at no extra charge after a charged bank attempt in the same session.", + "example": true + }, + "standalone_available": { + "type": "boolean", + "description": "`true` when a statement upload can run on its own, with no bank attempt first. `false` in production; sandbox sessions can test it without billing.", + "example": false + } + } + }, + "BankVerificationPricingCountry": { + "type": "object", + "description": "One country's Bank Verification methods and their list prices.", + "required": [ + "name", + "methods" + ], + "properties": { + "name": { + "type": "string", + "description": "Country name in English.", + "example": "United Kingdom" + }, + "methods": { + "$ref": "#/components/schemas/BankVerificationPricingMethods" + } + } + }, + "BankVerificationPricingMethods": { + "type": "object", + "description": "The two Bank Verification methods in a country.", + "required": [ + "open_banking", + "account_lookup" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/BankSignInPricing" + }, + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" + } + } + }, + "BankSignInPricing": { + "type": "object", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", + "required": [ + "available", + "market", + "price_usd", + "add_ons" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true + }, + "market": { + "type": "string", + "nullable": true, + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.7900" + }, + "add_ons": { + "type": "object", + "description": "Add-ons priced in this country, each a decimal string with 4 decimals in USD, added to the bank sign-in price. Only the add-ons the country offers are listed; empty when bank sign-in is not available.", + "properties": { + "balance": { + "type": "string", + "description": "Account balance add-on.", + "example": "0.3500" + }, + "transactions": { + "type": "string", + "description": "Transactions add-on.", + "example": "1.3000" + } + } + } + } + }, + "BankAccountCheckPricing": { + "type": "object", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", + "required": [ + "available", + "price_usd" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.4000" + } + } + }, + "ImportProvider": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "api_list", + "api_ids", + "export_file" + ], + "description": "`api_list`: Didit lists records with the key. `api_ids`: upload the record IDs from an export. `export_file`: upload an export in Didit's format." + }, + "record_noun": { + "type": "string" + }, + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + } + } + } + }, + "ImportJobSummary": { + "type": "object", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "nullable": true + }, + "provider_name": { + "type": "string", + "nullable": true + }, + "filename": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { + "type": "string", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "nullable": true + }, + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "progress": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "processed": { + "type": "integer", + "format": "int64" + }, + "imported": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "integer", + "format": "int64" + }, + "already_in_didit": { + "type": "integer", + "format": "int64" + }, + "failed": { + "type": "integer", + "format": "int64" + }, + "not_finished": { + "type": "integer", + "format": "int64" + }, + "same_person": { + "type": "integer", + "format": "int64" + } + } + }, + "face_templates": { + "type": "object", + "nullable": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "built": { + "type": "integer", + "format": "int64" + }, + "duplicates": { + "type": "integer", + "format": "int64" + }, + "blocklisted": { + "type": "integer", + "format": "int64" + } + } + }, + "eta_seconds": { + "type": "integer", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } } }, "securitySchemes": { diff --git a/src/didit/resources/__init__.py b/src/didit/resources/__init__.py index 3f3d316..0fa7b4c 100644 --- a/src/didit/resources/__init__.py +++ b/src/didit/resources/__init__.py @@ -38,6 +38,7 @@ from didit.resources.networks import AsyncNetworks, Networks from didit.resources.passive_liveness import AsyncPassiveLiveness, PassiveLiveness from didit.resources.phone import AsyncPhone, Phone + from didit.resources.pricing import AsyncPricing, Pricing from didit.resources.proof_of_address import AsyncProofOfAddress, ProofOfAddress from didit.resources.questionnaires import AsyncQuestionnaires, Questionnaires from didit.resources.regulatory_reports import AsyncRegulatoryReports, RegulatoryReports @@ -230,6 +231,12 @@ def phone(self) -> Phone: return Phone(self) + @cached_property + def pricing(self) -> Pricing: + from didit.resources.pricing import Pricing + + return Pricing(self) + @cached_property def proof_of_address(self) -> ProofOfAddress: from didit.resources.proof_of_address import ProofOfAddress @@ -474,6 +481,12 @@ def phone(self) -> AsyncPhone: return AsyncPhone(self) + @cached_property + def pricing(self) -> AsyncPricing: + from didit.resources.pricing import AsyncPricing + + return AsyncPricing(self) + @cached_property def proof_of_address(self) -> AsyncProofOfAddress: from didit.resources.proof_of_address import AsyncProofOfAddress diff --git a/src/didit/resources/pricing.py b/src/didit/resources/pricing.py new file mode 100644 index 0000000..a87cb35 --- /dev/null +++ b/src/didit/resources/pricing.py @@ -0,0 +1,166 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +"""The ``pricing`` resource.""" + +from __future__ import annotations + +from functools import cached_property +from typing import cast + +import httpx + +from didit._resource import AsyncAPIResource, SyncAPIResource +from didit._response import ( + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, + to_raw_response_wrapper, + to_streamed_response_wrapper, +) +from didit._types import NOT_GIVEN, Body, Headers, NotGiven, Query +from didit.types.components.bank_verification_pricing import BankVerificationPricing + +__all__ = [ + "Pricing", + "AsyncPricing", + "PricingWithRawResponse", + "AsyncPricingWithRawResponse", + "PricingWithStreamingResponse", + "AsyncPricingWithStreamingResponse", +] + + +class Pricing(SyncAPIResource): + """The ``pricing`` resource of the Didit API. + + Methods: ``bank_verification``. + """ + + @cached_property + def with_raw_response(self) -> PricingWithRawResponse: + """Every method of this resource, returning the raw HTTP response.""" + return PricingWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PricingWithStreamingResponse: + """Every method of this resource, returning the HTTP response before reading its body.""" + return PricingWithStreamingResponse(self) + + def bank_verification( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BankVerificationPricing: + """Get bank verification prices (public, unauthenticated). + + Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product. + + Public: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed. + + A method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet. + + These are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization. + + ``GET /v3/organization/bank-verification-pricing/`` + + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "BankVerificationPricing", + self._client.request( + "get", + "/v3/organization/bank-verification-pricing/", + cast_to=BankVerificationPricing, + auth="none", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + +class AsyncPricing(AsyncAPIResource): + """The ``pricing`` resource of the Didit API. + + Methods: ``bank_verification``. + """ + + @cached_property + def with_raw_response(self) -> AsyncPricingWithRawResponse: + """Every method of this resource, returning the raw HTTP response.""" + return AsyncPricingWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPricingWithStreamingResponse: + """Every method of this resource, returning the HTTP response before reading its body.""" + return AsyncPricingWithStreamingResponse(self) + + async def bank_verification( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> BankVerificationPricing: + """Get bank verification prices (public, unauthenticated). + + Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product. + + Public: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed. + + A method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet. + + These are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization. + + ``GET /v3/organization/bank-verification-pricing/`` + + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "BankVerificationPricing", + await self._client.request( + "get", + "/v3/organization/bank-verification-pricing/", + cast_to=BankVerificationPricing, + auth="none", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + +class PricingWithRawResponse: + def __init__(self, resource: Pricing) -> None: + self._resource = resource + self.bank_verification = to_raw_response_wrapper(resource.bank_verification) + + +class AsyncPricingWithRawResponse: + def __init__(self, resource: AsyncPricing) -> None: + self._resource = resource + self.bank_verification = async_to_raw_response_wrapper(resource.bank_verification) + + +class PricingWithStreamingResponse: + def __init__(self, resource: Pricing) -> None: + self._resource = resource + self.bank_verification = to_streamed_response_wrapper(resource.bank_verification) + + +class AsyncPricingWithStreamingResponse: + def __init__(self, resource: AsyncPricing) -> None: + self._resource = resource + self.bank_verification = async_to_streamed_response_wrapper(resource.bank_verification) diff --git a/src/didit/resources/sessions/imports.py b/src/didit/resources/sessions/imports.py index 8e59dd9..23e4ed5 100644 --- a/src/didit/resources/sessions/imports.py +++ b/src/didit/resources/sessions/imports.py @@ -4,7 +4,7 @@ from __future__ import annotations from functools import cached_property -from typing import Literal, cast +from typing import Literal, Mapping, Sequence, cast import httpx @@ -19,7 +19,21 @@ from didit._types import NOT_GIVEN, Body, FileTypes, Headers, NotGiven, Query from didit.types.components.session_import_error import SessionImportError from didit.types.components.session_import_job import SessionImportJob +from didit.types.sessions.imports.check_provider_params import ( + SessionsImportsCheckProviderParamsOptions, +) +from didit.types.sessions.imports.check_provider_response import ( + SessionsImportsCheckProviderResponse, +) +from didit.types.sessions.imports.create_params import SessionsImportsCreateParamsOptions +from didit.types.sessions.imports.list_active_jobs_response import ( + SessionsImportsListActiveJobsResponse, +) from didit.types.sessions.imports.list_errors_response import SessionsImportsListErrorsResponse +from didit.types.sessions.imports.list_jobs_response import SessionsImportsListJobsResponse +from didit.types.sessions.imports.list_providers_response import ( + SessionsImportsListProvidersResponse, +) __all__ = [ "SessionsImports", @@ -34,7 +48,7 @@ class SessionsImports(SyncAPIResource): """The ``sessions.imports`` resource of the Didit API. - Methods: ``create``, ``list_errors``, ``retrieve``, ``retrieve_template``. + Methods: ``cancel``, ``check_provider``, ``create``, ``list_active_jobs``, ``list_errors``, ``list_jobs``, ``list_providers``, ``pause``, ``resume``, ``retrieve``, ``retrieve_template``, ``retry``, ``start``. """ @cached_property @@ -58,6 +72,37 @@ def create( workflow_id: str | NotGiven = NOT_GIVEN, source_format: Literal["csv", "ndjson"] | str | NotGiven = NOT_GIVEN, source_file_url: str | NotGiven = NOT_GIVEN, + source: Literal["file", "provider"] | str | NotGiven = NOT_GIVEN, + provider: Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str + | NotGiven = NOT_GIVEN, + credentials: Mapping[str, str] | NotGiven = NOT_GIVEN, + options: SessionsImportsCreateParamsOptions | NotGiven = NOT_GIVEN, + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] + | str, + ] + | NotGiven = NOT_GIVEN, + dry_run: bool | NotGiven = NOT_GIVEN, + face_templates: bool | NotGiven = NOT_GIVEN, + lawful_basis_confirmed: bool | NotGiven = NOT_GIVEN, file: FileTypes | NotGiven = NOT_GIVEN, extra_headers: Headers | None = None, extra_query: Query | None = None, @@ -66,7 +111,7 @@ def create( ) -> SessionImportJob: """Create a verification import job. - Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Use this to migrate from providers such as Sumsub, MetaMap, Veriff, Onfido, Persona, Trulioo, Jumio, Incode, iDenfy, or any other provider after transforming the export into Didit's canonical CSV/NDJSON format. Imports are disabled by default and must be enabled per organization by Didit. + Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit. Imports are disabled by default and must be enabled per organization by Didit. ``POST /v3/session/imports/`` @@ -75,6 +120,14 @@ def create( workflow_id: Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions. source_format: Optional. Inferred from the URL's file extension when omitted. source_file_url: Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`. + source: `provider` makes this a migration from another identity verification provider. Omit for a file import. + provider: Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`. + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + options + status_mapping: Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported. + dry_run: Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`. + face_templates: Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`. + lawful_basis_confirmed: Confirms you have a lawful basis to share these selfies with Didit as processor. file: Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`. extra_headers: Headers to add to (or, with ``None``, remove from) this request. extra_query: Query parameters to add to this request. @@ -93,6 +146,14 @@ def create( "workflow_id": workflow_id, "source_format": source_format, "source_file_url": source_file_url, + "source": source, + "provider": provider, + "credentials": credentials, + "options": options, + "status_mapping": status_mapping, + "dry_run": dry_run, + "face_templates": face_templates, + "lawful_basis_confirmed": lawful_basis_confirmed, "file": file, }, multipart="auto", @@ -226,52 +287,133 @@ def list_errors( ), ) + def list_providers( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListProvidersResponse: + """List migration providers. -class AsyncSessionsImports(AsyncAPIResource): - """The ``sessions.imports`` resource of the Didit API. + The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping. - Methods: ``create``, ``list_errors``, ``retrieve``, ``retrieve_template``. - """ + ``GET /v3/session/imports/providers/`` - @cached_property - def with_raw_response(self) -> AsyncSessionsImportsWithRawResponse: - """Every method of this resource, returning the raw HTTP response.""" - return AsyncSessionsImportsWithRawResponse(self) + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListProvidersResponse", + self._client.request( + "get", + "/v3/session/imports/providers/", + cast_to=SessionsImportsListProvidersResponse, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) - @cached_property - def with_streaming_response(self) -> AsyncSessionsImportsWithStreamingResponse: - """Every method of this resource, returning the HTTP response before reading its body.""" - return AsyncSessionsImportsWithStreamingResponse(self) + def check_provider( + self, + *, + provider: Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str, + credentials: Mapping[str, str], + options: SessionsImportsCheckProviderParamsOptions | NotGiven = NOT_GIVEN, + record_ids: Sequence[str] | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsCheckProviderResponse: + """Check a provider key. - async def create( + Read the provider with your key before creating a migration. Nothing is stored. + + ``POST /v3/session/imports/providers/check/`` + + Args: + provider: Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`. + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + options + record_ids: For `api_ids` providers: the first record ID of your export. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsCheckProviderResponse", + self._client.request( + "post", + "/v3/session/imports/providers/check/", + cast_to=SessionsImportsCheckProviderResponse, + auth="api_key", + body={ + "provider": provider, + "credentials": credentials, + "options": options, + "record_ids": record_ids, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def start( self, + import_id: str, *, - import_type: Literal[ - "user_verification", "business_verification", "status_rules", "transactions" + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] + | str, ] - | str | NotGiven = NOT_GIVEN, - workflow_id: str | NotGiven = NOT_GIVEN, - source_format: Literal["csv", "ndjson"] | str | NotGiven = NOT_GIVEN, - source_file_url: str | NotGiven = NOT_GIVEN, - file: FileTypes | NotGiven = NOT_GIVEN, + face_templates: bool | NotGiven = NOT_GIVEN, + lawful_basis_confirmed: bool | NotGiven = NOT_GIVEN, extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SessionImportJob: - """Create a verification import job. + """Start a dry run. - Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Use this to migrate from providers such as Sumsub, MetaMap, Veriff, Onfido, Persona, Trulioo, Jumio, Incode, iDenfy, or any other provider after transforming the export into Didit's canonical CSV/NDJSON format. Imports are disabled by default and must be enabled per organization by Didit. + Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now. - ``POST /v3/session/imports/`` + ``POST /v3/session/imports/{importId}/start/`` Args: - import_type: What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records. - workflow_id: Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions. - source_format: Optional. Inferred from the URL's file extension when omitted. - source_file_url: Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`. - file: Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`. + import_id + status_mapping: Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported. + face_templates: Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`. + lawful_basis_confirmed: Confirms you have a lawful basis to share these selfies with Didit as processor. extra_headers: Headers to add to (or, with ``None``, remove from) this request. extra_query: Query parameters to add to this request. extra_body: Top-level JSON fields to add to this request's body. @@ -279,20 +421,17 @@ async def create( """ return cast( "SessionImportJob", - await self._client.request( + self._client.request( "post", - "/v3/session/imports/", + "/v3/session/imports/{importId}/start/", cast_to=SessionImportJob, + path_params={"importId": import_id}, auth="api_key", body={ - "import_type": import_type, - "workflow_id": workflow_id, - "source_format": source_format, - "source_file_url": source_file_url, - "file": file, + "status_mapping": status_mapping, + "face_templates": face_templates, + "lawful_basis_confirmed": lawful_basis_confirmed, }, - multipart="auto", - file_fields=("file",), extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, @@ -300,41 +439,36 @@ async def create( ), ) - async def retrieve_template( + def pause( self, + import_id: str, *, - import_type: Literal[ - "user_verification", "business_verification", "status_rules", "transactions" - ] - | str - | NotGiven = NOT_GIVEN, extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> bytes: - """Download verification import template. + ) -> SessionImportJob: + """Pause an import job. - Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`. + Pause a queued or running import. - ``GET /v3/session/imports/template/`` + ``POST /v3/session/imports/{importId}/pause/`` Args: - import_type + import_id extra_headers: Headers to add to (or, with ``None``, remove from) this request. extra_query: Query parameters to add to this request. extra_body: Top-level JSON fields to add to this request's body. timeout: Overrides the client's timeout for this request, in seconds. """ return cast( - "bytes", - await self._client.request( - "get", - "/v3/session/imports/template/", - cast_to=bytes, + "SessionImportJob", + self._client.request( + "post", + "/v3/session/imports/{importId}/pause/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, auth="api_key", - query={"import_type": import_type}, - accept="text/csv, application/json", extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, @@ -342,7 +476,7 @@ async def retrieve_template( ), ) - async def retrieve( + def resume( self, import_id: str, *, @@ -351,11 +485,11 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SessionImportJob: - """Retrieve a verification import job. + """Resume an import job. - Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected - list them via the errors endpoint. + Resume an import you paused. A job paused by the provider's rate limit resumes by itself. - ``GET /v3/session/imports/{importId}/`` + ``POST /v3/session/imports/{importId}/resume/`` Args: import_id @@ -366,9 +500,9 @@ async def retrieve( """ return cast( "SessionImportJob", - await self._client.request( - "get", - "/v3/session/imports/{importId}/", + self._client.request( + "post", + "/v3/session/imports/{importId}/resume/", cast_to=SessionImportJob, path_params={"importId": import_id}, auth="api_key", @@ -379,42 +513,776 @@ async def retrieve( ), ) - def list_errors( + def cancel( self, import_id: str, *, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[SessionImportError]: - """List verification import row errors. + ) -> SessionImportJob: + """Cancel an import job. - List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows. + Cancel an import. What was imported stays; the uploaded file and the key are erased. - ``GET /v3/session/imports/{importId}/errors/`` + ``POST /v3/session/imports/{importId}/cancel/`` Args: import_id - limit: Page size. Defaults to 50 when omitted. - offset: Zero-based offset of the first record to return. extra_headers: Headers to add to (or, with ``None``, remove from) this request. extra_query: Query parameters to add to this request. extra_body: Top-level JSON fields to add to this request's body. timeout: Overrides the client's timeout for this request, in seconds. """ return cast( - "AsyncPaginator[SessionImportError]", - self._client.request_page( - "get", - "/v3/session/imports/{importId}/errors/", - cast_to=SessionsImportsListErrorsResponse, - item_type=SessionImportError, + "SessionImportJob", + self._client.request( + "post", + "/v3/session/imports/{importId}/cancel/", + cast_to=SessionImportJob, path_params={"importId": import_id}, auth="api_key", - query={"limit": limit, "offset": offset}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def retry( + self, + import_id: str, + *, + credentials: Mapping[str, str] | NotGiven = NOT_GIVEN, + file: FileTypes | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Retry a migration. + + Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped. + + ``POST /v3/session/imports/{importId}/retry/`` + + Args: + import_id + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + file: The record IDs or export file again, for `api_ids` and `export_file` providers. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + self._client.request( + "post", + "/v3/session/imports/{importId}/retry/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + body={"credentials": credentials, "file": file}, + multipart=True, + file_fields=("file",), + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def list_jobs( + self, + *, + scope: Literal["all", "sessions", "users", "businesses"] | str | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListJobsResponse: + """List import jobs. + + Every import of the application in one shape: verification imports and migrations, people and businesses. + + ``GET /v3/import-jobs/`` + + Args: + scope + limit + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListJobsResponse", + self._client.request( + "get", + "/v3/import-jobs/", + cast_to=SessionsImportsListJobsResponse, + auth="api_key", + query={"scope": scope, "limit": limit}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def list_active_jobs( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListActiveJobsResponse: + """List active import jobs. + + What is running or waiting, plus what just ended, for a progress indicator. + + ``GET /v3/import-jobs/active/`` + + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListActiveJobsResponse", + self._client.request( + "get", + "/v3/import-jobs/active/", + cast_to=SessionsImportsListActiveJobsResponse, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + +class AsyncSessionsImports(AsyncAPIResource): + """The ``sessions.imports`` resource of the Didit API. + + Methods: ``cancel``, ``check_provider``, ``create``, ``list_active_jobs``, ``list_errors``, ``list_jobs``, ``list_providers``, ``pause``, ``resume``, ``retrieve``, ``retrieve_template``, ``retry``, ``start``. + """ + + @cached_property + def with_raw_response(self) -> AsyncSessionsImportsWithRawResponse: + """Every method of this resource, returning the raw HTTP response.""" + return AsyncSessionsImportsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSessionsImportsWithStreamingResponse: + """Every method of this resource, returning the HTTP response before reading its body.""" + return AsyncSessionsImportsWithStreamingResponse(self) + + async def create( + self, + *, + import_type: Literal[ + "user_verification", "business_verification", "status_rules", "transactions" + ] + | str + | NotGiven = NOT_GIVEN, + workflow_id: str | NotGiven = NOT_GIVEN, + source_format: Literal["csv", "ndjson"] | str | NotGiven = NOT_GIVEN, + source_file_url: str | NotGiven = NOT_GIVEN, + source: Literal["file", "provider"] | str | NotGiven = NOT_GIVEN, + provider: Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str + | NotGiven = NOT_GIVEN, + credentials: Mapping[str, str] | NotGiven = NOT_GIVEN, + options: SessionsImportsCreateParamsOptions | NotGiven = NOT_GIVEN, + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] + | str, + ] + | NotGiven = NOT_GIVEN, + dry_run: bool | NotGiven = NOT_GIVEN, + face_templates: bool | NotGiven = NOT_GIVEN, + lawful_basis_confirmed: bool | NotGiven = NOT_GIVEN, + file: FileTypes | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Create a verification import job. + + Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit. Imports are disabled by default and must be enabled per organization by Didit. + + ``POST /v3/session/imports/`` + + Args: + import_type: What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records. + workflow_id: Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions. + source_format: Optional. Inferred from the URL's file extension when omitted. + source_file_url: Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`. + source: `provider` makes this a migration from another identity verification provider. Omit for a file import. + provider: Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`. + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + options + status_mapping: Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported. + dry_run: Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`. + face_templates: Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`. + lawful_basis_confirmed: Confirms you have a lawful basis to share these selfies with Didit as processor. + file: Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/", + cast_to=SessionImportJob, + auth="api_key", + body={ + "import_type": import_type, + "workflow_id": workflow_id, + "source_format": source_format, + "source_file_url": source_file_url, + "source": source, + "provider": provider, + "credentials": credentials, + "options": options, + "status_mapping": status_mapping, + "dry_run": dry_run, + "face_templates": face_templates, + "lawful_basis_confirmed": lawful_basis_confirmed, + "file": file, + }, + multipart="auto", + file_fields=("file",), + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def retrieve_template( + self, + *, + import_type: Literal[ + "user_verification", "business_verification", "status_rules", "transactions" + ] + | str + | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> bytes: + """Download verification import template. + + Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`. + + ``GET /v3/session/imports/template/`` + + Args: + import_type + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "bytes", + await self._client.request( + "get", + "/v3/session/imports/template/", + cast_to=bytes, + auth="api_key", + query={"import_type": import_type}, + accept="text/csv, application/json", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def retrieve( + self, + import_id: str, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Retrieve a verification import job. + + Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected - list them via the errors endpoint. + + ``GET /v3/session/imports/{importId}/`` + + Args: + import_id + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "get", + "/v3/session/imports/{importId}/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def list_errors( + self, + import_id: str, + *, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[SessionImportError]: + """List verification import row errors. + + List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows. + + ``GET /v3/session/imports/{importId}/errors/`` + + Args: + import_id + limit: Page size. Defaults to 50 when omitted. + offset: Zero-based offset of the first record to return. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "AsyncPaginator[SessionImportError]", + self._client.request_page( + "get", + "/v3/session/imports/{importId}/errors/", + cast_to=SessionsImportsListErrorsResponse, + item_type=SessionImportError, + path_params={"importId": import_id}, + auth="api_key", + query={"limit": limit, "offset": offset}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def list_providers( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListProvidersResponse: + """List migration providers. + + The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping. + + ``GET /v3/session/imports/providers/`` + + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListProvidersResponse", + await self._client.request( + "get", + "/v3/session/imports/providers/", + cast_to=SessionsImportsListProvidersResponse, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def check_provider( + self, + *, + provider: Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str, + credentials: Mapping[str, str], + options: SessionsImportsCheckProviderParamsOptions | NotGiven = NOT_GIVEN, + record_ids: Sequence[str] | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsCheckProviderResponse: + """Check a provider key. + + Read the provider with your key before creating a migration. Nothing is stored. + + ``POST /v3/session/imports/providers/check/`` + + Args: + provider: Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`. + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + options + record_ids: For `api_ids` providers: the first record ID of your export. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsCheckProviderResponse", + await self._client.request( + "post", + "/v3/session/imports/providers/check/", + cast_to=SessionsImportsCheckProviderResponse, + auth="api_key", + body={ + "provider": provider, + "credentials": credentials, + "options": options, + "record_ids": record_ids, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def start( + self, + import_id: str, + *, + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] + | str, + ] + | NotGiven = NOT_GIVEN, + face_templates: bool | NotGiven = NOT_GIVEN, + lawful_basis_confirmed: bool | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Start a dry run. + + Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now. + + ``POST /v3/session/imports/{importId}/start/`` + + Args: + import_id + status_mapping: Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported. + face_templates: Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`. + lawful_basis_confirmed: Confirms you have a lawful basis to share these selfies with Didit as processor. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/{importId}/start/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + body={ + "status_mapping": status_mapping, + "face_templates": face_templates, + "lawful_basis_confirmed": lawful_basis_confirmed, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def pause( + self, + import_id: str, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Pause an import job. + + Pause a queued or running import. + + ``POST /v3/session/imports/{importId}/pause/`` + + Args: + import_id + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/{importId}/pause/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def resume( + self, + import_id: str, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Resume an import job. + + Resume an import you paused. A job paused by the provider's rate limit resumes by itself. + + ``POST /v3/session/imports/{importId}/resume/`` + + Args: + import_id + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/{importId}/resume/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def cancel( + self, + import_id: str, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Cancel an import job. + + Cancel an import. What was imported stays; the uploaded file and the key are erased. + + ``POST /v3/session/imports/{importId}/cancel/`` + + Args: + import_id + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/{importId}/cancel/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def retry( + self, + import_id: str, + *, + credentials: Mapping[str, str] | NotGiven = NOT_GIVEN, + file: FileTypes | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionImportJob: + """Retry a migration. + + Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped. + + ``POST /v3/session/imports/{importId}/retry/`` + + Args: + import_id + credentials: The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart. + file: The record IDs or export file again, for `api_ids` and `export_file` providers. + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionImportJob", + await self._client.request( + "post", + "/v3/session/imports/{importId}/retry/", + cast_to=SessionImportJob, + path_params={"importId": import_id}, + auth="api_key", + body={"credentials": credentials, "file": file}, + multipart=True, + file_fields=("file",), + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def list_jobs( + self, + *, + scope: Literal["all", "sessions", "users", "businesses"] | str | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListJobsResponse: + """List import jobs. + + Every import of the application in one shape: verification imports and migrations, people and businesses. + + ``GET /v3/import-jobs/`` + + Args: + scope + limit + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListJobsResponse", + await self._client.request( + "get", + "/v3/import-jobs/", + cast_to=SessionsImportsListJobsResponse, + auth="api_key", + query={"scope": scope, "limit": limit}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + async def list_active_jobs( + self, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SessionsImportsListActiveJobsResponse: + """List active import jobs. + + What is running or waiting, plus what just ended, for a progress indicator. + + ``GET /v3/import-jobs/active/`` + + Args: + extra_headers: Headers to add to (or, with ``None``, remove from) this request. + extra_query: Query parameters to add to this request. + extra_body: Top-level JSON fields to add to this request's body. + timeout: Overrides the client's timeout for this request, in seconds. + """ + return cast( + "SessionsImportsListActiveJobsResponse", + await self._client.request( + "get", + "/v3/import-jobs/active/", + cast_to=SessionsImportsListActiveJobsResponse, + auth="api_key", extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, @@ -430,6 +1298,15 @@ def __init__(self, resource: SessionsImports) -> None: self.retrieve_template = to_raw_response_wrapper(resource.retrieve_template) self.retrieve = to_raw_response_wrapper(resource.retrieve) self.list_errors = to_raw_response_wrapper(resource.list_errors) + self.list_providers = to_raw_response_wrapper(resource.list_providers) + self.check_provider = to_raw_response_wrapper(resource.check_provider) + self.start = to_raw_response_wrapper(resource.start) + self.pause = to_raw_response_wrapper(resource.pause) + self.resume = to_raw_response_wrapper(resource.resume) + self.cancel = to_raw_response_wrapper(resource.cancel) + self.retry = to_raw_response_wrapper(resource.retry) + self.list_jobs = to_raw_response_wrapper(resource.list_jobs) + self.list_active_jobs = to_raw_response_wrapper(resource.list_active_jobs) class AsyncSessionsImportsWithRawResponse: @@ -439,6 +1316,15 @@ def __init__(self, resource: AsyncSessionsImports) -> None: self.retrieve_template = async_to_raw_response_wrapper(resource.retrieve_template) self.retrieve = async_to_raw_response_wrapper(resource.retrieve) self.list_errors = async_to_raw_response_wrapper(resource.list_errors) + self.list_providers = async_to_raw_response_wrapper(resource.list_providers) + self.check_provider = async_to_raw_response_wrapper(resource.check_provider) + self.start = async_to_raw_response_wrapper(resource.start) + self.pause = async_to_raw_response_wrapper(resource.pause) + self.resume = async_to_raw_response_wrapper(resource.resume) + self.cancel = async_to_raw_response_wrapper(resource.cancel) + self.retry = async_to_raw_response_wrapper(resource.retry) + self.list_jobs = async_to_raw_response_wrapper(resource.list_jobs) + self.list_active_jobs = async_to_raw_response_wrapper(resource.list_active_jobs) class SessionsImportsWithStreamingResponse: @@ -448,6 +1334,15 @@ def __init__(self, resource: SessionsImports) -> None: self.retrieve_template = to_streamed_response_wrapper(resource.retrieve_template) self.retrieve = to_streamed_response_wrapper(resource.retrieve) self.list_errors = to_streamed_response_wrapper(resource.list_errors) + self.list_providers = to_streamed_response_wrapper(resource.list_providers) + self.check_provider = to_streamed_response_wrapper(resource.check_provider) + self.start = to_streamed_response_wrapper(resource.start) + self.pause = to_streamed_response_wrapper(resource.pause) + self.resume = to_streamed_response_wrapper(resource.resume) + self.cancel = to_streamed_response_wrapper(resource.cancel) + self.retry = to_streamed_response_wrapper(resource.retry) + self.list_jobs = to_streamed_response_wrapper(resource.list_jobs) + self.list_active_jobs = to_streamed_response_wrapper(resource.list_active_jobs) class AsyncSessionsImportsWithStreamingResponse: @@ -457,3 +1352,12 @@ def __init__(self, resource: AsyncSessionsImports) -> None: self.retrieve_template = async_to_streamed_response_wrapper(resource.retrieve_template) self.retrieve = async_to_streamed_response_wrapper(resource.retrieve) self.list_errors = async_to_streamed_response_wrapper(resource.list_errors) + self.list_providers = async_to_streamed_response_wrapper(resource.list_providers) + self.check_provider = async_to_streamed_response_wrapper(resource.check_provider) + self.start = async_to_streamed_response_wrapper(resource.start) + self.pause = async_to_streamed_response_wrapper(resource.pause) + self.resume = async_to_streamed_response_wrapper(resource.resume) + self.cancel = async_to_streamed_response_wrapper(resource.cancel) + self.retry = async_to_streamed_response_wrapper(resource.retry) + self.list_jobs = async_to_streamed_response_wrapper(resource.list_jobs) + self.list_active_jobs = async_to_streamed_response_wrapper(resource.list_active_jobs) diff --git a/src/didit/types/__init__.py b/src/didit/types/__init__.py index 1e9dc7b..175ffb5 100644 --- a/src/didit/types/__init__.py +++ b/src/didit/types/__init__.py @@ -501,9 +501,28 @@ from didit.types.components.bank_account_check_person_holder import ( BankAccountCheckPersonHolder as BankAccountCheckPersonHolder, ) + from didit.types.components.bank_account_check_pricing import ( + BankAccountCheckPricing as BankAccountCheckPricing, + ) from didit.types.components.bank_account_check_response import ( BankAccountCheckResponse as BankAccountCheckResponse, ) + from didit.types.components.bank_sign_in_pricing import BankSignInPricing as BankSignInPricing + from didit.types.components.bank_sign_in_pricing import ( + BankSignInPricingAddOns as BankSignInPricingAddOns, + ) + from didit.types.components.bank_statement_upload_pricing import ( + BankStatementUploadPricing as BankStatementUploadPricing, + ) + from didit.types.components.bank_verification_pricing import ( + BankVerificationPricing as BankVerificationPricing, + ) + from didit.types.components.bank_verification_pricing_country import ( + BankVerificationPricingCountry as BankVerificationPricingCountry, + ) + from didit.types.components.bank_verification_pricing_methods import ( + BankVerificationPricingMethods as BankVerificationPricingMethods, + ) from didit.types.components.bank_verification_result import ( BankVerificationResult as BankVerificationResult, ) @@ -516,6 +535,9 @@ from didit.types.components.bank_verification_result import ( BankVerificationResultConnection as BankVerificationResultConnection, ) + from didit.types.components.bank_verification_result import ( + BankVerificationResultContinuation as BankVerificationResultContinuation, + ) from didit.types.components.bank_verification_result import ( BankVerificationResultFinancialData as BankVerificationResultFinancialData, ) @@ -968,6 +990,20 @@ from didit.types.components.fraud_result_enum import FraudResultEnum as FraudResultEnum from didit.types.components.fraud_status_enum import FraudStatusEnum as FraudStatusEnum from didit.types.components.fraud_type_enum import FraudTypeEnum as FraudTypeEnum + from didit.types.components.import_job_summary import ImportJobSummary as ImportJobSummary + from didit.types.components.import_job_summary import ( + ImportJobSummaryFaceTemplates as ImportJobSummaryFaceTemplates, + ) + from didit.types.components.import_job_summary import ( + ImportJobSummaryProgress as ImportJobSummaryProgress, + ) + from didit.types.components.import_job_summary import ( + ImportJobSummaryStartedBy as ImportJobSummaryStartedBy, + ) + from didit.types.components.import_provider import ImportProvider as ImportProvider + from didit.types.components.import_provider import ( + ImportProviderCredentialFields as ImportProviderCredentialFields, + ) from didit.types.components.investigator_batch_accept_response import ( InvestigatorBatchAcceptResponse as InvestigatorBatchAcceptResponse, ) @@ -2658,12 +2694,42 @@ from didit.types.sessions.import_shared_params import ( SessionsImportSharedParams as SessionsImportSharedParams, ) + from didit.types.sessions.imports.check_provider_params import ( + SessionsImportsCheckProviderParams as SessionsImportsCheckProviderParams, + ) + from didit.types.sessions.imports.check_provider_params import ( + SessionsImportsCheckProviderParamsOptions as SessionsImportsCheckProviderParamsOptions, + ) + from didit.types.sessions.imports.check_provider_response import ( + SessionsImportsCheckProviderResponse as SessionsImportsCheckProviderResponse, + ) from didit.types.sessions.imports.create_params import ( SessionsImportsCreateParams as SessionsImportsCreateParams, ) + from didit.types.sessions.imports.create_params import ( + SessionsImportsCreateParamsOptions as SessionsImportsCreateParamsOptions, + ) + from didit.types.sessions.imports.list_active_jobs_response import ( + SessionsImportsListActiveJobsResponse as SessionsImportsListActiveJobsResponse, + ) from didit.types.sessions.imports.list_errors_response import ( SessionsImportsListErrorsResponse as SessionsImportsListErrorsResponse, ) + from didit.types.sessions.imports.list_jobs_response import ( + SessionsImportsListJobsResponse as SessionsImportsListJobsResponse, + ) + from didit.types.sessions.imports.list_jobs_response import ( + SessionsImportsListJobsResponseCounts as SessionsImportsListJobsResponseCounts, + ) + from didit.types.sessions.imports.list_providers_response import ( + SessionsImportsListProvidersResponse as SessionsImportsListProvidersResponse, + ) + from didit.types.sessions.imports.retry_params import ( + SessionsImportsRetryParams as SessionsImportsRetryParams, + ) + from didit.types.sessions.imports.start_params import ( + SessionsImportsStartParams as SessionsImportsStartParams, + ) from didit.types.sessions.list_aml_screening_history_response import ( SessionsListAmlScreeningHistoryResponse as SessionsListAmlScreeningHistoryResponse, ) @@ -3145,11 +3211,19 @@ "BankAccountCheckParams": "bank.account_check_params", "BankAccountCheckParamsAccount": "bank.account_check_params", "BankAccountCheckPersonHolder": "components.bank_account_check_person_holder", + "BankAccountCheckPricing": "components.bank_account_check_pricing", "BankAccountCheckResponse": "components.bank_account_check_response", + "BankSignInPricing": "components.bank_sign_in_pricing", + "BankSignInPricingAddOns": "components.bank_sign_in_pricing", + "BankStatementUploadPricing": "components.bank_statement_upload_pricing", + "BankVerificationPricing": "components.bank_verification_pricing", + "BankVerificationPricingCountry": "components.bank_verification_pricing_country", + "BankVerificationPricingMethods": "components.bank_verification_pricing_methods", "BankVerificationResult": "components.bank_verification_result", "BankVerificationResultAccount": "components.bank_verification_result", "BankVerificationResultBilling": "components.bank_verification_result", "BankVerificationResultConnection": "components.bank_verification_result", + "BankVerificationResultContinuation": "components.bank_verification_result", "BankVerificationResultFinancialData": "components.bank_verification_result", "BankVerificationResultOwnership": "components.bank_verification_result", "BankVerificationResultRoute": "components.bank_verification_result", @@ -3476,6 +3550,12 @@ "IdWalletsStartSmartIdParams": "id_wallets.start_smart_id_params", "IdWalletsStartUaePassParams": "id_wallets.start_uae_pass_params", "IdWalletsStartVippsParams": "id_wallets.start_vipps_params", + "ImportJobSummary": "components.import_job_summary", + "ImportJobSummaryFaceTemplates": "components.import_job_summary", + "ImportJobSummaryProgress": "components.import_job_summary", + "ImportJobSummaryStartedBy": "components.import_job_summary", + "ImportProvider": "components.import_provider", + "ImportProviderCredentialFields": "components.import_provider", "InvestigatorAcceptRunParams": "investigator.accept_run_params", "InvestigatorBatchAcceptResponse": "components.investigator_batch_accept_response", "InvestigatorBatchAcceptResponseResults": "components.investigator_batch_accept_response", @@ -3842,8 +3922,18 @@ "SessionsDeleteParams": "sessions.delete_params", "SessionsDeleteResponse": "sessions.delete_response", "SessionsImportSharedParams": "sessions.import_shared_params", + "SessionsImportsCheckProviderParams": "sessions.imports.check_provider_params", + "SessionsImportsCheckProviderParamsOptions": "sessions.imports.check_provider_params", + "SessionsImportsCheckProviderResponse": "sessions.imports.check_provider_response", "SessionsImportsCreateParams": "sessions.imports.create_params", + "SessionsImportsCreateParamsOptions": "sessions.imports.create_params", + "SessionsImportsListActiveJobsResponse": "sessions.imports.list_active_jobs_response", "SessionsImportsListErrorsResponse": "sessions.imports.list_errors_response", + "SessionsImportsListJobsResponse": "sessions.imports.list_jobs_response", + "SessionsImportsListJobsResponseCounts": "sessions.imports.list_jobs_response", + "SessionsImportsListProvidersResponse": "sessions.imports.list_providers_response", + "SessionsImportsRetryParams": "sessions.imports.retry_params", + "SessionsImportsStartParams": "sessions.imports.start_params", "SessionsListAmlScreeningHistoryResponse": "sessions.list_aml_screening_history_response", "SessionsListEventsResponse": "sessions.list_events_response", "SessionsListFeatureOverridesResponse": "sessions.list_feature_overrides_response", @@ -4259,11 +4349,19 @@ "BankAccountCheckParams", "BankAccountCheckParamsAccount", "BankAccountCheckPersonHolder", + "BankAccountCheckPricing", "BankAccountCheckResponse", + "BankSignInPricing", + "BankSignInPricingAddOns", + "BankStatementUploadPricing", + "BankVerificationPricing", + "BankVerificationPricingCountry", + "BankVerificationPricingMethods", "BankVerificationResult", "BankVerificationResultAccount", "BankVerificationResultBilling", "BankVerificationResultConnection", + "BankVerificationResultContinuation", "BankVerificationResultFinancialData", "BankVerificationResultOwnership", "BankVerificationResultRoute", @@ -4590,6 +4688,12 @@ "IdWalletsStartSmartIdParams", "IdWalletsStartUaePassParams", "IdWalletsStartVippsParams", + "ImportJobSummary", + "ImportJobSummaryFaceTemplates", + "ImportJobSummaryProgress", + "ImportJobSummaryStartedBy", + "ImportProvider", + "ImportProviderCredentialFields", "InvestigatorAcceptRunParams", "InvestigatorBatchAcceptResponse", "InvestigatorBatchAcceptResponseResults", @@ -4956,8 +5060,18 @@ "SessionsDeleteParams", "SessionsDeleteResponse", "SessionsImportSharedParams", + "SessionsImportsCheckProviderParams", + "SessionsImportsCheckProviderParamsOptions", + "SessionsImportsCheckProviderResponse", "SessionsImportsCreateParams", + "SessionsImportsCreateParamsOptions", + "SessionsImportsListActiveJobsResponse", "SessionsImportsListErrorsResponse", + "SessionsImportsListJobsResponse", + "SessionsImportsListJobsResponseCounts", + "SessionsImportsListProvidersResponse", + "SessionsImportsRetryParams", + "SessionsImportsStartParams", "SessionsListAmlScreeningHistoryResponse", "SessionsListEventsResponse", "SessionsListFeatureOverridesResponse", diff --git a/src/didit/types/components/bank_account_check_pricing.py b/src/didit/types/components/bank_account_check_pricing.py new file mode 100644 index 0000000..5f502c5 --- /dev/null +++ b/src/didit/types/components/bank_account_check_pricing.py @@ -0,0 +1,16 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel + +__all__ = ["BankAccountCheckPricing"] + + +class BankAccountCheckPricing(BaseModel): + """Account check: the person types their account number and Didit checks it with the bank. Priced per country.""" + + available: bool + """`true` only where Didit serves the account check in this country in production.""" + + price_usd: str | None + """List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.""" diff --git a/src/didit/types/components/bank_sign_in_pricing.py b/src/didit/types/components/bank_sign_in_pricing.py new file mode 100644 index 0000000..323fcc0 --- /dev/null +++ b/src/didit/types/components/bank_sign_in_pricing.py @@ -0,0 +1,32 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel + +__all__ = ["BankSignInPricing", "BankSignInPricingAddOns"] + + +class BankSignInPricingAddOns(BaseModel): + """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.""" + + balance: str | None = None + """Account balance add-on.""" + + transactions: str | None = None + """Transactions add-on.""" + + +class BankSignInPricing(BaseModel): + """Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.""" + + available: bool + """`true` only where Didit serves bank sign-in in this country in production.""" + + market: str | None + """The market whose price applies. `null` when not available.""" + + price_usd: str | None + """List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.""" + + add_ons: BankSignInPricingAddOns + """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.""" diff --git a/src/didit/types/components/bank_statement_upload_pricing.py b/src/didit/types/components/bank_statement_upload_pricing.py new file mode 100644 index 0000000..5d841d0 --- /dev/null +++ b/src/didit/types/components/bank_statement_upload_pricing.py @@ -0,0 +1,19 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel + +__all__ = ["BankStatementUploadPricing"] + + +class BankStatementUploadPricing(BaseModel): + """How the bank statement upload is priced.""" + + price_usd: str + """Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.""" + + included_after_charged_bank_attempt: bool + """`true` when the upload is included at no extra charge after a charged bank attempt in the same session.""" + + standalone_available: bool + """`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.""" diff --git a/src/didit/types/components/bank_verification_pricing.py b/src/didit/types/components/bank_verification_pricing.py new file mode 100644 index 0000000..58b2cf3 --- /dev/null +++ b/src/didit/types/components/bank_verification_pricing.py @@ -0,0 +1,26 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from typing import Literal + +from didit._models import BaseModel +from didit.types.components.bank_statement_upload_pricing import BankStatementUploadPricing +from didit.types.components.bank_verification_pricing_country import BankVerificationPricingCountry + +__all__ = ["BankVerificationPricing"] + + +class BankVerificationPricing(BaseModel): + """Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.""" + + currency: Literal["USD"] | str + """Currency of every price in the response.""" + + statement_upload: BankStatementUploadPricing + """How the bank statement upload is priced.""" + + own_provider_fee_usd: str + """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.""" + + countries: dict[str, BankVerificationPricingCountry] + """Every country, keyed by ISO 3166-1 alpha-3 code.""" diff --git a/src/didit/types/components/bank_verification_pricing_country.py b/src/didit/types/components/bank_verification_pricing_country.py new file mode 100644 index 0000000..394c0bd --- /dev/null +++ b/src/didit/types/components/bank_verification_pricing_country.py @@ -0,0 +1,17 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel +from didit.types.components.bank_verification_pricing_methods import BankVerificationPricingMethods + +__all__ = ["BankVerificationPricingCountry"] + + +class BankVerificationPricingCountry(BaseModel): + """One country's Bank Verification methods and their list prices.""" + + name: str + """Country name in English.""" + + methods: BankVerificationPricingMethods + """The two Bank Verification methods in a country.""" diff --git a/src/didit/types/components/bank_verification_pricing_methods.py b/src/didit/types/components/bank_verification_pricing_methods.py new file mode 100644 index 0000000..49f5d7f --- /dev/null +++ b/src/didit/types/components/bank_verification_pricing_methods.py @@ -0,0 +1,18 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel +from didit.types.components.bank_account_check_pricing import BankAccountCheckPricing +from didit.types.components.bank_sign_in_pricing import BankSignInPricing + +__all__ = ["BankVerificationPricingMethods"] + + +class BankVerificationPricingMethods(BaseModel): + """The two Bank Verification methods in a country.""" + + open_banking: BankSignInPricing + """Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.""" + + account_lookup: BankAccountCheckPricing + """Account check: the person types their account number and Didit checks it with the bank. Priced per country.""" diff --git a/src/didit/types/components/bank_verification_result.py b/src/didit/types/components/bank_verification_result.py index 9a522be..f20bc20 100644 --- a/src/didit/types/components/bank_verification_result.py +++ b/src/didit/types/components/bank_verification_result.py @@ -11,6 +11,7 @@ "BankVerificationResultAccount", "BankVerificationResultBilling", "BankVerificationResultConnection", + "BankVerificationResultContinuation", "BankVerificationResultFinancialData", "BankVerificationResultOwnership", "BankVerificationResultRoute", @@ -58,7 +59,7 @@ class BankVerificationResultBilling(BaseModel): """The marketplace platform fee charged, for `byok`.""" usage_type: str | None = None - """The usage line the charge was booked on.""" + """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.""" class BankVerificationResultConnection(BaseModel): @@ -107,6 +108,32 @@ class BankVerificationResultConnection(BaseModel): """Why the connection is not simply `connected`, for example `USER_CANCELLED`, `INSTITUTION_DOWN`, `ACCOUNT_NOT_FOUND`, `REFERENCE_IDENTITY_MISSING` or `VERIFICATION_COMPLETED`.""" +class BankVerificationResultContinuation(BaseModel): + """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`.""" + + reason: ( + Literal["no_route", "institution_unsupported", "attempts_exhausted", "person_choice"] + | str + | None + ) = None + """Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step.""" + + country: str | None = None + """The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one.""" + + offered: list[Literal["statement", "microdeposits"] | str] | None = None + """The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none.""" + + institution_id: str | None = None + """For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason.""" + + institution_name: str | None = None + """For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason.""" + + continued_at: _dt.datetime | None = None + """When the person continued.""" + + class BankVerificationResultFinancialData(BaseModel): """The financial data the step read. Only bank sign-in reads it.""" @@ -208,7 +235,7 @@ class BankVerificationResultRoute(BaseModel): class BankVerificationResult(BaseModel): - """One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified. See [Bank verification methods](/core-technology/bank-verification/verification-methods).""" + """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).""" schema_version: int | None = None """Version of this result shape. `1` today.""" @@ -227,10 +254,10 @@ class BankVerificationResult(BaseModel): original_status: str | None = None """The first verdict the step reached, before any later change such as a manual review.""" - method: Literal["open_banking", "account_lookup", "microdeposits", "statement"] | str | None = ( - None - ) - """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). `null` until an account is evaluated. Workflow rules read the same value as `bank.method`.""" + method: ( + Literal["open_banking", "account_lookup", "microdeposits", "statement", "none"] | str | None + ) = None + """How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`.""" provider: str | None = None """The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified.""" @@ -265,7 +292,13 @@ class BankVerificationResult(BaseModel): """Whether the step ended without a match after the person left at their bank.""" not_covered: bool | None = None - """Whether the person's bank or country could not be reached on this step.""" + """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: bool | None = None + """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: BankVerificationResultContinuation | None = None + """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`.""" evidence_purged_at: _dt.datetime | None = None """When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise.""" @@ -274,7 +307,7 @@ class BankVerificationResult(BaseModel): """Informational codes, for example `holder_declared`. A warning never changes the status on its own.""" reason_codes: list[str] | None = None - """Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply.""" + """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: _dt.datetime | None = None diff --git a/src/didit/types/components/import_job_summary.py b/src/didit/types/components/import_job_summary.py new file mode 100644 index 0000000..9a082e7 --- /dev/null +++ b/src/didit/types/components/import_job_summary.py @@ -0,0 +1,99 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +import datetime as _dt +from typing import Literal + +from didit._models import BaseModel + +__all__ = [ + "ImportJobSummary", + "ImportJobSummaryFaceTemplates", + "ImportJobSummaryProgress", + "ImportJobSummaryStartedBy", +] + + +class ImportJobSummaryFaceTemplates(BaseModel): + enabled: bool | None = None + + built: int | None = None + + duplicates: int | None = None + + blocklisted: int | None = None + + +class ImportJobSummaryProgress(BaseModel): + total: int | None = None + + processed: int | None = None + + imported: int | None = None + + updated: int | None = None + + already_in_didit: int | None = None + + failed: int | None = None + + not_finished: int | None = None + + same_person: int | None = None + + +class ImportJobSummaryStartedBy(BaseModel): + name: str | None = None + + email: str | None = None + + +class ImportJobSummary(BaseModel): + """One import job of any kind, in the shape the console's Import jobs drawer reads.""" + + id: str | None = None + + kind: ( + Literal["sessions", "businesses", "users", "status_rules", "transactions"] | str | None + ) = None + + source: Literal["file", "api", "provider"] | str | None = None + + provider: str | None = None + + provider_name: str | None = None + + filename: str | None = None + + state: ( + Literal[ + "ready", "queued", "running", "paused", "completed", "errors", "failed", "cancelled" + ] + | str + | None + ) = None + """The job's state as the console shows it; `errors` is a completed job with rejected rows.""" + + status: str | None = None + + is_dry_run: bool | None = None + + credential_hint: str | None = None + + resumes_at: _dt.datetime | None = None + + last_error: str | None = None + + started_by: ImportJobSummaryStartedBy | None = None + + progress: ImportJobSummaryProgress | None = None + + face_templates: ImportJobSummaryFaceTemplates | None = None + + eta_seconds: int | None = None + + created_at: _dt.datetime | None = None + + started_at: _dt.datetime | None = None + + completed_at: _dt.datetime | None = None diff --git a/src/didit/types/components/import_provider.py b/src/didit/types/components/import_provider.py new file mode 100644 index 0000000..136442e --- /dev/null +++ b/src/didit/types/components/import_provider.py @@ -0,0 +1,64 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from typing import Literal + +from didit._models import BaseModel + +__all__ = ["ImportProvider", "ImportProviderCredentialFields"] + + +class ImportProviderCredentialFields(BaseModel): + name: str | None = None + + label: str | None = None + + secret: bool | None = None + + +class ImportProvider(BaseModel): + key: ( + Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str + | None + ) = None + + name: str | None = None + + mode: Literal["api_list", "api_ids", "export_file"] | str | None = None + """`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: str | None = None + + credential_fields: list[ImportProviderCredentialFields] | None = None + + regions: list[str] | None = None + + required_permissions: list[str] | None = None + + docs_url: str | None = None + + status_mapping: ( + dict[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] + | str, + ] + | None + ) = None diff --git a/src/didit/types/components/session_decision.py b/src/didit/types/components/session_decision.py index 4b3ece3..c43c4ed 100644 --- a/src/didit/types/components/session_decision.py +++ b/src/didit/types/components/session_decision.py @@ -3556,7 +3556,7 @@ class SessionDecision(BaseModel): """**Array of questionnaire response reports** - always a JSON array, never a singular `questionnaire` object. `null` until at least one questionnaire step has produced data. Each item is the full report, including `node_id`, the questionnaire metadata, sections with form elements, and the user's answers.""" bank_verifications: list[BankVerificationResult] | None = None - """Bank Verification results, one item per Bank Verification step, for User and Business Verification. `null` until a step produces one. Each item's `method` says how the account was verified: `open_banking` (bank sign-in) or `account_lookup` (account check), or a `microdeposits` or `statement` fallback. See [Bank verification methods](/core-technology/bank-verification/verification-methods).""" + """Bank Verification results, one item per Bank Verification step, for User and Business Verification. `null` until a step produces one. Each item's `method` says how the account was verified: `open_banking` (bank sign-in) or `account_lookup` (account check), or a `statement` or `microdeposits` fallback, and `none` when the person continued without a bank before any attempt was evaluated (`continued_without_bank`). See [Bank verification methods](/core-technology/bank-verification/verification-methods).""" signatures: list[SignatureResult] | None = None """Electronic Signature results, one item per signer and signature step, for User and Business Verification. Present only when the workflow has an Electronic Signature step; an empty array until a signer reaches it. See the [Electronic Signature report](/core-technology/electronic-signature/report-electronic-signature).""" diff --git a/src/didit/types/components/session_import_job.py b/src/didit/types/components/session_import_job.py index 85a59a7..056e163 100644 --- a/src/didit/types/components/session_import_job.py +++ b/src/didit/types/components/session_import_job.py @@ -13,7 +13,10 @@ class SessionImportJob(BaseModel): uuid: str """Import job id. Use this value to poll progress and fetch row errors.""" - status: Literal["PENDING", "PROCESSING", "COMPLETED", "FAILED", "PAUSED", "CANCELLED"] | str + status: ( + Literal["PENDING", "PROCESSING", "COMPLETED", "FAILED", "PAUSED", "CANCELLED", "READY"] + | str + ) import_type: ( Literal["user_verification", "business_verification", "status_rules", "transactions"] | str @@ -48,3 +51,52 @@ class SessionImportJob(BaseModel): started_at: _dt.datetime | None = None completed_at: _dt.datetime | None = None + + state: ( + Literal[ + "ready", "queued", "running", "paused", "completed", "errors", "failed", "cancelled" + ] + | str + | None + ) = None + """The job's state as the console shows it; `errors` is a completed job with rejected rows.""" + + source: Literal["file", "api", "provider"] | str | None = None + + provider: str | None = None + """Migration source; empty for a file or API import.""" + + provider_options: dict[str, object] | None = None + + credential_hint: str | None = None + """The first characters of the key the migration holds. The key itself is never returned.""" + + credentials_expires_at: _dt.datetime | None = None + + resumes_at: _dt.datetime | None = None + """When a job paused by the provider's rate limit resumes by itself.""" + + status_mapping: dict[str, str] | None = None + + is_dry_run: bool | None = None + + dry_run_summary: dict[str, object] | None = None + """What a dry run found: `total`, `by_status`, `already_in_didit`, `same_person`, `not_finished`, `failed`, `unmapped_statuses`.""" + + face_templates_enabled: bool | None = None + + lawful_basis_confirmed_at: _dt.datetime | None = None + + not_finished_count: int | None = None + + same_person_count: int | None = None + + face_templates_count: int | None = None + + face_duplicates_count: int | None = None + + face_blocklist_hits_count: int | None = None + + created_by_name: str | None = None + + created_by_email: str | None = None diff --git a/src/didit/types/components/workflow_detail.py b/src/didit/types/components/workflow_detail.py index 102ab83..31ceae4 100644 --- a/src/didit/types/components/workflow_detail.py +++ b/src/didit/types/components/workflow_detail.py @@ -97,3 +97,15 @@ class WorkflowDetail(BaseModel): created_at: _dt.datetime | None = None updated_at: _dt.datetime | None = None + + is_bank_verification_enabled: bool | None = None + """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.""" + + bank_verification_config: dict[str, object] | None = None + """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).""" + + is_geolocation_enabled: bool | None = None + """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.""" + + geolocation_config: dict[str, object] | None = None + """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.""" diff --git a/src/didit/types/sessions/imports/check_provider_params.py b/src/didit/types/sessions/imports/check_provider_params.py new file mode 100644 index 0000000..a6e0243 --- /dev/null +++ b/src/didit/types/sessions/imports/check_provider_params.py @@ -0,0 +1,45 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from typing import Literal, Mapping, Sequence + +from typing_extensions import Required, TypedDict + +__all__ = ["SessionsImportsCheckProviderParams", "SessionsImportsCheckProviderParamsOptions"] + + +class SessionsImportsCheckProviderParamsOptions(TypedDict, total=False): + region: str + """The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`).""" + + +class SessionsImportsCheckProviderParams(TypedDict, total=False): + provider: Required[ + Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str + ] + """Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`.""" + + credentials: Required[Mapping[str, str]] + """The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart.""" + + options: SessionsImportsCheckProviderParamsOptions + + record_ids: Sequence[str] + """For `api_ids` providers: the first record ID of your export.""" diff --git a/src/didit/types/sessions/imports/check_provider_response.py b/src/didit/types/sessions/imports/check_provider_response.py new file mode 100644 index 0000000..9acba2e --- /dev/null +++ b/src/didit/types/sessions/imports/check_provider_response.py @@ -0,0 +1,14 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel + +__all__ = ["SessionsImportsCheckProviderResponse"] + + +class SessionsImportsCheckProviderResponse(BaseModel): + ok: bool | None = None + + provider: str | None = None + + credential_hint: str | None = None diff --git a/src/didit/types/sessions/imports/create_params.py b/src/didit/types/sessions/imports/create_params.py index 16a729f..f38617e 100644 --- a/src/didit/types/sessions/imports/create_params.py +++ b/src/didit/types/sessions/imports/create_params.py @@ -1,13 +1,18 @@ # Code generated by codegen from openapi/*.json. DO NOT EDIT. from __future__ import annotations -from typing import Literal +from typing import Literal, Mapping from typing_extensions import TypedDict from didit._types import FileTypes -__all__ = ["SessionsImportsCreateParams"] +__all__ = ["SessionsImportsCreateParams", "SessionsImportsCreateParamsOptions"] + + +class SessionsImportsCreateParamsOptions(TypedDict, total=False): + region: str + """The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`).""" class SessionsImportsCreateParams(TypedDict, total=False): @@ -25,5 +30,50 @@ class SessionsImportsCreateParams(TypedDict, total=False): source_file_url: str """Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`.""" + source: Literal["file", "provider"] | str + """`provider` makes this a migration from another identity verification provider. Omit for a file import.""" + + provider: ( + Literal[ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other", + ] + | str + ) + """Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`.""" + + credentials: Mapping[str, str] + """The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart.""" + + options: SessionsImportsCreateParamsOptions + + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] | str, + ] + """Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported.""" + + dry_run: bool + """Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`.""" + + face_templates: bool + """Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`.""" + + lawful_basis_confirmed: bool + """Confirms you have a lawful basis to share these selfies with Didit as processor.""" + file: FileTypes """Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`.""" diff --git a/src/didit/types/sessions/imports/list_active_jobs_response.py b/src/didit/types/sessions/imports/list_active_jobs_response.py new file mode 100644 index 0000000..8f61825 --- /dev/null +++ b/src/didit/types/sessions/imports/list_active_jobs_response.py @@ -0,0 +1,13 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel +from didit.types.components.import_job_summary import ImportJobSummary + +__all__ = ["SessionsImportsListActiveJobsResponse"] + + +class SessionsImportsListActiveJobsResponse(BaseModel): + results: list[ImportJobSummary] | None = None + + running: int | None = None diff --git a/src/didit/types/sessions/imports/list_jobs_response.py b/src/didit/types/sessions/imports/list_jobs_response.py new file mode 100644 index 0000000..9f6a231 --- /dev/null +++ b/src/didit/types/sessions/imports/list_jobs_response.py @@ -0,0 +1,23 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel +from didit.types.components.import_job_summary import ImportJobSummary + +__all__ = ["SessionsImportsListJobsResponse", "SessionsImportsListJobsResponseCounts"] + + +class SessionsImportsListJobsResponseCounts(BaseModel): + all: int | None = None + + running_and_waiting: int | None = None + + needs_attention: int | None = None + + done: int | None = None + + +class SessionsImportsListJobsResponse(BaseModel): + results: list[ImportJobSummary] | None = None + + counts: SessionsImportsListJobsResponseCounts | None = None diff --git a/src/didit/types/sessions/imports/list_providers_response.py b/src/didit/types/sessions/imports/list_providers_response.py new file mode 100644 index 0000000..d784943 --- /dev/null +++ b/src/didit/types/sessions/imports/list_providers_response.py @@ -0,0 +1,15 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from didit._models import BaseModel +from didit.types.components.import_provider import ImportProvider + +__all__ = ["SessionsImportsListProvidersResponse"] + + +class SessionsImportsListProvidersResponse(BaseModel): + providers: list[ImportProvider] | None = None + + mapping_targets: list[str] | None = None + + skipped_statuses: list[str] | None = None diff --git a/src/didit/types/sessions/imports/retry_params.py b/src/didit/types/sessions/imports/retry_params.py new file mode 100644 index 0000000..eb560a3 --- /dev/null +++ b/src/didit/types/sessions/imports/retry_params.py @@ -0,0 +1,18 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from typing import Mapping + +from typing_extensions import TypedDict + +from didit._types import FileTypes + +__all__ = ["SessionsImportsRetryParams"] + + +class SessionsImportsRetryParams(TypedDict, total=False): + credentials: Mapping[str, str] + """The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart.""" + + file: FileTypes + """The record IDs or export file again, for `api_ids` and `export_file` providers.""" diff --git a/src/didit/types/sessions/imports/start_params.py b/src/didit/types/sessions/imports/start_params.py new file mode 100644 index 0000000..801c75c --- /dev/null +++ b/src/didit/types/sessions/imports/start_params.py @@ -0,0 +1,22 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +from __future__ import annotations + +from typing import Literal, Mapping + +from typing_extensions import TypedDict + +__all__ = ["SessionsImportsStartParams"] + + +class SessionsImportsStartParams(TypedDict, total=False): + status_mapping: Mapping[ + str, + Literal["Approved", "Declined", "In Review", "Expired", "Not Finished", "Abandoned"] | str, + ] + """Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported.""" + + face_templates: bool + """Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`.""" + + lawful_basis_confirmed: bool + """Confirms you have a lawful basis to share these selfies with Didit as processor.""" diff --git a/tests/generated/test_pricing.py b/tests/generated/test_pricing.py new file mode 100644 index 0000000..26910f8 --- /dev/null +++ b/tests/generated/test_pricing.py @@ -0,0 +1,59 @@ +# Code generated by codegen from openapi/*.json. DO NOT EDIT. +"""Generated tests of the ``pricing`` resources: one per operation.""" + +from __future__ import annotations + +from didit.types.components.bank_verification_pricing import BankVerificationPricing +from tests.harness import Expect, check_operation + + +def test_pricing_bank_verification() -> None: + """``GET /v3/organization/bank-verification-pricing/``""" + check_operation( + Expect( + method="GET", + path="/v3/organization/bank-verification-pricing/", + api="verification", + auth="none", + status=200, + response_json={ + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": True, + "standalone_available": False, + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": True, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": {"balance": "0.3500", "transactions": "1.3000"}, + }, + "account_lookup": {"available": True, "price_usd": "0.4000"}, + }, + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": False, + "market": None, + "price_usd": None, + "add_ons": {}, + }, + "account_lookup": {"available": False, "price_usd": None}, + }, + }, + }, + }, + ), + lambda c: c.pricing.bank_verification(), + lambda c: c.pricing.bank_verification(), + lambda r: isinstance(r, BankVerificationPricing), + lambda r: isinstance(r, BankVerificationPricing), + ) diff --git a/tests/generated/test_sessions.py b/tests/generated/test_sessions.py index bd7195d..1ed8be6 100644 --- a/tests/generated/test_sessions.py +++ b/tests/generated/test_sessions.py @@ -25,6 +25,16 @@ ) from didit.types.sessions.create_response import SessionsCreateResponse from didit.types.sessions.delete_response import SessionsDeleteResponse +from didit.types.sessions.imports.check_provider_response import ( + SessionsImportsCheckProviderResponse, +) +from didit.types.sessions.imports.list_active_jobs_response import ( + SessionsImportsListActiveJobsResponse, +) +from didit.types.sessions.imports.list_jobs_response import SessionsImportsListJobsResponse +from didit.types.sessions.imports.list_providers_response import ( + SessionsImportsListProvidersResponse, +) from didit.types.sessions.list_feature_overrides_response import ( SessionsListFeatureOverridesResponse, ) @@ -3157,6 +3167,87 @@ def test_sessions_update_status() -> None: ) +def test_sessions_imports_cancel() -> None: + """``POST /v3/session/imports/{importId}/cancel/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/00000000-0000-4000-8000-000000000001/cancel/", + api="verification", + auth="api_key", + status=200, + response_json={ + "uuid": "00000000-0000-4000-8000-000000000003", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "csv", + "source_filename": "sample", + "total_rows": 1, + "processed_rows": 1, + "checkpoint_row": 1, + "created_count": 1, + "updated_count": 1, + "skipped_count": 1, + "failed_count": 1, + "total_media_bytes": 1, + "last_error": "sample", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", + }, + strict_type=SessionImportJob, + ), + lambda c: c.sessions.imports.cancel("00000000-0000-4000-8000-000000000001"), + lambda c: c.sessions.imports.cancel("00000000-0000-4000-8000-000000000001"), + lambda r: isinstance(r, SessionImportJob), + lambda r: isinstance(r, SessionImportJob), + ) + + +def test_sessions_imports_check_provider() -> None: + """``POST /v3/session/imports/providers/check/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/providers/check/", + api="verification", + auth="api_key", + json_body={"provider": "sumsub", "credentials": {"key": "sample"}}, + status=200, + response_json={"ok": True, "provider": "sample", "credential_hint": "sample-secret"}, + strict_type=SessionsImportsCheckProviderResponse, + ), + lambda c: c.sessions.imports.check_provider( + provider="sumsub", credentials={"key": "sample"} + ), + lambda c: c.sessions.imports.check_provider( + provider="sumsub", credentials={"key": "sample"} + ), + lambda r: isinstance(r, SessionsImportsCheckProviderResponse), + lambda r: isinstance(r, SessionsImportsCheckProviderResponse), + ) + + def test_sessions_imports_create() -> None: """``POST /v3/session/imports/``""" check_operation( @@ -3207,6 +3298,64 @@ def test_sessions_imports_create() -> None: ) +def test_sessions_imports_list_active_jobs() -> None: + """``GET /v3/import-jobs/active/``""" + check_operation( + Expect( + method="GET", + path="/v3/import-jobs/active/", + api="verification", + auth="api_key", + status=200, + response_json={ + "results": [ + { + "id": "00000000-0000-4000-8000-000000000005", + "kind": "sessions", + "source": "file", + "provider": "sample", + "provider_name": "sample", + "filename": "sample", + "state": "ready", + "status": "sample", + "is_dry_run": True, + "credential_hint": "sample-secret", + "resumes_at": "2026-01-01T00:00:00Z", + "last_error": "sample", + "started_by": {"name": "sample", "email": "sample"}, + "progress": { + "total": 1, + "processed": 1, + "imported": 1, + "updated": 1, + "already_in_didit": 1, + "failed": 1, + "not_finished": 1, + "same_person": 1, + }, + "face_templates": { + "enabled": True, + "built": 1, + "duplicates": 1, + "blocklisted": 1, + }, + "eta_seconds": 1, + "created_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + } + ], + "running": 1, + }, + strict_type=SessionsImportsListActiveJobsResponse, + ), + lambda c: c.sessions.imports.list_active_jobs(), + lambda c: c.sessions.imports.list_active_jobs(), + lambda r: isinstance(r, SessionsImportsListActiveJobsResponse), + lambda r: isinstance(r, SessionsImportsListActiveJobsResponse), + ) + + def test_sessions_imports_list_errors() -> None: """``GET /v3/session/imports/{importId}/errors/``""" check_operation( @@ -3252,6 +3401,216 @@ def test_sessions_imports_list_errors() -> None: ) +def test_sessions_imports_list_jobs() -> None: + """``GET /v3/import-jobs/``""" + check_operation( + Expect( + method="GET", + path="/v3/import-jobs/", + api="verification", + auth="api_key", + query={"scope": ["all"], "limit": ["1"]}, + status=200, + response_json={ + "results": [ + { + "id": "00000000-0000-4000-8000-000000000005", + "kind": "sessions", + "source": "file", + "provider": "sample", + "provider_name": "sample", + "filename": "sample", + "state": "ready", + "status": "sample", + "is_dry_run": True, + "credential_hint": "sample-secret", + "resumes_at": "2026-01-01T00:00:00Z", + "last_error": "sample", + "started_by": {"name": "sample", "email": "sample"}, + "progress": { + "total": 1, + "processed": 1, + "imported": 1, + "updated": 1, + "already_in_didit": 1, + "failed": 1, + "not_finished": 1, + "same_person": 1, + }, + "face_templates": { + "enabled": True, + "built": 1, + "duplicates": 1, + "blocklisted": 1, + }, + "eta_seconds": 1, + "created_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + } + ], + "counts": {"all": 1, "running_and_waiting": 1, "needs_attention": 1, "done": 1}, + }, + strict_type=SessionsImportsListJobsResponse, + ), + lambda c: c.sessions.imports.list_jobs(scope="all", limit=1), + lambda c: c.sessions.imports.list_jobs(scope="all", limit=1), + lambda r: isinstance(r, SessionsImportsListJobsResponse), + lambda r: isinstance(r, SessionsImportsListJobsResponse), + ) + + +def test_sessions_imports_list_providers() -> None: + """``GET /v3/session/imports/providers/``""" + check_operation( + Expect( + method="GET", + path="/v3/session/imports/providers/", + api="verification", + auth="api_key", + status=200, + response_json={ + "providers": [ + { + "key": "sumsub", + "name": "sample", + "mode": "api_list", + "record_noun": "sample", + "credential_fields": [ + {"name": "sample", "label": "sample", "secret": True} + ], + "regions": ["sample"], + "required_permissions": ["sample"], + "docs_url": "sample", + "status_mapping": {"key": "Approved"}, + } + ], + "mapping_targets": ["sample"], + "skipped_statuses": ["sample"], + }, + strict_type=SessionsImportsListProvidersResponse, + ), + lambda c: c.sessions.imports.list_providers(), + lambda c: c.sessions.imports.list_providers(), + lambda r: isinstance(r, SessionsImportsListProvidersResponse), + lambda r: isinstance(r, SessionsImportsListProvidersResponse), + ) + + +def test_sessions_imports_pause() -> None: + """``POST /v3/session/imports/{importId}/pause/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/00000000-0000-4000-8000-000000000001/pause/", + api="verification", + auth="api_key", + status=200, + response_json={ + "uuid": "00000000-0000-4000-8000-000000000003", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "csv", + "source_filename": "sample", + "total_rows": 1, + "processed_rows": 1, + "checkpoint_row": 1, + "created_count": 1, + "updated_count": 1, + "skipped_count": 1, + "failed_count": 1, + "total_media_bytes": 1, + "last_error": "sample", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", + }, + strict_type=SessionImportJob, + ), + lambda c: c.sessions.imports.pause("00000000-0000-4000-8000-000000000001"), + lambda c: c.sessions.imports.pause("00000000-0000-4000-8000-000000000001"), + lambda r: isinstance(r, SessionImportJob), + lambda r: isinstance(r, SessionImportJob), + ) + + +def test_sessions_imports_resume() -> None: + """``POST /v3/session/imports/{importId}/resume/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/00000000-0000-4000-8000-000000000001/resume/", + api="verification", + auth="api_key", + status=200, + response_json={ + "uuid": "00000000-0000-4000-8000-000000000003", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "csv", + "source_filename": "sample", + "total_rows": 1, + "processed_rows": 1, + "checkpoint_row": 1, + "created_count": 1, + "updated_count": 1, + "skipped_count": 1, + "failed_count": 1, + "total_media_bytes": 1, + "last_error": "sample", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", + }, + strict_type=SessionImportJob, + ), + lambda c: c.sessions.imports.resume("00000000-0000-4000-8000-000000000001"), + lambda c: c.sessions.imports.resume("00000000-0000-4000-8000-000000000001"), + lambda r: isinstance(r, SessionImportJob), + lambda r: isinstance(r, SessionImportJob), + ) + + def test_sessions_imports_retrieve() -> None: """``GET /v3/session/imports/{importId}/``""" check_operation( @@ -3280,6 +3639,25 @@ def test_sessions_imports_retrieve() -> None: "updated_at": "2026-01-01T00:00:00Z", "started_at": "2026-01-01T00:00:00Z", "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", }, strict_type=SessionImportJob, ), @@ -3310,6 +3688,131 @@ def test_sessions_imports_retrieve_template() -> None: ) +def test_sessions_imports_retry() -> None: + """``POST /v3/session/imports/{importId}/retry/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/00000000-0000-4000-8000-000000000001/retry/", + api="verification", + auth="api_key", + multipart_fields=["credentials"], + multipart_files=[], + status=201, + response_json={ + "uuid": "00000000-0000-4000-8000-000000000003", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "csv", + "source_filename": "sample", + "total_rows": 1, + "processed_rows": 1, + "checkpoint_row": 1, + "created_count": 1, + "updated_count": 1, + "skipped_count": 1, + "failed_count": 1, + "total_media_bytes": 1, + "last_error": "sample", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", + }, + strict_type=SessionImportJob, + ), + lambda c: c.sessions.imports.retry( + "00000000-0000-4000-8000-000000000001", credentials={"key": "sample"} + ), + lambda c: c.sessions.imports.retry( + "00000000-0000-4000-8000-000000000001", credentials={"key": "sample"} + ), + lambda r: isinstance(r, SessionImportJob), + lambda r: isinstance(r, SessionImportJob), + ) + + +def test_sessions_imports_start() -> None: + """``POST /v3/session/imports/{importId}/start/``""" + check_operation( + Expect( + method="POST", + path="/v3/session/imports/00000000-0000-4000-8000-000000000001/start/", + api="verification", + auth="api_key", + json_body={"status_mapping": {"key": "Approved"}}, + status=200, + response_json={ + "uuid": "00000000-0000-4000-8000-000000000003", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "csv", + "source_filename": "sample", + "total_rows": 1, + "processed_rows": 1, + "checkpoint_row": 1, + "created_count": 1, + "updated_count": 1, + "skipped_count": 1, + "failed_count": 1, + "total_media_bytes": 1, + "last_error": "sample", + "created_at": "2026-01-01T00:00:00Z", + "updated_at": "2026-01-01T00:00:00Z", + "started_at": "2026-01-01T00:00:00Z", + "completed_at": "2026-01-01T00:00:00Z", + "state": "ready", + "source": "file", + "provider": "sample", + "provider_options": {}, + "credential_hint": "sample-secret", + "credentials_expires_at": "2026-01-01T00:00:00Z", + "resumes_at": "2026-01-01T00:00:00Z", + "status_mapping": {"key": "sample"}, + "is_dry_run": True, + "dry_run_summary": {}, + "face_templates_enabled": True, + "lawful_basis_confirmed_at": "2026-01-01T00:00:00Z", + "not_finished_count": 1, + "same_person_count": 1, + "face_templates_count": 1, + "face_duplicates_count": 1, + "face_blocklist_hits_count": 1, + "created_by_name": "sample", + "created_by_email": "sample", + }, + strict_type=SessionImportJob, + ), + lambda c: c.sessions.imports.start( + "00000000-0000-4000-8000-000000000001", status_mapping={"key": "Approved"} + ), + lambda c: c.sessions.imports.start( + "00000000-0000-4000-8000-000000000001", status_mapping={"key": "Approved"} + ), + lambda r: isinstance(r, SessionImportJob), + lambda r: isinstance(r, SessionImportJob), + ) + + def test_sessions_reviews_create() -> None: """``POST /v3/sessions/{session_id}/reviews/``""" check_operation( diff --git a/tests/generated/test_workflows.py b/tests/generated/test_workflows.py index e252f29..69d41ad 100644 --- a/tests/generated/test_workflows.py +++ b/tests/generated/test_workflows.py @@ -292,6 +292,10 @@ def test_workflows_update() -> None: "kyb_role_settings": [{}], "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", + "is_bank_verification_enabled": True, + "bank_verification_config": {}, + "is_geolocation_enabled": True, + "geolocation_config": {}, }, strict_type=WorkflowDetail, ),