# Didit Analytics, Teams and Roles
Source: https://docs.didit.me/academy/analytics-teams-roles
Video transcript: the operator's guide to Didit analytics dashboards, team management, roles and permissions.
Full transcript of **Didit Analytics, Teams and Roles - The Operator's Guide** - Didit Academy video 9 of 10, 06:37. [Watch on YouTube](https://www.youtube.com/watch?v=sRcOBxjRYag). Each paragraph links to the exact moment in the video.
## What this video covers
[00:04](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=4s) This video covers two things every operator needs once Didit is live. The first is seeing how your account is actually performing and the second is controlling who on your team can do what. So, on the analytic side, if you're running transaction monitoring, Didit turns all the activity into a live dashboard of numbers that you can act on. Then, we'll move over to the team side where you decide who gets to see and change each part of the account. So, let's dive in.
## Where analytics live
[00:36](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=36s) So, the analytics in Didit live in the transactions product. So, we head over to the transactions tab here on the left and we could see at the top we can hit overview, right? So, instead of reading one row at a time, you're reading numbers that you can actually act on. So, if you're only running verifications, you'll spend your time in the sessions list from previous videos, which you can see in the user verifications tab here, or you can see the overview in your dashboard on your home page. You can also add these cards to your home page, but this is
## Date ranges, filters and export
[01:13](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=73s) specifically about your transactions. Now, we can actually change the the date range here. We can filter by tag. We can filter by type, so that's fiat, crypto, KYC, gambling, all of those. And we can actually export the the report here. So, that's just going to give you an Excel export that you can do
## Volume, geography and channel split
[01:31](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=91s) things with. You can put it into Claude. You can create fancy reports to share with your team. So, the first numbers that you can check out here is volume, so how many transactions run over your time window here, and whether that's trending up or down against the period before. So, you can see there's a breakdown here by the country of the user behind each transaction. We've got the split down here between fiat, crypto, and gambling. So, really you could see at a glance where your activity is concentrated and which side
## Rule analytics and the review trend
[02:01](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=121s) of the house it's coming from. Now, we can also see the analytics for our rules. So, you could see which monitoring rules are firing the most and how your approved, declined, and in review mix. So, you see this card right here, this shows your top rules that have been triggered. So, you could see which monitoring rules are firing the most and how your approved, declined, and in review mix is trending over time.
[02:25](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=145s) So, a rising in review line is your early warning that more work is about to
## Customizing your home dashboard
[02:31](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=151s) land on your analysts. So, you can spot risk forming rather than counting it after the fact. So, this is the best place to go to see your analytics on your transactions and for your verifications, we can customize our home page to show the volume of verifications, IP locations, all of this stuff. And if you want to customize that, we can go to edit and we can move around these tabs if we want and we can also add additional tabs from user verifications and business verifications. You can throw in some transaction data as well if you want if you want everything in one dashboard.
[03:08](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=188s) So, let's just switch from measuring to governing and the first thing to
## Organizations
[03:12](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=192s) understand is that everything in Didit lives inside an organization. So, that's the account that holds your billing, your people, and your applications. So, you see this switcher down here, you can jump between organizations here. Now, I only have one organization set up, but you could create a separate organization as well. Now, the account
## Account settings and the audit log
[03:31](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=211s) settings holds the organizational wide settings. So, your plan, your invoices, an audit log of who did what, which is exactly what you want for compliance and incident review. Now, inside each
## Applications, live and sandbox
[03:43](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=223s) organization, you can create multiple applications. So, if I had an additional application here, you know, I can create a new one with this tab, it's going to show up inside of here. We're going to have live and sandbox for each. Now, if you want to switch to sandbox, that's coming soon. It's going to have its own API keys, its own workflows, and its own webhooks for testing. So, let's head over to the
## Team members and roles
[04:04](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=244s) settings tab down in manage. We've got our team members in here as well. And each person carries exactly one role. And this is important, right? It's the role, not the person, that decides what they can see and do. You can see here, this is me. And you can see there's no settings cog here. So, I can't accidentally come in here and change my role and lock myself out of my own account. But, if I do click into here from somebody else, I can change them from owner to developer to compliance to admin, all of the different options here. So,
## Inviting a teammate
[04:38](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=278s) if I want to add a new team member, just click this tab up here. I can add in their email address and select the applications that they're going to have access to and the different kinds of access that we want to give them. Now, until they accept, you'll see a orange pending badge, which means the invite went out, but the person has no access at all yet. So, every organization comes
## The five built-in roles
[05:01](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=301s) with five roles out of the box, and you just match the role to the person. The owner has full access, so including billing, that's usually just you, the owner of the business, or select few people on your team. Admin runs the members, the applications, the workflows, basically everything apart from billing or deleting the organization. We've got compliance officer. This is built for your risk people. So, reviewing verifications, cases, and screening with no developer or billing access at all. Developer covers the APIs, the webhooks, the applications. And the reader down here is read-only on sessions, analytics, and reports, which makes it the safest role for observers and executives who just
## Custom roles
[05:41](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=341s) need to look. Now, if none of those roles suit the needs of someone on your team, we can actually create a custom role and define the permissions that we want to give them. And that can be done here.
## Recap
[05:54](https://www.youtube.com/watch?v=sRcOBxjRYag\&t=354s) Finally, at the bottom here, we can see how many members have access to the specific role. So, that's the operators view of Didit, an analytics side that shows you how your transaction activity is really performing, and a team side that lets you control exactly who can see and change what. So, between the two, you can measure the business and keep it locked down at the same time. And if you'd rather work with the numbers in your own tools, all of it is available over the API and the MCP. So, head over to didit.me, sign up for free, and invite your first teammate today.
# Build a KYC Workflow With No Code
Source: https://docs.didit.me/academy/build-kyc-workflow
Video transcript: building a KYC verification workflow visually - checks, branching logic, risk rules, testing, and publishing to production.
Full transcript of **Build a KYC Verification Workflow With No Code (Full Tutorial)** - Didit Academy video 3 of 10, 18:54. [Watch on YouTube](https://www.youtube.com/watch?v=7-nJ3JE20i4). Each paragraph links to the exact moment in the video.
## What a workflow is
[00:03](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=3s) In this video, we're going to be building a complete verification flow from scratch, and you won't need to write a single line of code. A workflow is really just a recipe for a verification, which checks to run and in what order. Normally, that kind of thing lives with your engineers. But in Didit, you build it yourself visually. You watch the price update as you go, and you push it live whenever you're ready.
[00:28](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=28s) If you want to build one yourself, you can head over to didit.me, sign up for free, and put your first workflow together in just a few minutes. All right, so let's start where every verification flow lives, and that's in
## The workflow list and workflow IDs
[00:39](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=39s) the workflow list underneath the configure tab here. So all of the workflows that we've currently got in this account, you can see the type. We've got KYC and KYB. We've got simple and graph, and we could see all of the different checks and features inside of each workflow. We could see the price spread. So this basically shows the price from somebody who goes through and does one check and doesn't complete versus somebody who completes the entire flow. We have a a link for the workflow.
[01:09](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=69s) We can add a new session manually. We can copy the workflow ID. We can duplicate, rename, archive, all of your usual stuff. And this is your workflow ID here. So multiple versions of the same workflow is just going to run off the same ID. So if you do update the workflow, you don't need to change anything inside of your code or your app. So the simplest way to think about a workflow is that this is just a recipe for a verification. So which checks to run in what order and what rules to follow. So there's two different
## Simple vs graph workflows
[01:40](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=100s) structures like I mentioned. There's simple and there's graph. So a simple flow is just one check or a few checks with no branching at all. No conditional logic. It's quick to set up and it covers most cases. So you pick your modules and you're done. So I mean if we take a look at this example workflow, you can see we can select the modules here over on the left and it's going to sequence them all in order. Now for a
## A graph workflow with branching
[02:05](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=125s) graph-based workflow, if we click into here, we could see there is some branching at the end. So we can get a little bit more granular with where we direct the user through this verification flow. So you can see in this workflow for example there is multiple different branching points. We can include webhook calls as well. Um and in general we can just add a little bit more complexity in here when needed.
[02:30](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=150s) So you just note here as well the KYB workflows look exactly the same but we just have different checks. Okay. So if we want to add a check we can pop in here. We can grab one of the user actions or set up some background checks. These are the ones that the user doesn't see on the front end. So, all
## Creating a new workflow from a template
[02:51](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=171s) you need to do is hit new workflow. Got a couple of options here. KYC, KYB. Let's hit KYC. And we've got simple versus advanced like I showed you earlier. And I'll show you options for both. If we click simple, we have a ton of different templates here for all sorts of different industries. And we can see the cost for each of these templates. We've got age gates. We've got email verification.
[03:19](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=199s) We've got biometric authentication, insurance, healthcare, all of this stuff. Now, for example, if we use a business verification, and we use the advanced version, which is the graph, again, we've got a bunch of different templates or recipes if you want to call them that. So, we could set up high-risk country KYB and boom. You don't even have to click and drag and drop anything in here. It's already built. So every
## Picking a check on each node
[03:48](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=228s) node here has a dropdown. So you can just pick the check that you want. You can change it like here or we can put the registry check. Whatever you want. ID verification, livveness, face match, AML, proof of address. All of the checks that Didit can offer for KYB and KYC are available inside of these nodes right here. So, if I wanted to add an additional check into here, for example, let's say I wanted to do a AML screening, then I can add that into here. We'll get into the rules and these tabs in in just a moment, but you could see in this workflow, I've added that in to this workflow here. So, if you go into a KYC graph workflow here, most of these workflows are going to start with ID verification. It's the most common
## Inside the ID Verification node
[04:33](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=273s) check. And you can see here, this is the ID verification node. We got 500 free checks per month and the same for the livveness and same for the face match. Now, this ID verification does way more than just read text off a card. It proves the document is genuine. checking the security features, looking for tampering, reading the fonts and the layouts, and then it extracts every field, the name, the date of birth, the document number, the expiry, all by OCR in any language across all 14,000 plus supported document types. So, this is never just text. It's an authenticity check first and then data second. So, let's configure one of these modules.
## Liveness modes and what each costs
[05:15](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=315s) Let's I mean, if we head over to the livveness module, you can see we've got a few different livveness modes. We've got passive, which detects livveness using a selfie. We've got flashing, so we confirm the livveness using dynamic light patterns to detect real user faces. Then we've got a 3D action. So, it's going to confirm the lighting with some kind of action, whether it's a blinking of the eyes or a turning of the head. Now you can see for the passive livveness check, we do get 500 free credits a month and then 10 cents per check while the flashing and the 3D action is 15 cents per check. And we don't get any free checks included in our free account. So in the rules tab,
## Rules: thresholds, duplicate faces, multiple faces
[05:56](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=356s) this is how we decide whether the module is required or optional. So if we head over to the rules tab, we can see there's a few different options here. So this allows us to configure the sensitivity of the testing. So we can set a tolerance level. If somebody has a livveness score of less than 70, then it's going to send them to in review. And we can we can change that if we want. And it's the same for decline. We can also have a setting for a duplicated face. So when a user's face matches one from a previously approved session, how do we proceed? So approve, review, or decline. And then we can customize the similarity threshold by ethnicity. So we can set the duplicate detection sensitivity depending on their ethnicity. And then finally we can define the action to take when multiple faces are detected in the livveness image. The the largest face will always be used for livveness scoring. So uh that's an option that we can set here. If we head into the
## Advanced settings and quality thresholds
[06:56](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=416s) advanced tab, we have a few different options here. Here we've got the face privacy mode, maximum livveness attempt, so how many times a user can reattempt the livveness check within a single verification session, the face quality threshold. So, so if you are ever unsure of what these settings actually do, we can hover over this information button here. We can see face quality measures image clarity, resolution, sharpness.
[07:20](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=440s) Low quality images may result from blur, pixelation, or poor lighting conditions. So we can set the threshold here based on the face quality score. We have a luminescence threshold. So we can set the acceptable luminescence range and then if the luminescence is below that range, we can obviously send them to review or decline. And finally, we can set up our own custom rules if none of these match the
## Return data and GDPR
[07:47](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=467s) settings that we want. So in the return data tab we can select the data points that we would like to exclude from the verification results for GDPR etc. Now for each of these modules we can see the price of the check and then at the top
## How workflow pricing adds up
[08:00](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=480s) here we could see a total price. So let me just sort of walk you through this because each node has its own price and each workflow is probably going to be slightly different in terms of the total cost. So you only pay for the steps that actually run. So each step is charged on its own. So if the applicant stops partway through the check, only those first few checks that they've gone through only going to be charged. So that's why you see at the bottom a range from 20 cents to 43 cents. That is the range. If somebody is going to go through the workflow, if they go through the first step and they exit, then it's only going to cost you 20 cents. If they fully complete the verification workflow, it's going to cost 43. And as you can see in the middle, these are all of the modules that are inside of this particular workflow. We can see how many credits we've got per month. Once those free credits are used up, once those 500 checks are done, then you will start getting build for them. So, one of the most powerful modules you can add isn't actually a camera check at all. It's
## Database validation against government registries
[09:05](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=545s) database verification. So, let's just add this in. This is a background check. And we're going to take a look at what this actually does because this database validation check connects to official government registries and databases all over the world. So what it does is cross-check the users data against those official sources and then enrich it with extra fields the registry holds. So you can choose which registries and sources to run as you can see here for each country. And each service lists its own price. So you can see down here we've got different services inside of Canada for example and there's obviously different pricing for different registries. So if you wanted to see a specific country like Germany for example, we could see the checks that are available in Germany and we can select these based on our needs.
[09:59](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=599s) And this database validation is not just something that you can run here in the dashboard is callable directly through the Didit API from your own backend too. And again, like with all of these modules, we've got rules, we've got advanced, and we've got return data. So again, you could set your rules here. And if we click here on partial match, we could see what a full match looks like. Full name and ID number, if that matches in the registry, for example, and a partial match maybe is just an ID number. And then we can decide what that partial match means inside of your workflow. Do we put it to review? Do we decline? Do we approve? So we have some branching inside of this workflow. We
## AML branching and conditional logic
[10:36](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=636s) have AML branching depending on the AML risk score. AML is just anti-moneyaundering. But we can see down at the bottom we already have branching configured for this AML risk score. So we can see if the risk score is over 70, we can just decline and we can click into here. We can see the conditional logic. Right? So this is something that we can set up however we want. So we've got branch one, we've got branch two and then all other cases which means the session is going to be approved. So for example, if somebody is in this group here, they get sent to branch two. We could ask them for uh more information, right? We could ask them for proof of address, for example. So that way if somebody is a little bit risky, we can ask them for for more information that will help us decide whether this person should be approved or declined. So, one of the other nodes that we've got here is we've got status, and we've already been added here, but you can add those at the end of your workflow to determine what the outcome's going to be. So, another one of the
## The webhook node
[11:40](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=700s) nodes that we can include in the workflow is the webhook node. So, the flow can also reach out to your own systems in the middle of a verification, and that's what this node is for. It can call an endpoint and pull data back. And so it's going to wait for that webhook to respond. You can see here we've got a post webhook. We've got a JSON payload which can be changed. Also, we can also send different payloads. So these are the key value pairs that maps your session fields into the request that you send out. And the fields that come back become variables for the next branch to test. So you're combining digits checks with your own risk data all inside a single flow. And one final thing we we can include the headers down here at the bottom. Some more key pairs. We got basic authentication here. All of the stuff that you would expect uh from a webhook call. Didit validates the
## Graph validation and errors
[12:28](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=748s) graph the whole time you're building. So if a node is misconfigured or a path is broken, it's going to tell you exactly where. Right? So we click down here in the bottom. We see flow has two errors. And we can see branch one has incomplete rules. And it's going to tell you what needs fixing inside of your workflow. And we're not going to be able to publish this until these errors are fixed. So if I had a brand new workflow for example and there was an error then I wouldn't be able to publish that workflow. So beyond the individual nodes
## Workflow settings: retries, expiry, callback URL
[12:58](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=778s) in the workflow we have the settings tab in the top here that we could see these settings apply to the whole workflow rather than a single check. We've got a workflow name of course we've got a call back URL which is the address Didit redirects the user to once the flow finishes. We can allow desktop access if you want to allow them to continue on desktop. We've got the retry attempts so you can change that. So, the amount of times a user can reattempt document capture within a single verification session. If a previous attempt fails, we've got the session expiration, so how long an unfinished session stays valid before it automatically expires. And this is going to apply to newly created sessions. And we've got the retry window, so the number of days within which retries are allowed. So from
## White-labelling the flow
[13:43](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=823s) inside of this workflow, we can actually head over to the customizations tab by clicking this button up here. So white labeling is wired straight into the flow itself. So you can jump into this tab here and you could start tweaking how this verification flow looks, the colors, the branding, the email, the domain, all of that good stuff. And in the mockup here, you could see how it's going to look on mobile and also how it's going to look on desktop as well.
[14:13](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=853s) You can also test it out in different languages. So if we put this into French, you could see every check is now written in the French language. And if you want to turn on, you know, you can switch on your branding here. Boom. You could turn it to what we set in the customization tab or we can turn that off. And we can actually see what happens if we choose a different path here and we can see what gets sent to the user. Now, obviously the user is not going to see the the pick a path module, but this is just to help us visualize what's going to happen on the next step, right? So, if they declined, we're going to see verification declined, verification in review, or that they've been verified. We do have the copy link
## The shareable link, and when not to use it
[14:58](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=898s) at the top here. So, if we copy that, open a new tab, we can come through to the the link here, and we're going to be able to see what the user is going to see. And sometimes you just want to hand someone the flow without wiring any code at all. And this copy link does exactly that. It copies a ready to share hosted URL for this workflow. Now, this is handy for a quick test or a manual send, but it's not the recommended way to integrate this. For production, you can create a session per user through the SDK or the API carrying the vendor data and that's how you get real-time results back through webhooks and the SDK. So the copy link is great for tests and events and a proper session is what you're going to ship in production. So
## Version history and rollback
[15:41](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=941s) at the top here we've got the version history. So we could see the different versions and we can restore or we can view the previous versions. And if we were to be editing this workflow, then those edits would sit as a draft um above the published history in here. And the cool thing is that every new version that you publish, it keeps the same workflow ID. So your backend keeps calling that one ID and it's going to get silently forwarded to the newest workflow that you've created cuz that history is tracked and you can iterate with confidence and roll back to previous versions whenever necessary. So for example, in this workflow here, this is a brand new one. You can see it's in draft mode. If we make any changes to the workflow, it's going to be in draft and we'll need to publish it. So, we can
## Publishing your workflow
[16:24](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=984s) hit publish. This workflow is only going to go live when it's published. Uh, it's going to validate the graph to make sure there's no errors. And boom, we can grab the link and now this workflow is live and ready to go. Uh, let's take a look
## Integrating: SDK, API key and the setup prompt
[16:37](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=997s) at the integration. So, there are multiple ways to integrate this. We can grab our target SDK. So whether we want React Native, iOS, React Next.js, we can select the workflow and in this case we got the high-risk country of review. We can select the API key. So we can have different API keys for different workflows or different integrations, whatever you want. Or you can select a webhook. So if you want to get notified of decisions, you can pop that into there. Now the easiest way to integrate this is we just copy a prompt and we can just slap that into claw code or codeex.
[17:12](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=1032s) And that prompt is going to integrate this workflow. So even if you don't have didit integrated into your app yet, this prompt will give you everything that you need to have this workflow up and running into your app. Now you don't even need to do that. You could just
## Building workflows with the MCP server
[17:27](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=1047s) install a Didit MCP server. Just describe the flow in plain language and it will create the workflow for you and automatically integrate it into your app. Tell it what to do and it will go ahead and do it. Now, we are going to get into how to set up the MCP in another video, but MCP is essentially model context protocol that allows you to build. with Didit using natural language in tools like Claude Code, Cursor, Codex, and all the rest. So, we can jump into customization real quick.
[17:57](https://www.youtube.com/watch?v=7-nJ3JE20i4\&t=1077s) We'd add in the public name, privacy policy, URL. We've got the branding where we can change the colors. You can also pull your logos and colors automatically from your website, which could be super useful and easy to do. We can upload your logos down here. And you can see I've already set these colors. And to to test how it would look with those colors, if we just flick this switch here, we could see our white label branding getting implemented here on the verification flow. I'm just going to toggle it off for now. You can set a personalized email. You will need to fill out these details. And then when you continue to verification, you can just get the DNS information and hook that up with your domain. And the same here if you want to put a subdomain. So that this whole workflow is fully hosted using your own branding, your own domain. So this really is a seamless process for your users to go through the verification.
# Integrate Didit: API, SDK and Webhooks
Source: https://docs.didit.me/academy/integrate-api-sdk
Video transcript: the developer integration path - session API, SDKs, webhooks, and going from sandbox to production.
Full transcript of **Integrate Didit in Your App: API, SDK and Webhooks (Developer Guide)** - Didit Academy video 8 of 10, 08:13. [Watch on YouTube](https://www.youtube.com/watch?v=fX03-WEu_EI). Each paragraph links to the exact moment in the video.
## What you'll wire up
[00:04](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=4s) In this video, you'll get Dedit wired into your own app. So, your users verify right inside your product, and every result flows back to your system the moment it happens. This is the developers video, so it's a little bit more technical, but it's simpler than you think. And by the end, you'll see you can hand most of it straight to cloud code and have it build it for you. So, we'll go through it in order that
## SDK, API and webhook: the three pieces
[00:28](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=28s) you'd actually build it. Let's dive right in. Integrating Dedit means wiring up three things. The SDK renders the camera, the document capture, the NFC read, and the liveness UI right inside of your app. The API is server-to-server, so your back end creates the session and reads the authoritative decision. And the webhook calls your HTTPS endpoint the instant a session changes state, which is way
## The integrate tab
[00:56](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=56s) faster and richer than polling for it. So, the easiest way to integrate is to go to your manage tab, click on integrate, and we can just grab all the settings from in here, so we can grab our API key. So, we could set up a specific API key for this particular app that we're working on. We can create and choose a workflow that we want to integrate. We can add a webhook. So, this is where the verification results are pushed to.
[01:24](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=84s) And the signing secret, that I'll show you in a second, this is what's going to
## Choosing your platform
[01:28](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=88s) go in your ENV file. You can then connect it to your app. So, we've got a few different options here. We've got web JS, React Native, iOS, Android, Flutter, and a few other surfaces here. So, you can select the one that is relevant for your app. This is the environment secrets that you're going to put in your ENV file over on your
## The AI setup prompt
[01:48](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=108s) server. So, the easy way to set this up would be to use the AI setup prompt. So, we configure all the settings up here, and then we copy the prompt and it's going to copy all of the relevant information that we need from the setup that we've completed here. We copy and paste this, and we can pop that into Cloud Code or Codex or wherever you're
## API keys, one per environment
[02:09](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=129s) building your app. Your API keys live here, and each row has one key, okay? And so, a good habit is a separate key per environment per integration. So, you can rotate or revoke one without breaking the others. So, if you're going to integrate a specific workflow, you
## Creating a key
[02:27](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=147s) might want to just have a separate API key for that workflow, for example. Now, if I click create API key, um I'm just going to put test here, create the API key. Now, I can copy this, right? And I'm not going to see it again. So, it's important that you copy it and maybe note it down somewhere so that you can use it when you're doing the integration. When you come through to the integration section and you grab your API key, and let's say if I grab the test key and I come down, I select, okay, we want to use this uh let's see, KYC workflow. We can add a webhook.
[03:05](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=185s) We can connect our app, okay. And then we can pop in this setup prompt here. You can see here this prompt is going to
## Webhooks overview
[03:13](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=193s) include the API key. So, moving on to webhooks real quick, we can set up a webhook for basically anything that we want. So, say if you want to send a specific event to Slack, right? We can
## The documentation
[03:25](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=205s) add the webhook URL here. Okay, so let's take a look at some of the documentation here cuz you do have access to all the technical documentation. If you just go to docs.didit.me, and we're going to look at a few things
## Create session: the one required field
[03:37](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=217s) in here. Everything revolves around the create session. All right, so this is a post request. This is the start of a verification. So, we could see here in the curl request, we got the workflow ID. This is the only required field, and the really useful bit is that this same endpoint creates both KYC and KYB sessions. The
## Vendor data and callback URL
[04:00](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=240s) workflow is what decides which is which. So, we've got some additional fields down here, including the vendor data, which we've spoken about a lot in these videos. This is the unique identifier for each user, and you can define that as you wish, if it's an email or a unique identifier, and then we have the callback URL here. This is where the user is going to get sent after they've completed the verification workflow. So,
## Trying the endpoint live
[04:24](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=264s) if we scroll down here, we can actually try this out. So, if we click try it, we can actually create a verification session here. So, I'm just going to pop in one of the API keys here. Just going to create a brand new one, test two. I'll copy that. Pop this in here. We want to grab a workflow ID. So, pop over to here. We can grab this workflow ID here. Enter that into there. The vendor data is not required. Let's just do a a random string of numbers.
[05:02](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=302s) The callback, we can keep it the same here. None of this is required, by the way. I think this is because I have a test account here, but if you did want to test this endpoint, this is exactly how you would test it. Now, if you are a developer and you want specific answers here, you can check out the documentation. The MCP also has access to all of this documentation, so you can literally just integrate the MCP and ask Claude a question that you might have about something technical that's probably going to be in these documents
## Webhooks: why not polling
[05:32](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=332s) inside of here. So, let's talk about webhooks. We do have a tab here in the manage section for webhooks, and this is how your results actually reach you. Webhooks are the recommended channel because polling is pretty slow, and it's
## Adding a destination and picking events
[05:44](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=344s) going to cost you more requests, and it's going to skip events. So, we add a new destination in here. We can paste a URL. We can pick the events that we want. So, this is all the data that we want to send to this particular endpoint. And Diddy is going to hand you back a one-time signing secret, which I'll show you in a second. And the URL has to be directly reachable. So, no localhost and no private IPs. So, I'm just going to come out of here and we can scroll over to the right-hand side and we can we've got a few options here.
[06:16](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=376s) So, we can see here the number of events this webhook is listening to and we can
## The signing secret
[06:19](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=379s) copy the signing secret, which is going to be important for your integration. Now, when we do the setup the integration in the integrations tab, and I walked you through that we have a prompt, then the yeah, the signing secret is going to be in here for the webhook that you've selected in the setup. So,
## The webhook handler prompt
[06:34](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=394s) another one of the ways that you can leverage AI, we've got a pre-made prompt here that I found in the webhooks tab here. We could see if you want to set up our webhook handler with AI. We can check out the documentation and we can find an integration prompt here. So, this is going to cover identity verification, entity, and transaction. It's going to build it all out for you. So, it lays everything out. So, what to build, the signature headers of verification, the webhook event types that you just saw when we were setting up a webhook earlier. We've got the webhook envelope and a whole bunch more and we can also select where we want to paste it into, which tool that we're using here.
[07:15](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=435s) So, if you want to use this prompt, that's the easy way to get rolling with this and get your integrations set up. Everything I've just gone through in this video is accessible and queryable inside of your AI tool if you integrate the MCP server. So, you can just explain whatever you want to happen in plain English and Cloud Code will read the documentations and build the integration for you. So, you're just checking the work, making sure it's running and
## Recap
[07:41](https://www.youtube.com/watch?v=fX03-WEu_EI\&t=461s) working rather than coding everything from scratch. So, that's Didit in your app. The SDK, your users see, the API that your back end calls, and the signed webhook that delivers the verdict that you build on. Trust the webhook, use vendor data everywhere, and lean on the setup prompt, so you're not hand-wiring the boring parts. You can head to didit.me, sign up for free, and you can have a verification run in minutes.
# KYB Explained: Registry to UBO
Source: https://docs.didit.me/academy/kyb-explained
Video transcript: business verification end to end - registry data, documents, officers, ownership graphs, and UBO identification.
Full transcript of **KYB Explained - Verify a Business From Registry to UBO, Start to Finish** - Didit Academy video 6 of 10, 11:05. [Watch on YouTube](https://www.youtube.com/watch?v=JEnW_sEe35Y). Each paragraph links to the exact moment in the video.
## What this video covers
[00:03](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=3s) In this video, we're going to verify a business from start to finish. Checking a company is more than confirming a name. You've got to look it up in the official registries, work out who really owns it, screen the business and its owners, and collect the right documents. Didit pulls all of that into a single workflow called KYB. We're going to set it up, see what the company representative goes through, and then read the full result in the dashboard.
[00:32](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=32s) So, let's get into it. All right. So,
## The business verifications dashboard
[00:35](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=35s) let's navigate to the business verifications dashboard. So, this is KYB, know your business, and it verifies the company itself, its registry records, its owners, and you can see here, we're going to go into detail on one of these checks. This particular business is under review. And again, this is a demo account with demo data. But you could see the checks that this business has gone through, the checks that have been approved and the warnings right here.
[01:03](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=63s) So, we're going to go through each of these tabs and I'm going to explain the details of each of these checks. Now, if
## Filtering for businesses in review
[01:10](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=70s) you want to filter for all of the businesses that are under review, we can apply the filter in the status column. in review. You can see this particular business has been flagged on a lot of different fronts. Uh we can also check out some other ones here and we can see this business has been flagged on key people. So at the top just like with the user verification results we've got similar tabs and these tabs are related to the checks that this person goes through in a specific workflow. So up
## Warnings first
[01:38](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=98s) top as usual the first place to look is the warnings. Where has this particular business been flagged? So you can see company officers data not available. So that's what we're going to be looking at when we're working through and figuring out if we can approve this business or not. Now we do have the business details
## Business details and vendor data
[01:56](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=116s) here. The country, the session ID, and the vendor data ID. Now, we've gone through this in previous videos. The vendor data is what ties all of the checks and all of the transactions under one profile. We can take a look at the session ID when it was created, the vendor data again, and and the workflow. So the workflow this particular business has gone through. We've got a cost breakdown here of all the checks that have been applied to this business. And that's everything in the overviews tab. All right. So let me find another profile that's got more checks here so can walk you through in a little bit more detail. Again, this is a different company. Again, demo data. Got the cost breakdown, possible match found in the AML screening. So we're going to work through that. Let's take a look at
## The registry check
[02:43](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=163s) the next tab. This is the registry check. This is going to happen with every business that you put through KYB. You can see the registry is active. Got the company name, the registration number, all of the information that we've pulled from the registry. So, there's a lot of stuff in here. I'm not going to walk you through every single field. It's going to be different for different countries, but just know there's a bunch of checks running on this particular registry check. In the background, you can see company country accepted, the ownership data is available, the company is active, and all of these other checks. And if you want a little bit more info on these, you can just hover over and see a further explanation. So, if you're responsible for coming in and reviewing these verifications, you can flick that to approve if you think that check is worth approving. So let's move on to the
## Key people: shareholders, officers and UBOs
[03:34](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=214s) key people because a business is usually not just typically one person. There are many people involved and so we need to run checks on all of those people inside the business. All the shareholders, all the representatives that we can see here. There's plenty of people in this business that we're going to need to run checks on. So we've got a list, but we've al also got a hierarchy. We can see how that looks on this chart here.
[03:56](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=236s) So keep it as a list. We're going to scroll down and again we've got a checks list. And if we hit this drop down, we can see there's only one check happening here on the key people. But if we scroll up, we can see fully verified, all parties clear. So we could flick that to approved. Now, if we keep going, we can
## AML screening and ongoing monitoring
[04:12](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=252s) see the AML screening. So this is the same as the user verification, the KYC. And again, we've got a sanctions list that we're going to be matching against. We can flick on ongoing monitoring if we want to keep monitoring this particular user on an ongoing basis. Instead of just running the check once, we can run that on a weekly, monthly basis. You can see there is a match here, but the match status is inconclusive. We could see the the lists that this particular business has appeared on. If we click the drop down on the checks list, we can see no AML matches found. AML screening performed. So, there was a match found here, but it's it's not a particularly high risk. We can see that there is a
## KYB documents and signature integrity
[04:50](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=290s) match found here, but again, inconclusive. We keep rolling with this. We got the KYB documents. We can see what's been verified, what's in review, and what's declined. The corporate documents that were submitted, and the information that was pulled from these documents. We can also see the document integrity. Was it signed by pen or was it digitally signed? And that's important to know, right? Because digital signatures are not as authentic as real signatures, right? We've got the checks list. Again,
## Device and IP analysis
[05:18](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=318s) these are all the checks that are running on these documents. Like we saw in the KYC, we've got the device and IP analysis looking at where the IP is for the verification device and then where the actual proof of address location is based. Again, if we want to see the checks that are running on the device and IP analysis, we can take a look
## The event log
[05:35](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=335s) here. And down here, we've got an event log. And this is probably going to be a little bit more busier than the KYC because there are going to be different people inside the business that are going to need to submit information. So, we're able to see here when files were uploaded, when the statuses have been changed, when the session was created, including the IP address of each of these actions, and finally, we've got
## Webhooks
[05:58](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=358s) the webhooks at the bottom. So, when these webhooks were pinged, got all the
## Inside a KYB workflow
[06:02](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=362s) information of these webhooks inside of this dropdown. So, what does an actual KYB workflow look like? So, let's just take a quick look inside the configure tab. We go into workflows and we can find a KYB workflow inside of here. Registry and documents. So let's take a look at what a basic KYB workflow looks like. We start off with a registry check. Check the key people. And let me just show you what appears over here on
## The ownership threshold
[06:30](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=390s) the right hand side. So there's a bunch of settings here that we can tweak. So the ownership threshold. So set the ownership threshold used to exempt low ownership owners from verification. you know, if somebody owns 1% of the company, there's really no point in verifying that person. And we can set that threshold here by just using this slider. So, anybody who's below this particular value is exempt from identity verification. So, in the settings tab over on the right, we can set the ownership threshold on UBO ownership and shareholder ownership. We can change that so anyone below the
## Party verification and reusing KYC sessions
[07:04](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=424s) threshold is exempt from identity verification. We can also select the party verification. You can select the ones in here. We can auto decline on UBO rejection. So if any UBO fails verification, we can just auto decline. Also, we want to wait for all of the UBO verifications before making a final decision. Uh we can turn that off if we want. We can reuse verified individuals. So we can reuse approved KYC sessions for the same person across KYB, which is pretty smart. And notify parties by email. We want to notify these people so we can get their verification for the KYB check. There is some more advanced settings here, but it's not the scope of this video. I just want to work through what else we've got here in this workflow. We've got the AML screening.
[07:47](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=467s) So, very similar to KYC. We can set the risk threshold on here. Uh we've got the phone verification, email verification. Again, we've got a bunch of settings for each of these steps, but if you don't really know what to configure, you can just leave everything as default. And the cool thing is you can have a conversation with Claude to set all of this up for you and it can walk you through all of the settings and you can just tell it your preference and it will know which setting to change and tweak.
[08:15](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=495s) So I'm not going to go through all of these individual settings. We will be here forever, but just know you can add additional steps in here. You can add branching based on the outcomes of specific steps in this workflow. Now, if
## What the company representative sees
[08:29](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=509s) you want to see what this workflow actually looks like, we can head over to the customization tab by clicking this icon up here, and we can see what somebody would see on the other end when they're coming in to verify their business. So, we start off with the welcome screen. We find their company using their company name and their country. Then, they select their company because we've done a search on the registry. They're going to edit the company information, list all of the key people. So the UBOS's shareholders representatives phone number verification which they'll then enter in here email for email verification which they'll enter the code in here. And now they're not going to see this. This is the branching that I've put inside the workflow. But we're going to be able to see what that user sees on the other end if they go down branch one or the other branch. But I've not set that in this workflow. So we could you can see at the bottom I I just added this in just to show you as a demo. So we can delete that step. So that's exactly what someone's going to see on the other end.
[09:30](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=570s) So if we want to check in on our data,
## KYB analytics
[09:32](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=572s) we can go through to the homepage. We could check the analytics. We can add some tabs at the bottom here if you want. So we can come in here. We can check the business verifications. We could look at the the volume, IP locations, company locations, warning insights, conversion rate, conversion rate by country, um, automatic verdict breakdown, and we can add all of these to our dashboard.
[09:57](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=597s) So, if you scroll down, you can see that we've added all of these business checks. Down here, we can see the Caribbe registry check, key people, AML screening. So, this is just a warning insights at a glance. So we can see our approved versus rejected, conversion rate from approved, decline, and in review. We can customize this. We can also run reports at the bottom and export any data that we want from that dashboard. So that's a business verified from start to finish. One workflow looked the company up in the official registries, screened the business and its owners against the watch lists and mapped out who really owns it with an identity check on each of them and pulled in the documents. And all of that comes back in the dashboard registry record and the company status, the ownership, the screening results and the documents with the same data available over the API. So if you want to run a KYB of your own, head over to didit.me.
[10:54](https://www.youtube.com/watch?v=JEnW_sEe35Y\&t=654s) me. You can sign up for free and create your own business workflow today.
# What Users See in a KYC Verification
Source: https://docs.didit.me/academy/kyc-user-experience
Video transcript: the end-user KYC flow step by step - branding, document capture, NFC, liveness, face match, and what happens on decline.
Full transcript of **What Your Users Actually See in a KYC Verification (Full Walkthrough)** - Didit Academy video 2 of 10, 14:36. [Watch on YouTube](https://www.youtube.com/watch?v=LzGe-dMWjUw). Each paragraph links to the exact moment in the video.
## What this video covers
[00:03](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=3s) When you send a customer off to verify with Didit, what do they actually see? Well, that's what this video is about. We're going to walk through the whole flow from their side. The exact screens a real person taps through after they click your link. a flow you fully brand in their own language on whatever device they've got with a stack of invisible checks doing the heavy lifting and the real result landing in your system the moment it's made and the best way to get a feel for it is to run one yourself. So head over to didit.me you can sign up for free and verify on your own phone in
## Opening the flow yourself
[00:38](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=38s) a couple of minutes. Okay, so the easiest way for you to see exactly what your users go through is to open the flow yourself. So that's what we're going to do. Uh we're going to head over to one of our workflows here. We're going to copy the link and we're going to put it in the browser. So let's just take this basic KYC workflow. We're going to copy this link and we're going to pop it in here. So right here is the
## The first screen your user sees
[00:58](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=58s) very first thing that your user sees after they tap one of your links. So the first thing to notice that well this is a demo for it. So we do have the Didit branding on here, but you can fully customize this. This list here is every step this particular user will go through. So we've got ID verification, face verification, and additional documents. So this list is not hardcoded. The workflow that this session is attached to is actually going to decide which steps show up and in what order. So this flow just renders
## Branding, and why it drives completion
[01:28](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=88s) whatever is inside of that workflow when you turn it on. So the logo and the styling are yours, too. So to the user, this still feels like your product. That's pretty important because how much it feels like you and your brand is the single biggest lever on how many people actually finish this verification flow. So there is one small mark down here secured by Didit and even that's removable. Everything above is yours to configure. Did just the engine that's running underneath. So what your user sees is exactly the workflow that you set up wrapped in your brand. So here's
## The checks that never show a screen
[02:00](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=120s) something that surprises most people. Not every check you see here has a screen. The only steps that show up are the ones that genuinely need the user to do something. So document, face, phone, email, and while they're working through those checks, a whole set of other checks is running server side with nobody touching anything. So device and IP intelligence, AML screening, ongoing AML monitoring, database validation, these are all happening in the background. The user never sees a step from any of them and you still get every result back in your dashboard. And that's how the flow stays super short while still doing a ton of work. Some of did its strongest checks at zero friction because they never face the user. So if you go back and look into one of these workflows, we could see here that we've got all these background checks. We got database validation. Uh we've got email verification, phone verification, AML screening, IP and risk analysis. So these are not public facing. These are all the checks that
## Customization: colours, logo, email, domain
[03:00](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=180s) you can add into a workflow. So let's take a look at the customization tab. This is where you're going to be able to add in your branding, your colors, your own email address, your own domain, all of this stuff. As you can see here, we can change the name of the app. We can show the welcome page or turn it off. We could show the progress bar or turn it off. We've got the branding tab where we can add in all of these colors. Can add in the logo, the favicon, and change the typography. We can add a custom email.
[03:25](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=205s) So, if you just pop in a name here, we can put in a an email address and we can continue to verification. This is going to give us the that's not worked out. But if we put in an actual working domain and we click continue to verification, that's going to give us the DNS records uh that we can add and we can set up a custom email address. And this email is where the notifications are going to get sent from and all the updates about the verification status. For the domain, we can also add a custom domain. So, we had a subdomain for example. And again, if you add in your subdomain here, it will give you the the records that you need to update on your domain provider. So you see this globe up here. The
## 54 languages, auto-detected
[04:04](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=244s) interface ships in 54 different languages and that includes left to right scripts like Arabic. And by default, this flow just auto detects whatever language the browser is set to and falls back to English if it doesn't support that particular language. But the user can open this searchable list and switch into any language at any time and have every instruction follow their choice from there. And if you already know someone's language ahead of time, maybe on your own database, you can set this language when you create their session. So it opens in the right language without relying on the browser at all. So the flow meets the user in their own language automatically with 54 to choose from. So, if we take a look at
## Mobile vs desktop, and the QR handoff
[04:49](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=289s) this preview, you see most people end up verifying on their phone. And that kind of makes sense, right? The camera makes capturing a document and a selfie super easy. Someone would just point their phone at this QR code and they'll be able to continue the verification process on their phone. When someone lands on this page on desktop, you could see here we can actually continue on desktop, but that's an option. You can force people to just verify through their mobile, but you can absolutely enable desktop and and let them finish right here on the computer instead. But if you do, just know that the livveness check gets forced to passive on desktop because the active version doesn't really play nicely with computer webcams. And what I mean by active versus passive is where you have to
## The hosted flow vs building your own screens
[05:34](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=334s) blink on the camera. That's an active livveness check. Whereas passive is just a a photo. So the simplest path to get started is the hosted flow. You see when I hit this copy link and I open it up in my my browser. You could send the user a link and Didit handles all the hard parts for you. So the camera permissions, the document capture, the livveness, every fiddly device quirk. So you can drop this into your app through the SDK, an iframe, a redirect, or a pop-up. And if you'd rather build your own screens from scratch, you totally can. But then you're on the hook for all of that. We get into the integration side properly in another video, but for now just know that the link that you're sending that you're getting from this workflow is the hosted flow and it's tuned to get people through to the end.
## Detecting the issuing country
[06:20](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=380s) Okay, so back into the flow. When the document step needs to know which country the ID is from, most of the time the flows already figured that out. It geoloccates the user from their IP the moment the page loads and pre-selects the issuing country. And here you can see that it's worked out. And if your workflow only allows one country anyway, that's just going to get selected automatically and they're never going to see this picker. Uh this example here is a KYC flow, but this is going to be exactly the same for KYB or any other workflow that you build. The engine just runs whatever steps that are configured inside of your workflow. So next, as you can see here on this preview, the user
## Choosing a document type
[07:00](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=420s) is going to pick which government document they're going to show. So didit recognizes seven named types. There's passport, identity card, residence permit, driver's license, health insurance card, tax card, and social security card. But this list is filtered down to only the ones that are actually valid for the country we just detected. So the user can't pick something Didit can't read. And again, if your workflow only allows for a single document type, this whole screen gets skipped and they
## Capturing the document
[07:26](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=446s) go straight to the capture page. So if we continue, we go to the capture page. So, as you can see here in this preview, I am on my desktop and so I can upload a file or I can take a photo. So, forcing a live capture instead. It's going to drive guided real-time photo with no uploads allowed. And that's your strongest setting against tampered or recycled images. So, on the upload side, the accepted formats are your classics, JPEGs, PGs, PDFs, and a few other file types, all under 10 megabytes. And if it's a PDF and it's password protected, it'll prompt for the password. So we can either upload or we can force a live capture and that's all configurable inside of your workflow settings. So as you can see here in this preview as they line up the document ondevice cues are checking the lighting and the sharpness and it captures automatically the moment the shot is good enough so the analysis can start right away. And it's not just reading the text off the card. Didit is running authenticity and tamper checks, a document livveness check that catches things like a photo of a screen or a printed copy. It's also doing security features and checking the MRZ validation across more than 130 document languages.
[08:37](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=517s) And the thing that really sets this apart is it's all happening in real time. So the user finds out instantly if a document didn't work and you get the exact same data at the same moment. So everybody wins on speed. So for the
## The liveness selfie
[08:50](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=530s) livveness check, they can take a selfie. The user just centers their face in the oval that will appear on the screen from this step that you can see here. And behind that simple screen, there's a livveness check proving a real present human is actually in front of the camera. And that's also what lets Didit match the face against the document they just submitted. So you could choose which livveness method runs here. And you can set it per workflow node, which we are going to get into in other
## Passive, 3D flash and 3D action
[09:17](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=557s) videos. And there are three different settings. We've got passive where the user does nothing. We've got 3D flash where colored lights build a depth map and still asks the user to do nothing and a 3D action which adds the lights plus a randomized little action like blinking or turn the head. So it's one selfie step with three livveness strengths that you get to pick from. So
## The liveness video and scores
[09:41](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=581s) let's take a look at what that livveness step gives you. It records a short video of the person and scores it. So, if we go over to the user verifications and maybe we scroll down and take a look at one of the users here, we can look into their profile. And don't worry, we're going to get into everything that's inside of this verification check in another video. But just to show you what the livveness check actually does and what it gives you inside of your profile is a short video that you can see here.
[10:08](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=608s) So, we could play the video. And yes, this is just a video for demo purposes, but this is what will actually show up for each of your users in the verification step. We have a livveness score here in a percentage. I'm going to get into that in another video. So each of the methods, the passive or the active, spits out a livveness score from one to 100. And it runs presentation attack and deep fake detection on top.
[10:30](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=630s) So the video gets saved right alongside the score. And you can come back and replay the video and read every score right here in the verification check. and also in the user's profile. So you walk away with the selfie video plus the livveness and the deep fake scores
## What happens when a step fails
[10:45](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=645s) replayable whenever you need them. So what happens when a step fails? The user doesn't hit a dead end. They get a specific guided retry. And in the settings inside of your workflows, you can set the maximum number of retries, the retry window. Um but we're going to get into more of the settings and how these workflows work in another video. But when they do see a retry message, it's going to be precise. something like the document expired or retake this in a stronger light, not some vague generic error. So they know exactly what to fix and how many attempts that they get is configurable as I've just shown you here in the workflow. By default, the flow just lets them keep trying until they get through because the whole goal is to finish them, not to lock them out. So attempts are tunable and the default is
## The approved screen
[11:30](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=690s) built so the user can always reach the end. So when every required check passes, the user lands here. So, a green check and a plain language. You've been verified. It tells them there's nothing more that they need to do and they can head back to your app. And here's the part that they don't see. Your app has already gotten the verdict in real time before they even tap away. So, approved is this green reassuring end screen and
## The under-review screen
[11:54](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=714s) your back end already has the results. So, sometimes an automated check raises a warning rather than a clean pass or fail. When this happens, the user is going to end up seeing an orange clock instead of this green check mark. With the text underneath, your submission is under review. So on your side, the verification is going to come in to here under review. And it's going to be up to you, somebody on your team, to either pass them, to approve their application, to decline, to ask them to resubmit with more information. And the cool thing is that user is going to get an email the moment the status changes on their
## The declined screen
[12:30](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=750s) verification request. So if a check genuinely fails, they're going to get a clear declined state. So they're going to get a red mark with the words, "We couldn't verify your information at this time," which explains the outcome without giving away any of your fraud logic. So what happens next is entirely up to your workflow. You can let them resubmit within whatever attempt budget that you set or route them into a support flow inside of your own app. So declined is a clear non-technical end
## Redirect vs webhook: which one to trust
[12:58](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=778s) screen and you decide whether that means resubmit or support. So two things happen at the very end of this workflow and it's worth knowing which one to trust. Right? So first the user's browser bounces back to your app. So they land right back where they started but that part is really just a handoff for them. The thing you actually rely on is the result that your server gets at the same moment sent straight to your back end the instant the decision is made and that's the version that you build your logic on. Never what's just sitting in the browser. The nuts and bolts of wiring that up are a whole video on its own. And so we'll save that for later. But the takeaway here is simple. The user returns to your app and your system already has the real trusted
## Finding the session in your dashboard
[13:41](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=821s) results waiting. Every verification is going to show up in the user verifications tab. We got the filters here. We could see approved, in review, approved, declined, and depending on the outcome of the verification and the workflow that they've gone through, then that status is going to update accordingly. Now, the ones that are in review are obviously the ones that you're going to need to pay most attention to. So, we can filter that out and just have all these in here for human review. And with each of these verification checks, we can click in and we can see every piece of evidence behind the results. And this value down here, the vendor data, this is the ID that your own system sent across when it created the session. It's your stable reference for that person.
[14:25](https://www.youtube.com/watch?v=LzGe-dMWjUw\&t=865s) And Didit groups every session that shares the same vendor data ID under one profile?
# Connect Didit to Claude With MCP
Source: https://docs.didit.me/academy/mcp-claude
Video transcript: connecting Didit's MCP server to Claude - running KYC checks, querying sessions, and editing fraud rules from chat.
Full transcript of **Connect Didit to Claude With MCP - Run KYC and Fraud Rules by Chat** - Didit Academy video 4 of 10, 10:50. [Watch on YouTube](https://www.youtube.com/watch?v=I3UkFIhW548). Each paragraph links to the exact moment in the video.
## What you'll build
[00:03](https://www.youtube.com/watch?v=I3UkFIhW548\&t=3s) In this video, you're going to connect Didit straight to Claude and then get real work done just by typing what you want in plain English. So, building a workflow, clearing your review queue, setting up fraud rules. All of it just from the chat. And the thing that makes this possible is the MCP, which lets an AI assistant securely use Didit's own tools. So, let's start with what this connection actually
## The hosted MCP server
[00:31](https://www.youtube.com/watch?v=I3UkFIhW548\&t=31s) gives you and get it set up. So, Didit ships a hosted MCP server, so any MCP client can drive the whole platform. And so, the three products that you should already know by now map straight onto this one connector. So, there's nothing really new to learn about Didit itself. You just type what you want. Claude calls the matching tool and shows you the result. So, anytime it's about to change something, it'll ask you to confirm if you do have permission set inside of Claude. So, one connector turns Claude into a full Didit operator. So, we could see here at the top, we've just got a a link here that we can add straight into Claude. So, I'm going
## What the connector actually gives you
[01:11](https://www.youtube.com/watch?v=I3UkFIhW548\&t=71s) to click that in just a second. But, what do you actually get with this MCP? Cuz this isn't just some wrapper around one endpoint. The connector exposes 108 different tools across 12 tool sets that covers sessions, workflows, KYB, fraud rules, cases, lists, webhooks. Basically, everything that you can do in this dashboard and all the data points, you can access through the MCP. And the cool thing is, you don't actually need to put in any API keys. You can just do an OAuth authentication one click. I'm
## Adding the connector in Claude
[01:42](https://www.youtube.com/watch?v=I3UkFIhW548\&t=102s) going to show you and walk you through that in just a moment. So, let's add the Claude connector. So, we can add this in Claude desktop, right? We can add it in Claude code. We can set it up in CodeX. All sorts of different options for you there. All All so let's just click this link and add the connector straight into Claude, shall we? So we can add a custom connector. This is in the web version.
[02:02](https://www.youtube.com/watch?v=I3UkFIhW548\&t=122s) We can add for now, and you can see it's added the MCP, but we're not connected to Didit yet. So we can click connect. You will sign in here. You'll log in. You'll authenticate. It'll redirect you back to Claude. And so when you come
## OAuth: authorizing without API keys
[02:16](https://www.youtube.com/watch?v=I3UkFIhW548\&t=136s) through to authorize the application, this is the screen that you're going to see. And this is what permissions you're giving it. All right, so I'm just going to hit authorize, and you can see at the top connected to Didit. So I'm just going to see what we've got here. So you can see if we click that plus button, you can see connectors. I can see Didit is connected
## Listing the available tools
[02:38](https://www.youtube.com/watch?v=I3UkFIhW548\&t=158s) here. So if I say "What tools do I have access to?" We'll be able to see the list of tools that we have access to here inside of Claude. So just like I mentioned earlier, we've got 121 tools that we've got access to in here. This is the list of everything. Now, don't worry if this seems like a lot. This is just all of the the data points that we can tap into inside of Didit. So yeah, from here in this chat, you can just describe what you want, and it's going to build it for you through the MCP.
## Claude Code, Codex and ChatGPT
[03:12](https://www.youtube.com/watch?v=I3UkFIhW548\&t=192s) Now, if you are using a different tool like ChatGPT or Claude code, I mean Claude code is the same process. You'd have to just add the MCP URL, and then go through the authentication, signing with your account. It's going to be very similar for ChatGPT. I've got a few prompts here that I've I've developed, so I'm going to throw them in, and we'll see what we can create. Now, of course, you can create workflows. You can check your review
## Querying your review queue
[03:39](https://www.youtube.com/watch?v=I3UkFIhW548\&t=219s) queue, all of this stuff. So So you can see we've got a permissions box show here. It's going to ask us to use this specific tool. Going to click always allow, or you can change to just allow once. I'm going to hit always allow. So, we can see the result set is pretty large. So, perhaps we could have filtered it and said, "How many verification sessions do I have in review in the last 7 days?" Now, because I am using a demo account here, nobody is actually going in and approving any of the verifications that are in review.
[04:11](https://www.youtube.com/watch?v=I3UkFIhW548\&t=251s) So, the situation for you and your business, you'll have somebody doing that daily, depending on the volume that you're running. You're not going to have a huge backlog. So, you can see it's just pulling live data from our account, and let's get into an actual build. So, I'm going to pop this prompt in here, "Build
## Building a KYC workflow from one prompt
[04:26](https://www.youtube.com/watch?v=I3UkFIhW548\&t=266s) me a KYC verification workflow." And then we're going to name the checks that we want. So, I want an ID document check that reads the document, extracts the data, passive liveness, face match, AML screening. Once it's built, set it as my default workflow. So, we do have another permission request that is going to ask you these permissions every time it's using a new tool. Boom, so we're done.
[04:46](https://www.youtube.com/watch?v=I3UkFIhW548\&t=286s) That workflow is built. That was super quick. It was probably like 10, 20 seconds. So, let's go over to Didit and
## Checking the result in the console
[04:54](https://www.youtube.com/watch?v=I3UkFIhW548\&t=294s) go into our workflows tab. Give it a refresh. And we can see KYC, liveness, AML, face match. And you can see last updated 1 minute ago. It's created as a graph workflow.
## Asking for a conversion-lift recommendation
[05:12](https://www.youtube.com/watch?v=I3UkFIhW548\&t=312s) Boom. Pretty cool. So, what else can we build here? Well, I've got another prompt that I think would be really useful if you've been using Didit for some time and you already have some data. "Take a look at my current workflows and my recent conversion data, then suggest one specific change that would lift my pass rate without weakening fraud protection. Please show me the exact update before you apply anything." So, now again, I will say the account that I'm connected to is a demo account, so it doesn't have a ton of actual data, but we'll we'll see what it can come back with. That took a few minutes, but we do have some results back. We could see what's happening over the last 30 days.
[05:52](https://www.youtube.com/watch?v=I3UkFIhW548\&t=352s) 443 sessions started, 84% conversion. But, look at the automated decision before human review. So, auto approved 338, auto routed to in review, so 24% of all sessions, and then 24 declined. So, we could see it's just done an analysis of the approvals and the verifications. So, we could see the mechanism your default workflow ends in a blanket determine. So, I'm not going to read through everything here. It has suggested one change, replace the default workflow as blanket auto decide with the same AML score bands. So, low score AML false positives get auto approved instead of clogging review.
[06:31](https://www.youtube.com/watch?v=I3UkFIhW548\&t=391s) Kind of interesting. And you can run this analysis on your own account with your own data. And you can use AI to to help pull insights instead of having your team spend, you know, hours looking at your numbers and your reports. It's great just to pull reports straight from Claude. You can pull specific numbers. You can even create a nice formatted PDF report straight here from Claude that you can download, that you can present in your meetings. So, this is just a really cool way of accessing all of your data and then using AI to analyze it all in one place. You can see it's made a suggestion of what to change here. It's validated the analysis, and then we can go ahead and ask it to implement these updates. Like you see, if the publish live step is what gave you pause, you can apply it as an unpublished draft.
[07:22](https://www.youtube.com/watch?v=I3UkFIhW548\&t=442s) Yeah.
## Applying the change as an unpublished draft
[07:26](https://www.youtube.com/watch?v=I3UkFIhW548\&t=446s) So, we're going to apply it as a draft. So, if you did want to do this with a live workflow, and you wanted to check everything first before you change something that's live, then we can go ahead and do that, and you can see it's updated. So, we'll check that in a second. We'll just set this other
## Writing transaction monitoring rules
[07:41](https://www.youtube.com/watch?v=I3UkFIhW548\&t=461s) prompt off. to set up two real-time transaction monitoring rules. So a velocity rule that flags too many transactions in a short window and a structuring rule that captures amounts kept just under reporting threshold. Back test both against the last 90 days of my history and show me what they'd have flagged before we turn them on. So this can be super useful if you want to develop new rules that might make your verification process smoother, faster, and easier.
[08:09](https://www.youtube.com/watch?v=I3UkFIhW548\&t=489s) And then you can back test your data. So if we go over to didit, let's just take a look at the workflow that we had created. We can see the version history and we can see boom, we've got a draft here in the version history. Got the V2, we can publish that and boom, it's done that and you can see here at the bottom we do have the branching, which is the update that it was suggesting based off the AML screening. All right, so we've got a few questions here. Cord is asking
## Answering the clarifying questions
[08:35](https://www.youtube.com/watch?v=I3UkFIhW548\&t=515s) us what the velocity rule should be. So how many transactions in what window? Let's do three in an hour. What reporting threshold and just under band? So we're going to flag Yeah, the 10K threshold. Let's do all apps, all categories. Boom. So it's just getting clarification on the specifics of the rules that we want to set up because I noticed that I didn't actually specify that.
[09:00](https://www.youtube.com/watch?v=I3UkFIhW548\&t=540s) So it needs that information to create the rules and to run the back tests.
## Back-testing against 90 days of history
[09:05](https://www.youtube.com/watch?v=I3UkFIhW548\&t=545s) So we can see it's completed the back test. It did take a few minutes to go through all of this data. You know, looking back, I should have specified a specific time period or a specific amount of records to back test on. You know, if you have tens or hundreds of thousands of transactions inside of your account, then that might be something to consider or using a model like Sonnet that would be much quicker. So we can see it's flagged a few users here where the transactions over three in one hour. And we've got the structuring multiple payments underneath that threshold. And you can see extrapolated to the full history, we've got a an analysis here. I'm not I'm not going to read through absolutely everything here, but you can see two things worth noting before you enable.
[09:51](https://www.youtube.com/watch?v=I3UkFIhW548\&t=591s) It's given us some suggestions here. So, refine the rules, extend the back test. Uh we could even send a webhook to your Slack whenever these kinds of transactions come through or whenever somebody meets these specific rules. So, I'm just scratching the surface with what's possible here with these AI tools when you integrate with Didit. You can use it for analysis, intelligence, for flagging specific issues, for crunching
## Recap
[10:15](https://www.youtube.com/watch?v=I3UkFIhW548\&t=615s) data, for building, for editing, the whole works. And that's the Didit MCP from end to end. One hosted connector, an OAuth login with no keys to paste, and 108 tools that let you build workflows, clear reviews, set up fraud rules, and figure out your declines, all just by asking. So, if you want to try it yourself, head over to didit.me, sign up for free, and add the connector just following the steps in this video.
# Didit Academy
Source: https://docs.didit.me/academy/overview
Didit Academy video course with full transcripts: platform tour, KYC, KYB, workflows, transaction monitoring, integration and pricing.
[Didit Academy: Identity Verification & Fraud Prevention](https://www.youtube.com/playlist?list=PLIikxNViJoCY) is the official video course on the [Didit YouTube channel](https://www.youtube.com/channel/UCDnTpf4XOflvmwVdMiPSRIA). Every video has a full, timestamped transcript here - each paragraph deep-links into the exact moment on YouTube.
Video 1 of 10 - 10:59. Full transcript with timestamps.
Video 2 of 10 - 14:36. Full transcript with timestamps.
Video 3 of 10 - 18:54. Full transcript with timestamps.
Video 4 of 10 - 10:50. Full transcript with timestamps.
Video 5 of 10 - 14:26. Full transcript with timestamps.
Video 6 of 10 - 11:05. Full transcript with timestamps.
Video 7 of 10 - 13:05. Full transcript with timestamps.
Video 8 of 10 - 08:13. Full transcript with timestamps.
Video 9 of 10 - 06:37. Full transcript with timestamps.
Video 10 of 10 - 09:08. Full transcript with timestamps.
# Platform Tour: KYC, KYB & Transaction Monitoring
Source: https://docs.didit.me/academy/platform-tour
Video transcript: a full tour of the Didit console - dashboard, credits, KYC sessions, KYB, transaction monitoring, directory, API and MCP.
Full transcript of **What Is Didit? Full Platform Tour - KYC, KYB & Transaction Monitoring** - Didit Academy video 1 of 10, 10:59. [Watch on YouTube](https://www.youtube.com/watch?v=skV9iUR2UhQ). Each paragraph links to the exact moment in the video.
## What this course covers
[00:03](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=3s) Welcome to Didit. If you're just getting started, this is a quick tour of the whole platform. So, by the end, you'll know your way around and find anything on your own. That's the whole platform in one pass. Three products that follow your customer from the moment they sign up all the way through to ongoing monitoring, every check and every transaction rolling up into one profile, and you can work with all of it here in the dashboard, over on the API, or through an AI tool. If you want to jump in and try it, head to didit.me. You can sign up for free, and you can run your first verification in just a couple of minutes. Okay, so this is the first thing that you see when you log in. This
## The home dashboard
[00:44](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=44s) is the home dashboard. This is where you're going to do everything in Didit. Build your verification flows, read your results, watch for fraud, handle your billing, and manage your team. So, over here on the left, this is the navigation rail, and that's basically the map of the whole platform. So, we're going to walk through this together. I'm going to take you through each of the tabs, so you know exactly where to go for each of the products and each of the settings inside of Didit.
[01:10](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=70s) So, in the middle, that you can see here, this is a live overview of your traffic. So, on this side, where we see needs review, this is anything that's waiting on a human to make a decision. Over here, we've got recent activity, which is basically a live feed of everything happening across all of the
## Environments, credits and pay-per-success
[01:26](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=86s) three products in real time. So, up here, you could see production environment, and this switcher lets you flip between your live app, your sandbox. You can obviously add another application here as well. And down here in the corner, this is where we've got the balance, so you can add your own credits, discounts if you add higher amounts into here. And Didit is pay per success, so you load up your credits and you only get charged when a check actually completes.
[01:56](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=116s) So, that's the home base. We're going to get into all of these different menus and the analytics and the dashboard uh in this video and later videos, but this is where you're going to be spending most of your time. So, before we go any further, the simplest way to think about Didit it is really three products. They
## The three products, one customer lifecycle
[02:12](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=132s) follow the life of your customer. So, first you authenticate, so you make sure that they're a real person. Then you verify them, so things like KYC, KYB, biometrics, and AML. Then you monitor them for fraud. Those are the three products we've got. User verification, so KYC. We've got business verification, KYB. And then we've got transaction monitoring, which watches the money flow. Just a quick note, the wallet and crypto screening aren't a separate product. They live inside the transaction monitoring, which we are going to get into in a lot more depth in other videos.
[02:49](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=169s) So, we've got three products, one customer life cycle. So, let's dig into
## User Verification (KYC): sessions and checks
[02:53](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=173s) the first product, shall we? User verification or KYC. So, we can just tap into this tab here. And you can see every verification that's been run on a person. Each one's called a session. So, a single KYC check can bundle a few different things together. Government ID check that actually reads the document in any language, not just basic text scanning. We've got the liveness selfie. We've got the face match between the two, the selfie and the ID. And plus got some background checks, proof of address, email verification, device and IP, all of this stuff. So, you could see here that the icons and these represent the checks that happen inside of this workflow that you could see tagged here.
[03:36](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=216s) So, right now you could see we've got multiple statuses in this window. If we go back to the home page, we could see all of these need review. If we click view all and we go over back to the verifications tab, we can see that it's added the filter, right? So, if you want to add a filter for in review, we want to set a specific date range, we can do all of that in this dashboard. Whenever somebody gets tagged in review, this
## The review queue and webhooks
[03:59](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=239s) means that the system can't auto decide. This means we need a human on your team, someone in the compliance, someone in your in your review team to actually come along and check out why this person was flagged for in review. And the second a decision is made, it's also pushed straight to your own back end by webhooks. So, your entire system knows instantly when someone has passed or is in review. So, we're going to get into reading a full result in another video, but the short version, KYC
## Business Verification (KYB)
[04:27](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=267s) confirms a real person. So, let's get into KYB, so business verification. So, this one confirms a company instead of a person. It checks the business against official registries, it maps out who actually owns it, so the beneficial owners and the officers, and it can run an identity check on each of those owners all in one session. So, as you can see here, we've got the registry check, just like we saw in the user verifications, we've got these icons here that represent each of the checks that happen inside of a specific workflow. Don't worry, we're going to get onto workflows in a minute.
[05:02](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=302s) So, you end up with the company's details, the key people getting verified, and all the supporting documents in one place. We're going to build a full KYB workflow end to end in another video, but for now, KYB verifies
## Transaction Monitoring (fiat and crypto)
[05:14](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=314s) a company and the real people behind it. Let's take a look at the transaction monitoring tab here. This is the third product, and this is the fraud detection side of Didit. So, verifying someone at sign up only really covers them for the verification on day one. This part runs continuously after a user or business has onboarded and been verified. So, you could see here, we've got some payments declined, some approved, and again, we've got the filter section here at the top, and we can click this button, and it's going to automatically apply the filter to include all of the transactions that are in review. So, you send each payment over to Didit and a real-time rule engine scores it and opens a case if something looks off. So, it works for anything that moves money.
[05:54](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=354s) So, fiat or crypto. And remember that wallet and crypto screening is built right in here. I'm not going to get into too much detail in this video. I'm just giving you an overview of each of these individual products. So, if you go back to the home page, you can notice the transaction flow in the same activity feed as your verifications. So, you can see here we've got transaction, we've got KYB, KYB. So, everything runs through in this recent activity panel here on your home page. Listen, there's a lot more under the hood here, but the point is simple. Didit watches money movement in real time right next to
## The Directory: one profile per person
[06:27](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=387s) identity. Let's look at the directory tab at the top here. Let's go into users. So, this where everything ties together. We're going to look into the directory. We're going to check out the users. And everything we just looked at, the verifications and the transactions, all of it rolls up here into one profile per person. So, you can see here we've got a person here. You can see the checks that they've gone through. One session, we've got the country, and we have their user ID. So, each row is one individual. You've got the name, you've got the country, and Didit stacks up everything that person has ever done under this one single profile. You can see these little green icons here like you saw in the verifications and the KYB. These are all the checks that they've passed at a glance. So, you can see we've got ID verification, the liveness, the face match, the device and IP analysis. And this number here is every session stacked up under that one person. You can see just one session for this particular profile. You're also going to see the vendor data value. So, when we open up a profile, you also see a vendor data value. And that's the idea that your system sends across when it creates a session. So, in this case, it is an email address, but it can be a random number that's unique to this person. In this account, it's an email, but it can be any stable ID that your system uses. And below it is Didit's own internal ID for this profile. So, the vendor data is the key that ties everything together. So, every session that comes in with the same vendor data
## The business directory and linked fraud signals
[07:48](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=468s) lands under this same person. Okay, so let's head over to the business directory. That's where your verified companies roll up in exactly the same way, one profile per company. So, every person in every business carries a single status, active, flagged if something needs a look at, like an AML hit, or blocked. Over in the business directory, when we click on a business, you're going to get their complete profile. So, we've got the overview, profile, the verifications, transactions, payment methods, documents, devices, location, cases, activity. Everything is here under this one business entity, because their verifications and their money all live under the same profile. So, a payment gets judged against who the person actually is. So, this is how Deduct can catch things that disconnected tools just can't, like the same device showing up across accounts, sudden spikes in activity, or whole networks of linked people. So, one profile gives you the full fraud picture. Let's go back to the home dashboard and check out some more of these tabs down here. So, down at the bottom of the rail, there are two
## Configure vs Manage
[08:52](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=532s) sections where you set Deduct up. So, we've got the configure tab here, and we've got the manage tab. So, configure is everything that your users will see and experience. So, that's workflows, so where you build your verification flows, customization for your branding, questionnaires for any custom questions, and lists for your allow and block lists. And don't worry, we're going to get into these in other videos. But, just a little preview here in the customizations tab, you can actually see how this verification flow looks and how you can customize it. So, the second panel here on the left is manage, and this is how Deduct connects to your own app. So, that's where you integrate it, you grab your API keys, you set up your webhooks. So, results get pushed to you, and it's also where you handle your team and pull your reports. We're going to spend whole videos inside both of these tabs later on, but for now, configure builds the experience and manage connects it to your product. So, the
## Building your own dashboard
[09:46](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=586s) last thing I want to talk about here is back on the home page, we can actually build our own dashboard. So, the cool thing here is we can actually add cards however we want. So, these widgets, demographics, conversion rate, the locations, we can add them, we can drag and drop, we can move them around. We've got these filters here, we can filter by different date ranges. We can also look at the performance of specific workflows, and we can see all of that in this customizable dashboard below. So, the last thing to note is that everything that I've just walked you through, so creating sessions, building workflows, reading results, changing a
## Doing all of it over the API and MCP
[10:18](https://www.youtube.com/watch?v=skV9iUR2UhQ\&t=618s) status, all of that you can also do in code. So, you can grab an API key under the manage tab here on the left, and the whole platform is yours over a simple API. So, you can build your own dashboards, you can automate anything without ever logging in to this dashboard. You do all of that inside of Codex or Claude Code, also with the MCP server. So, we're going to get into that in another video, but that is the dashboard at a glance. And there's another way to work with Didit on top of that. It has an MCP server with over 100 tools, which lets AI assistants like Claude, Cursor, and Codex work with it directly. So, you can use Didit right here in the dashboard, in your own code, or straight from an AI tool.
# Didit Pricing Explained (Video)
Source: https://docs.didit.me/academy/pricing
Video transcript: how Didit pricing works - pay per successful check, 500 free checks a month, per-feature prices, no contracts or minimums.
Full transcript of **How Much Does Didit Cost? Pricing Explained (500 Free Checks a Month)** - Didit Academy video 10 of 10, 09:08. [Watch on YouTube](https://www.youtube.com/watch?v=aTWy1rfChhI). Each paragraph links to the exact moment in the video.
## Why Didit publishes its pricing
[00:04](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=4s) Let's talk about what Didit actually costs. Most verification vendors hide their pricing behind a sales call, but Didit puts everything out into the open so you can work out your own costs
## The pricing page
[00:16](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=16s) before you ever sign up. So, let's head over to the pricing page and break it all down. Okay, so let's start right here on the pricing page. And notice I didn't have to log in to see any of this pricing. Didit publishes every price openly, which most vendors simply won't do. The model is simple. You start free and you pay per successful check for only the modules that you turn on. And you can scale all the way to enterprise on the same account. And every module's price is listed here with no minimums and no contracts to begin. And you're charged only for the checks that you actually run. So you can compare the
## The three plans
[00:52](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=52s) cost of each check before you ever sign up. So as you can see here there's three plans and the thing to understand is that you pick by volume and the terms that you need not by features because the same modules are available on every plan. So there's three tiers. There's free, there's usage based and there is enterprise. Free gives you 500 verifications every month plus blocklist and duplicate detection checks on every session. Usagebased is payer use across dozens of modules with no monthly minimum fee. An enterprise adds a custom master service agreement, an SLA for large volumes and regulated programs. So you're choosing by volume and terms and the same checks. They're on all three.
## What the free plan really includes
[01:38](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=98s) Now, if we take a look at the free plan, this is a real production tier. This is not a sandbox. It cost \$0 a month and needs no credit card. And several anti-fraud checks are already included on every session at no cost. So, every session is screened against your block lists for free. Duplicate detection captures the same person signing up
## 500 free credits, every month
[02:01](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=121s) twice, which is matched on the vendor data ID that you send over. And over 200 device and IP fraud signals get analyzed on every session, again, at no cost. So, it's a free tier that you can genuinely run in production. You get 500 free verification credits every month, and I'm going to show you the features that you can do that on in just a second. So, if we go into Didit and we check out the workflows and let's say if we So, let's say we open up a new workflow, we do user verification, uh we do simple workflow, start from scratch, you're going to see the pricing of each of the modules. So you can see ID verification
## The usage-based plan
[02:41](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=161s) 500 free per month, livveness 500 free and face match 500 free. And also I mentioned earlier the device and IP analysis again you get 500 free verifications. So the allowance resets on the first of each month. And while unused checks don't roll over, you always get a fresh 500 and it's permanent, not a timelimited trial. So that's 500 free identity checks a month forever. Now, if we take a look at the usagebased plan here in the middle. So, when you outgrow your free tier, you move to usagebased, the pay-per-use plan. It's the same account, no migration. You just start paying for what you run beyond the free allowance.
[03:21](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=201s) It's essentially pay as you go across dozens of modules. So, again, if we go back into the workflow, we could see what modules that we're going to need to pay for straight out the gate and which modules we're going to have to pay for after we've used up our 500 free credits. So you can turn whatever modules on or off that you want to use in your verification flows and you'll only pay for the successful checks that a user goes through. So that's why we see at the top here we've got a price from \$0 to 15. And if we click on the
## How a workflow's price is assembled
[03:52](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=232s) pricing, we can see how this workflow is priced. So you see you only pay for the steps that actually run. So if somebody enters the workflow and leaves immediately or after that first check, you're not going to pay anything because the first check is ID verification which is free. You're not going to pay anything because the ID verification you get 500 free. Okay? And after that it's
## KYB pricing
[04:15](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=255s) only going to be 15 cents. So that's going to be the maximum cost of this workflow. And it's the same for KYB. So the business verification. So, if we go into a KYB workflow, we can take a look at some of the modules inside of there. If we zoom in here, we could see the registry check \$2. The KYB is variable because it's going to depend on how many people we need to verify on that step.
[04:39](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=279s) Got the AML screening. If we want to add in any additional checks, we can do that here. And just know this AML screening is 20 cents per check, but that runs people against over 10,000 data sets covering sanctions, PEPS, adverse media.
## Database validation pricing
[04:55](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=295s) So the only other module that is variable on cost is the database validation. So if we add that node into here, we click database validation and you see the panel that opens up here on the right. You can see we can select whichever database validation we want to run. And this is country specific and database specific. You can see if we want to run a credit check, it's going to cost us more than checking the consumer data, for example. But the cool thing is you can see the cost of absolutely every check in here. So you're not going to be surprised when somebody runs through this workflow and you're you're wondering why is this costing so much? The price is going to appear up at the top here. And you can see it is variable. It just depends on how far somebody gets through this step.
[05:40](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=340s) So if I select a database in here, you can see it's going to give us a a price
## White-label pricing
[05:44](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=344s) down here. So the only other thing that I haven't mentioned here is the white labeling. So if you want to set up the white labeling, we can turn that on. It's going to cost 20 cents per verification. And you can see when we toggle this on, the price at the top, it's going to change. So this is going to consider the
## No minimums, no contracts
[06:03](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=363s) cost of absolutely everything inside of this workflow. You can see custom branding extra 20 cents. So with Didit there are no minimums. There's no contract on the self-s served plans and there's nothing to commit upfront. So if you spend nothing in a quiet month your account stays open and with the free 500 a month still available. So if you spend nothing in a quiet month your account stays open with the free 500 a month credits still available. Uh the usage plan has no monthly floor, no platform fee, no seat fee. So your bill is
## Why the prices are this low
[06:34](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=394s) exactly your usage and nothing more. You might be wondering how the prices come in this low. You see, Didit runs its own optimized verification infrastructure with sub 2C inference? And that's why a complete identity check from about 30 cents is typically 70 to 80% cheaper than comparable providers for the same checks. And because it's all published openly, you can compare line by line instead of negotiating a
## Enterprise
[07:01](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=421s) hidden quote. So for large volumes and regulated programs, there's enterprise. It gives you custom pricing with volume discounts on top of the public per module rates. You also get access to a dedicated Slack channel, the WhatsApp channels with manual reviewers on demand and reseller and white label terms with four deployed engineers who build alongside you. at high volume, the team genuinely recommends it because they like working closely with customers to fit into their flows and improve their KPIs. So, if you're running serious volume here, that's the conversation to have. So, let's head over to the billing
## Billing, top-ups and auto-recharge
[07:40](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=460s) section inside of our dashboard here. The bottom here, we have our current balance. This is where you're going to get build from. So, you're going to need a prepaid balance to use Didit. Now, as you can see here, there are discounts depending on how much you decide to top up. You can also set the auto recharge function as well, so you can keep running your checks without your balance going to zero. So, if we head over to the settings tab here, we can head over to billing. And again, we've got options to top up. We've got previous deposit history in here as well. And the amount that you top up in here, your credits, they never expire. So, if you need your invoices for your accounting team, you can just come in here and download them.
## The usage tab
[08:18](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=498s) So if we head over to the usage tab, this is where you're going to see exactly where your credits go. So you can see the spend breakdown module by module and you can filter by date range at the top here and by application. So if you have multiple applications, you can see which application is spending on which checks. So the whole thing is
## Recap
[08:38](https://www.youtube.com/watch?v=aTWy1rfChhI\&t=518s) refreshingly simple. You start free every month and you only pay for the checks that you actually use. And the credits that you buy, they don't expire. So, there's no contracts, no hidden fees, and you can see every price for yourself before you commit a penny. Honestly, the best way to know your real number is to just start using it. So, head over to diddit.mme, sign up for free, and run your first verification in a couple of minutes today.
# Real-Time Transaction Monitoring Rules
Source: https://docs.didit.me/academy/transaction-monitoring
Video transcript: setting up real-time transaction monitoring - fraud rules for fiat and crypto, alerts, cases, and wallet screening.
Full transcript of **Real-Time Transaction Monitoring: Fraud Rules for Fiat and Crypto** - Didit Academy video 7 of 10, 13:05. [Watch on YouTube](https://www.youtube.com/watch?v=Yhm63_n7HuU). Each paragraph links to the exact moment in the video.
## What this video covers
[00:04](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=4s) In this video, you'll see how Didit watches the money moving through your app and catches the transactions that should worry you. Checking who someone is when they sign up is one thing, but it doesn't tell you what they'll do once they start moving money around. That's the job of transaction monitoring. It scores every payment the moment it lands, ties it back to the person who made it, and flags the things that actually matter. So, a sudden spike in activity, a payment that's far too big, or a match against a sanction list. It works for regular money and for crypto, all against rules that you set yourself.
[00:40](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=40s) So, let's open it up and walk through it. All right, so we can head over to the
## The live transaction feed
[00:45](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=45s) transactions tab here on the left. And this is a live feed of every payment Didit is watching. So, it's ranked by date, so the newest first, and each row is one economic event. So, we're talking a deposit, a withdrawal, a transfer, a payment, a payout, or a bet. And every one of them gets scored and decisioned in real time as it arrives. So, the place that you'll probably want
## The review queue and filters
[01:12](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=72s) to head to first is this tab here, 100 transactions to be reviewed. We'll click on that, and it's going to automatically filter out the transactions that need to be reviewed by your team. And if you want to see everything, we can add the filter all statuses. We can refine this if we want, if we want to select a specific day in the last 7 days, for example. If you want to search for a specific transaction ID or a Didit ID. If we want to look in specific currencies, etc.
[01:42](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=102s) But for today, we're just going to flip the status to in review, and these are the ones we're going to walk through because these are going to be most important for you and for your own use cases. So, we
## Every transaction tied to an identity
[01:52](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=112s) can see this transaction here is connected to a specific identity because nobody in your platform is going to be able to complete transactions without going through the KYC or KYB. We can see the type. This is crypto in this scenario. The amount, any tags that are involved, and the date that it was created. Now, one thing to note here is that we actually have the counterparty
## Counterparty screening
[02:15](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=135s) here. They also get screened against sanctions and watch lists. The same AML engine that your identity checks already use. You can see we've got the score here. I'm going to jump into the transaction. I'm going to show you exactly what's going on here. So, if we click on one of these, again, I will preface this by saying this is this is demo data. This is not a real transaction, but to show you how this works, I want to walk you through each of these tabs. So, if we scroll down, each of these tabs corresponds to a tab up top here. Just like in the KYC and KYB, we can change the status here, download as a PDF, we can add to a block
## The overview tab and risk score
[02:49](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=169s) list. So, let's take a look, right? So, in the overview section, we've got the risk score. So, this is medium. Uh it's been flagged as in review. We can see all of the warnings here. So, in the crypto monitoring, cuz this is a crypto
## Wallet risk and what the score bands mean
[03:01](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=181s) transaction, we see there's a high wallet risk score. And we can read what this actually means. So, if the risk score sits between 70 and 89, it's going to escalate it for review. So, wallets at this score have a meaningful exposure to illicit clusters, like mixers, darknet markets, and scams. So, these are all the things that we want to be looking out for when we're reviewing this particular transaction. We could see it's been flagged for a large single
## Vendor data
[03:28](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=208s) transaction. Now, just like with the KYC and KYB, we've got the vendor data here. So, all of these transactions for this particular vendor data, so in this case we're using an email, and it can be a unique identifier. So, this person will have gone through KYC. And all of the transactions that correspond to this vendor data will be
## How the risk score is calculated
[03:52](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=232s) flagged underneath this particular user. So, you might be thinking, well, where does this score come from? You could see the risk score is 284, there's three rules matched, eight rules evaluated, and one open alert. Like, what's going on here? Well, every transaction is decisioned the moment it arrives, and each rule that matches adds to a cumulative risk score. And so, your thresholds decide when that
## The four transaction statuses
[04:17](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=257s) score is high enough to send it to review or to decline it outright. So, in transactions, there are actually four statuses. We've got approved, meaning the transaction is clean, in review, like this particular example here, we've got declined, and then we also have awaiting user. So, when a rule needs a bit more assurance before it lets a payment through, the status flips to awaiting user. So, Didit quietly spins up a linked verification session, hands back a URL, and the user re-verifies themselves, and the transaction picks up where it left off.
## Getting transactions into Didit
[04:51](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=291s) So, before we start digging through all of this information here, how do we actually get transactions into Didit? Well, we can import them from a CSV or a list. We can integrate, which is going to be the obvious option for for most of you watching this. You integrate with the SDK. And we can also create a manual transaction, and this really is only going to be for testing purposes.
[05:15](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=315s) It's going to be a massive hassle to create a manual transaction every time a transaction runs through your company. So, if you click integrate, you can see we can just literally copy the prompt, select an API key, and then we can paste that into our cloud code or code X, and it will automatically set up the installation, so that your transaction monitoring is completely set up. Okay, so let's work through some of these checks and tabs here, and let me explain what each of them mean. Now, there is a lot of information in in but the reason why this account was flagged is because of the rules, and we'll get into the rules
## Transaction details
[05:49](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=349s) section uh a little bit later on. But, we can see here this this is all of the transaction details that we've got here, crypto outbound, uh the country from Argentina to Netherlands, the transaction ID, the payment ID, payment details, and the vendor data, and the the currency amount. We've got the AML risk score. This just means the score that we get from the anti-money laundering module.
[06:13](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=373s) We can look at all of the specifics of the transaction, the chain, the wallet risk, in this case potential scam wallet risk, the beneficiary wallet, and the wallet score risk. We've got some alerts here, the simulated crypto exposure. This is demo crypto transaction flagged by synthetic monitoring metadata, and
## The crypto exposure visualization
[06:30](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=390s) medium. We've got the crypto monitoring. This is really cool. Let's click visualization, and you're going to be able to see the visualization of the wallets, how they're connected, the transactions. So, this can be really useful if you're trying to spot scams, and mixers, things like this. So, we've got the whole transaction overview here. You can see we've got an analysis here from Merkle Science. This is a third-party tool that you can integrate here into Didit for crypto screening and transaction analysis. If we keep going down here, this is all part of the crypto
## Counterparties
[07:03](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=423s) monitoring from Merkle Science, transaction summary, exposure table, and then we get to the counterparties. So, this is the person who is receiving the the funds in this particular transaction. We can see this person is not verified inside of the current system, so it's going to check if this particular wallet is already inside your platform, and then this is the important part down here at the bottom. These are
## Triggered rules
[07:27](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=447s) the triggered rules. So, we're going to look at these rules in a moment and see how we can set these up for our transaction monitoring, but if you click on each one of these, you could see what each rule is all about. So, we've got the rule identity, the trigger, the conditions, the actions, deploy settings. I'm going to run you through these in just a moment. You see there's related transactions down here.
[07:48](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=468s) Yeah, it's a lot of information, but the the thing to focus on is the rules. So, let's go ahead and create some rules,
## Creating a rule
[07:55](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=475s) shall we? Now, there is a library of rules. These are defaults that you can set up when you start a brand new account. We've got the installed rules that you can set up manually. We can create the rules here, so rules are essentially filters. So, if this happens, then do this. That's essentially what a rule is doing. So, we can give it a name. We'll call it test. We can add a description if we want. We can add a specific category.
## Triggers
[08:18](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=498s) So, let's say we select geography and we move over to the trigger. Triggers on all transactions. We could specify by the channel, so crypto or fiat. Let's do crypto.
## Conditions
[08:29](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=509s) The entity type, we can specify by individual or company. I'm going to do individual. We go through to the conditions. We can look for, I don't know, travel rule. Uh we can look for subjects. We can look for the country equals maybe if we put a high-risk country on there, somewhere in Africa, for example. We can add an additional condition here as well. So, if someone's putting multiple transactions through in a short space of time from this country, then that's what this rule is going to flag. And then we
## Actions and scoring
[09:01](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=541s) can decide whether that's going to decline the transaction or put them into review. So, you can see here, choose what happens on a match. We change the status, right? We could add points. Okay, so that's what I was mentioning earlier. That's how the score is calculated. So, we can say add maybe 50 points in here, for example. And then
## Back-testing and deploy settings
[09:24](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=564s) we've got the deploy settings. We can back test the rule to see how many hits this rule is going to get. Now, this is important because if you have a very restrictive rule, that's going to mean a lot more work for your team. So, you've got to evaluate the the risk versus reward of setting up these kinds of rules and how strict you want to be with this. Now, you can see on here, each rule shows you how often it fired and how much of that was sent to review or declined. For example, you got this percentage just here, and this is the data that you're going to lean on to tune the rule. So, if setting up your own rules seems like a lot of work, you could just actually have a conversation with Claude and say, "We want to filter out these kinds of people or these kinds of transactions.
## The rule library
[10:07](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=607s) We want to detect this kind of behavior from these countries." You can also look in the library and we've got different categories here. We've got AML, nominee detection, crypto monitoring, device intelligence, finance for protection, responsible gaming, travel rules, and so there's a library of different rules that you can pull from inside of there. So, there's one condition in this rule
## Velocity conditions
[10:33](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=633s) that I was working on earlier is the velocity condition. And this is pretty clever. So, instead of judging one payment in isolation, a rule can add up or average activity over a specific window of time. So, anywhere from an hour to 90 days, grouped by the user, the device, the IP, the payment method, or the counterparty. So, that's how you're going to catch structuring and sudden bursts that no single transaction would ever reveal.
[10:59](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=659s) So, we could hit velocity condition. So, we can do the count of transactions in the last 24 hours for the same user, and we can specify is greater than, let's say, five, for example. So, that's the velocity condition. I think that's pretty clever. I've not seen this in any other platform. The idea being that AI can surface hidden patterns from your own history. Now, you could do this with Claude right now. You could hook up the MCP, ask Claude to analyze your data, and create rules
## Turning on crypto monitoring
[11:26](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=686s) based on the patterns that it's seeing. So, if you were wondering where to turn on crypto compliance and the crypto monitoring integration. You can see here in the transactions tab, we can go to settings. We can adjust the risk threshold and then we can select the crypto monitoring Merkle Science. We can turn that on 15 cents per check. You can bring your own key, that's going to be happening sometime soon in the future. So, we can flip that on or off to your own needs.
[11:54](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=714s) So, one final thing on the dashboard,
## Transaction analytics
[11:56](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=716s) again, we can adjust our analytics here to include transaction monitoring as well. So, if you click on add, we can come through to transactions. We can take a look at the volume, for example, the action breakdown, approved versus rejected, and then we can add that to our analytics dashboard. And you can see that's going to appear down at the bottom here. And to move these tabs around, we just click edit, and we can let say we remove this one, and yeah, we can move these around like this. We can change whatever order we want them in.
## Recap
[12:34](https://www.youtube.com/watch?v=Yhm63_n7HuU\&t=754s) So, that's transaction monitoring end-to-end. Every payment scored the moment it lands. So, regular money and crypto, all through one engine, each one tied back to a verified person or business. And anything suspicious flagged for your team to review and act on. If you want to try it for yourself, you can head over to didit.me, sign up for free, and send your first transaction today.
# How to Read a Verification Result
Source: https://docs.didit.me/academy/verification-results
Video transcript: every check in a Didit verification result explained - document authenticity, liveness, face match, IP analysis, warnings.
Full transcript of **How to Read a Didit Verification Result - Every Check Explained** - Didit Academy video 5 of 10, 14:26. [Watch on YouTube](https://www.youtube.com/watch?v=7htTWCWsl60). Each paragraph links to the exact moment in the video.
## What this video covers
[00:03](https://www.youtube.com/watch?v=7htTWCWsl60\&t=3s) Every verification you run in Didit comes back as a detailed result and that result is laid out as a row of tabs across the top of a session. In this video, we're going to go walk through those tabs in order left to right so you know exactly what each check does and what it looks like when one of them fails. And by the end, you'll be able to open any session and read it with confidence from the headline verdict right down to the evidence behind it.
[00:30](https://www.youtube.com/watch?v=7htTWCWsl60\&t=30s) So, let's open one up and get stuck in.
## Finding a session
[00:33](https://www.youtube.com/watch?v=7htTWCWsl60\&t=33s) Okay, so let's navigate to the user verifications. This is where every session is going to appear from all of your workflows. It's going to be one row per person. And normally you'd filter by status, workflow, or date to find whatever you need, but today we're just going to go straight into a single result. So, I'm just going to click on one of these and open it. So, right here we can see this
## The overview tab
[00:55](https://www.youtube.com/watch?v=7htTWCWsl60\&t=55s) is the overview tab. These are collapsible so we can collapse these. We can collapse all the checks if we want if we want to see everything, but honestly they're all up here as well. So, you could see this particular user has passed all of these specific checks. So, if we just head over to overview, we
## Warnings: where to look first
[01:13](https://www.youtube.com/watch?v=7htTWCWsl60\&t=73s) can pull that out and you can see we've got a few different tabs here. Now, the most important thing to check out here is the warnings. So, you could see this user is in review and you can see they've been flagged for a duplicate phone number. Okay, so if we check out other people that are in review, we could see the warnings in the overview section might look a little bit different. Based on the flags from the workflow, this particular user has gone through. You could see the database validation and the name mismatch. So, this is where we need to look if we're reviewing a verification and we need to figure out what the issue is and then we can go to the specific section and we can see where these checks actually are and we can look at the data that's been submitted to see if there is indeed a mismatch and what's really going on there. Now, if we remove all of these filters, so I can just come out of here,
## An approved session
[02:04](https://www.youtube.com/watch?v=7htTWCWsl60\&t=124s) we can remove these filters. So, I'm going to select all of these and let's see if we go into someone who has been approved, we can see there's there's no major warnings that have been flagged. We can see this has been listed under the warnings, but it's not triggered the in review or the decline. This has actually been approved. We can see this little pill up here and we can actually change these as needed. We'll get into
## A declined session
[02:26](https://www.youtube.com/watch?v=7htTWCWsl60\&t=146s) that in a little moment. And what does it look like when someone has been declined? We can see here the warnings, again, the name mismatch. And then up here at the top, we can see proof of address is the one that caused some issues. So, we can come in and take a look at this as well. So, we're going to go through each section one by one so you could see exactly what's happening underneath the hood. So, I'm just clicking into this profile here and you can see the liveness and the face match is where this particular user has been flagged for checking. So, we'll get to that in a moment. But the rest of the
## Session details, vendor data and cost breakdown
[03:01](https://www.youtube.com/watch?v=7htTWCWsl60\&t=181s) tabs in the overview section, we've got session details, so you've got the session ID, when it was created, the vendor data. So, we spoke about this in previous videos. The vendor data is a unique identifier for this particular user. This is where all of the \[snorts] checks, all of the verifications and transactions are going to be collected under the specific vendor data. We can see the workflows that this user has gone through for the verification. We've got the contact details here and any tags. I mean, we can we can add a tag here if you want.
[03:31](https://www.youtube.com/watch?v=7htTWCWsl60\&t=211s) We can tag people in workflows as well. Now, the total cost breakdown, I believe this this user was imported. You can see here on the left, it was imported, so he hasn't actually gone through a workflow. Let's click this other user here and you can see the workflow that they've gone through and you can see the cost
## ID Verification: authenticity before data
[03:48](https://www.youtube.com/watch?v=7htTWCWsl60\&t=228s) breakdown of all the checks this user has completed. So, let's take a look at the ID verification. Now, the main job is not just reading text off the card. It's It's proving the document itself is genuine before anyone trusts a single field on it. So, under the hood, we're running tamper detection, document liveness to catch a screen replay or a printed copy, security feature validation for holograms and watermarks, and a a cross-check between the printed fields and the machine-readable zone, the MRZ, the block of chevron characters at the back that you could see here on this particular ID. Now, once the document is proven authentic, Dedit will read the fields with OCR and lay them out. Here, you can see the personal data. Got the document type, the document number, personal number, issuing state, all of the information that you would find on the ID, including the address data and any additional data in here. And remember, Dedit can read more than 130 languages across 14,000 document types. So, you're going to get clean fields
## The checks list
[04:47](https://www.youtube.com/watch?v=7htTWCWsl60\&t=287s) wherever someone presents their ID. So, there is a tab here called checks list. And these are the checks that are running in the background on this specific ID. So, I'm not going to read through all of these. There is a lot here, but this is essentially how Dedit is verifying that this is an authentic ID. So, you don't have to configure any
## Liveness: score, video and quality
[05:07](https://www.youtube.com/watch?v=7htTWCWsl60\&t=307s) of this. This is all running in the background. So, the next check is the liveness check. You can see that this particular check is in review. This is what's been flagged. You can see liveness and face match. Now, I will say this is a demo account, so some of this data is not matching. Um you can see the liveness score is it's not 100%, and if we play the liveness video, you could see it's it's not actually the person.
[05:30](https://www.youtube.com/watch?v=7htTWCWsl60\&t=330s) This is just a random example. Now, if you were to see this on a on a real user, you would see the the person doing the liveness check, the video, whether it's passive or active. We've got the face quality score and the face luminance score. And the cool thing about Dedit is if you're confused about any of these scores, you can just click this button here and it'll give you an explanation of what this does. We can change the status of any of these checks if we believe that this has been flagged inappropriately, and obviously we can change the overall status of this verification. Again, just like the ID verification, we've got a checks list and there's a bunch of checks here that are all happening in the background, right? And we can hover over these and we can see a little bit more information about each of these checks. And if somebody fails any of these tests, instead of the green
## Face Match
[06:16](https://www.youtube.com/watch?v=7htTWCWsl60\&t=376s) tick, there's going to be a red X. So, if we keep going, we've got the face match tab. This is going to match the face on the ID with the face in the liveness check. And you can see here, obviously the similarity score is pretty low because they're not really the same person. And again, we've got a checks list of what's happening in the background there and what this particular check is doing. So, the similarity score is based on the original ID and the liveness image that we've captured. And this is to figure out if the person holding the ID is a genuine owner. Now, we're not going to see this in the dashboard, but there is a face
## Face Search and block lists
[06:54](https://www.youtube.com/watch?v=7htTWCWsl60\&t=414s) search. So, beyond matching against the document, Didit searches the same face across all of your other sessions. So, this part only shows when it finds a match. So, if the same face turns up in another session, a grid of matches is going to appear here. And if one of those matched faces is on a block list that you maintain, it's going to be flagged and the session will be declined, which is how a known fraudster gets stopped on site. But on a clean session with no matches, this area stays
## Proof of Address
[07:21](https://www.youtube.com/watch?v=7htTWCWsl60\&t=441s) empty, so we're not going to see anything here. Let's move on to proof of address. We can see the document that was submitted and the data that was extracted from this particular document with all of the metadata. So, we've got a checks list here and we can see one of the checks does not have a green tick, meaning that this check was being flagged. And we can see one is in review. Now, that's not enough for this proof of address module to be flicked over to declined, but this is again something that your human reviewer is going to be looking at when they come to a profile that is in review. And a pass here means the code was entered correctly and a failure means the number couldn't be confirmed, and you'll see that reflected in this module here. So, you remember at the top, one of the flags here was duplicate phone number, and now that's probably due to this being a demo
## Phone verification and duplicates
[08:08](https://www.youtube.com/watch?v=7htTWCWsl60\&t=488s) account, but you could see when we scroll down under the phone verification, we could see phone matches. Now, this is going to search the phone number on your database, and if it matches anything from a previous user, then this is going to be flagged in this tab here. We can also hover over here. We can see other verification sessions that use the same phone number. This is just an explanation of what this module does.
## Email verification
[08:35](https://www.youtube.com/watch?v=7htTWCWsl60\&t=515s) Same for email verification, the user is going to receive a code and put the
## AML screening: sanctions, PEPs and adverse media
[08:40](https://www.youtube.com/watch?v=7htTWCWsl60\&t=520s) code, and if everything's good, then this module is also approved. Now, the anti-money laundering, the AML screening, there's quite a lot going on here. This checks the verified person against sanction lists, politically exposed persons, adverse media, and then reports how many matches were found. So, you could see the data that it's using to screen, and we can actually see the list. So, if I open this up, we could see all of the sanction lists here that we can run the checks against. So, narcotics, terrorism, financial crime, everything. And we can see if this user has matched on any of the lists, we could see here the match score, the risk score, and the lists that this user has appeared on and whether it's a false positive or not. Now, a false positive can occur if the name matches somebody on one of those lists. So, this is where we have other information like the date of birth to to cross-reference. We do have a checks list here again. Uh it's pretty short for this particular one, but the the checks against all these lists is doing the heavy lifting here.
[09:41](https://www.youtube.com/watch?v=7htTWCWsl60\&t=581s) And if you want to have this person monitored ongoing, where we can check the lists against this user on an ongoing basis, we can actually turn that on. If we keep moving down, we can see
## Device and IP analysis
[09:50](https://www.youtube.com/watch?v=7htTWCWsl60\&t=590s) the device IP analysis. So, we have the ID verification document location. So, the actual address from the document. We have the address details here for the proof of address. And then we have the device information. So, the IP address and where that person is actually completing the verification from. And we could see at the bottom the document location versus the proof of address, how far away those distances and those locations are from the verification IP. And we can see all of that on this map. So, we see if we hover over here, we've got the ID verification document location.
[10:28](https://www.youtube.com/watch?v=7htTWCWsl60\&t=628s) Got device number one, so they're pretty close. And then we have the proof of address in the US here. Again, this is a demo account, so these details are not particularly accurate, but this just gives you a good understanding of is that person close to where the proof of address and their ID actually is. And here we have the checks list again, and these are all the checks that ID it is doing in the background to decide whether to flag \[snorts] this particular check or not. If we keep going down, we've got database validation. So, if you remember in previous videos, we spoke about database validation. It's a node that you can add in a workflow. And what we can do here is we can run a check. And for this example, we can run
## Database validation: full, partial and no match
[11:06](https://www.youtube.com/watch?v=7htTWCWsl60\&t=666s) a check in Argentina, and we can check against a government registry, civil registry, the tax authority, the electoral roll, all of these different directories that we do have access to. You could see we have a match found here. And you could see the source data, the fields that match on the results. So, we've got a full name match and a date of birth match. And if we open up the checks list, we can see one of these checks was flagged.
[11:29](https://www.youtube.com/watch?v=7htTWCWsl60\&t=689s) And we can see here the match type. Now, if a full match has been found, it means the identity exists in the registry. Partial match means some fields matched and you should review the rest. And a no match is a strong synthetic identity signal because a fabricated ID number simply isn't in any registry. And this is one of the strongest defenses you have against made-up identities. And again, it runs in the background with no
## Document AI and questionnaires
[11:54](https://www.youtube.com/watch?v=7htTWCWsl60\&t=714s) extra steps for the user. Now, moving on, we do have a few other tabs here and these can vary depending on the checks that this particular user has gone through. We can see document AI. This check hasn't run. You can see not started. There are other checks that we do have for questionnaires. If somebody ends up filling out a questionnaire, we could see the answers to those questions in
## The event log
[12:15](https://www.youtube.com/watch?v=7htTWCWsl60\&t=735s) the questionnaire tab. And then if we keep on moving down, we've got the events. So, this is a audit trail, a log of all of the events that happened. So, when the session was created, when the session status changed, and each entry carries the device, the IP, the timing, who did it, so you can reconstruct
## Webhook deliveries
[12:31](https://www.youtube.com/watch?v=7htTWCWsl60\&t=751s) exactly how a session unfolded. And then finally, at the bottom, we have webhooks. So, webhooks list every delivery Didit attempt to your own endpoint with the response status. We can see 200 here. And we could see the request header, the body, the webhook URL, the status of this particular verification. And we've also got the date here this webhook was sent. So, one thing to note here is that this dashboard can be accessed through the API through the MCP. You can ask Claude to pull all of this data, to pull all of your verifications that are in review that you can analyze inside of
## Resubmission, PDF export and block lists
[13:08](https://www.youtube.com/watch?v=7htTWCWsl60\&t=788s) Claude as well. Now, if you take a look at the top here, we've got some breadcrumbs and we can see we can request resubmission. We can download the PDF, which is excellent if you want to have an audit trail or you need to present something in a meeting. Um you can also access this download through the API and you can add this person to a block list. So, that is how you read a verification
## Session chat for your review team
[13:30](https://www.youtube.com/watch?v=7htTWCWsl60\&t=810s) result. There's one last thing at the bottom here. We've got a session chat. So, we can actually tag team members, and we can talk about a specific verification result with somebody on our team. So, if we opened up this chat and we put in a message and we tagged someone on our team, that's going to be specific for this particular verification. So, if we do have a question or there's multiple people working on this case, this chat is going to be incredibly useful.
[13:55](https://www.youtube.com/watch?v=7htTWCWsl60\&t=835s) So, that's a verification result read the way you'll actually read it, tab by tab, top to bottom. The verdict and the warnings on overview, then each check in turn, and the full audit trail at the end. So, once you've done it on one session, every other session reads the same way. And if you want to try it on your own data, head to didit.me, sign up for free, and open your first result.
# Argentina Citizens (AFIP)
Source: https://docs.didit.me/api-reference/database-validation/argentina/citizens
POST /v3/database-validation/
Verifies input data to the AFIP (Administracion Federal de Ingresos Publicos). Authoritative real-time identity lookup for Argentina. Real-time lookup, pay-per-call.
Verifies input data to the AFIP (Administracion Federal de Ingresos Publicos). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Argentina
* **Service ID:** `arg_citizens`
* **Data domain:** Identity
* **Category:** Citizenship
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `document_number` | Yes | `1111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `document_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$1.90 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ARG`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `arg_citizens`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Document number extracted from or provided by the user.
Example: `1111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Argentine DNI (7-8 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be 7-8 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ARG" \
-F "services=arg_citizens" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "document_number=1111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ARG",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "arg_citizens",
"service_name": "Argentina Citizens (AFIP)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ARG",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "arg_citizens",
"service_name": "Argentina Citizens (AFIP)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ARG",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "arg_citizens",
"service_name": "Argentina Citizens (AFIP)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `arg_citizens` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Argentina Citizens (AFIP) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.90 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Argentina Database Validation overview](/api-reference/database-validation/argentina)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Argentina Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/argentina/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Argentina. Authoritative real-time identity lookup for Argentina. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Argentina. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Argentina
* **Service ID:** `arg_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `document_number` | Yes | `1111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `document_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$2.15 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ARG`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `arg_credit_bureau`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Document number extracted from or provided by the user.
Example: `1111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Argentine DNI (7-8 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be 7-8 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ARG" \
-F "services=arg_credit_bureau" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "document_number=1111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ARG",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "arg_credit_bureau",
"service_name": "Argentina Credit Bureau",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ARG",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "arg_credit_bureau",
"service_name": "Argentina Credit Bureau",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ARG",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "arg_credit_bureau",
"service_name": "Argentina Credit Bureau",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `arg_credit_bureau` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Argentina Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Argentina Database Validation overview](/api-reference/database-validation/argentina)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Argentina - DNI verification (RENAPER)
Source: https://docs.didit.me/api-reference/database-validation/argentina/renaper
POST /v3/database-validation/
Verifies Argentine DNI against the official RENAPER civil registry, with biometric face-match against the photo on file. 100% population coverage. Authoritative real-time identity lookup for Argentina. Real-time lookup, pay-per-call.
Verifies Argentine DNI against the official RENAPER civil registry, with biometric face-match against the photo on file. 100% population coverage. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Argentina
* **Service ID:** `arg_renaper`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | --------------- |
| `document_number` | Yes | `1111111` |
| `selfie` | Yes | `@./selfie.jpg` |
| `gender` | Yes | `M` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`, `selfie`, `gender`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ARG`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `arg_renaper`
Document number extracted from or provided by the user.
Example: `1111111`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Gender value, when required by the database.
Allowed values: `M` (male), `F` (female), `X` (other or unknown).
Example: `M`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Argentine DNI (7-8 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be 7-8 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ARG" \
-F "services=arg_renaper" \
-F "vendor_data=user-1234" \
-F "document_number=1111111" \
-F "selfie=@./selfie.jpg" \
-F "gender=M"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ARG",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "arg_renaper",
"service_name": "Argentina - DNI verification (RENAPER)",
"source_data": {
"banks": "sample_value",
"date_of_birth": "1990-01-01",
"face_match_score": "0.990",
"first_name": "John",
"full_name": "John Doe",
"highest_position": "sample_value",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"last_position": "sample_value",
"rejected_checks": "sample_value",
"sit_1_since": "sample_value",
"tax_id": "SAMPLE-12345",
"tax_id_type": "SAMPLE-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ARG",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "arg_renaper",
"service_name": "Argentina - DNI verification (RENAPER)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ARG",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "arg_renaper",
"service_name": "Argentina - DNI verification (RENAPER)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ARG",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "arg_renaper",
"service_name": "Argentina - DNI verification (RENAPER)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `arg_renaper` currently documents this normalized shape:
* `banks`
* `date_of_birth`
* `face_match_score`
* `first_name`
* `full_name`
* `highest_position`
* `identification_number`
* `last_name`
* `last_position`
* `rejected_checks`
* `sit_1_since`
* `tax_id`
* `tax_id_type`
## Pricing & SLAs
Argentina - DNI verification (RENAPER) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Argentina Database Validation overview](/api-reference/database-validation/argentina)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS - Birth Certificate
Source: https://docs.didit.me/api-reference/database-validation/australia/birth-certificate
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_birth_certificate`
* **Data domain:** Identity
* **Category:** BirthRecord
## Inputs
| Field | Required | Example |
| --------------------------- | -------: | -------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `birth_registration_number` | Yes | `SAMPLE-BIRTH-12345` |
| `birth_registration_date` | Yes | `1990-01-01` |
| `birth_registration_state` | Yes | `Sample State` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `birth_registration_number`, `birth_registration_date`, `birth_registration_state`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
In a workflow, `birth_registration_date` and `birth_registration_state` are not printed on an identity document, so no step produces them on their own. Collect them from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map each one on the Database Validation step. Until they are mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_birth_certificate`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`birth_registration_number` value required by this database service.
Example: `SAMPLE-BIRTH-12345`
`birth_registration_date` value required by this database service.
Example: `1990-01-01`
`birth_registration_state` value required by this database service.
Example: `Sample State`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_birth_certificate" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "birth_registration_number=SAMPLE-BIRTH-12345" \
-F "birth_registration_date=1990-01-01" \
-F "birth_registration_state=Sample State"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_birth_certificate",
"service_name": "Australia - DVS - Birth Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_birth_certificate",
"service_name": "Australia - DVS - Birth Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_birth_certificate",
"service_name": "Australia - DVS - Birth Certificate",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_birth_certificate` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `middle_name`
## Pricing & SLAs
Australia - DVS - Birth Certificate queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS - Centrelink Card
Source: https://docs.didit.me/api-reference/database-validation/australia/centrelink-card
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_centrelink_card`
* **Data domain:** Government
* **Category:** Government
## Inputs
| Field | Required | Example |
| ---------------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `medicare_card_number` | Yes | `1111111111` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `medicare_card_number`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_centrelink_card`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Medicare card number.
Example: `1111111111`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Australian Medicare card number (exactly 10 digits)
* `medicare_card_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `medicare_card_number` must be exactly 10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_centrelink_card" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "medicare_card_number=1111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_centrelink_card",
"service_name": "Australia - DVS - Centrelink Card",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_centrelink_card",
"service_name": "Australia - DVS - Centrelink Card",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_centrelink_card",
"service_name": "Australia - DVS - Centrelink Card",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_centrelink_card` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `middle_name`
## Pricing & SLAs
Australia - DVS - Centrelink Card queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS – Change of Name Certificate
Source: https://docs.didit.me/api-reference/database-validation/australia/change-of-name-certificate
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_change_of_name_certificate`
* **Data domain:** Identity
* **Category:** CivilRegistry
## Inputs
| Field | Required | Example |
| ----------------------------------- | -------: | ------------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `change_of_name_certificate_number` | Yes | `SAMPLE-CON-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `change_of_name_certificate_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
In a workflow, `change_of_name_certificate_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_change_of_name_certificate`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`change_of_name_certificate_number` value required by this database service.
Example: `SAMPLE-CON-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_change_of_name_certificate" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "change_of_name_certificate_number=SAMPLE-CON-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_change_of_name_certificate",
"service_name": "Australia - DVS – Change of Name Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_change_of_name_certificate",
"service_name": "Australia - DVS – Change of Name Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_change_of_name_certificate",
"service_name": "Australia - DVS – Change of Name Certificate",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_change_of_name_certificate` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Australia - DVS – Change of Name Certificate queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS – Citizenship Certificate
Source: https://docs.didit.me/api-reference/database-validation/australia/citizenship-certificate
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_citizenship_certificate`
* **Data domain:** Identity
* **Category:** Citizenship
## Inputs
| Field | Required | Example |
| -------------------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `citizenship_certificate_number` | Yes | `SAMPLE-CERT-12345` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `citizenship_certificate_number`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
In a workflow, `citizenship_certificate_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_citizenship_certificate`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`citizenship_certificate_number` value required by this database service.
Example: `SAMPLE-CERT-12345`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_citizenship_certificate" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "citizenship_certificate_number=SAMPLE-CERT-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_citizenship_certificate",
"service_name": "Australia - DVS – Citizenship Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_citizenship_certificate",
"service_name": "Australia - DVS – Citizenship Certificate",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_citizenship_certificate",
"service_name": "Australia - DVS – Citizenship Certificate",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_citizenship_certificate` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `middle_name`
## Pricing & SLAs
Australia - DVS – Citizenship Certificate queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/australia/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from the largest Credit Bureau in Australia. Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from the largest Credit Bureau in Australia. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 75%
* **Country:** Australia
* **Service ID:** `aus_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `phone` | No | `+15550101000` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `phone`, `email`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 75%
* **Price:** \$0.70 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_credit_bureau",
"service_name": "Australia Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_credit_bureau",
"service_name": "Australia Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_credit_bureau",
"service_name": "Australia Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_credit_bureau` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
Australia Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.70 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia Death Check (BDM)
Source: https://docs.didit.me/api-reference/database-validation/australia/death-check
POST /v3/database-validation/
The official Australian Birth, Deaths, Marriages Register. Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
The official Australian Birth, Deaths, Marriages Register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_death_check`
* **Data domain:** Other
* **Category:** DeathRecord
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.05 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_death_check`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_death_check" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_death_check",
"service_name": "Australia Death Check (BDM)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_death_check",
"service_name": "Australia Death Check (BDM)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_death_check",
"service_name": "Australia Death Check (BDM)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_death_check` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Australia Death Check (BDM) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.05 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DVS - Australia Driver Licence
Source: https://docs.didit.me/api-reference/database-validation/australia/driver-licence
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_australia_driver_licence`
* **Data domain:** Document
* **Category:** DriverLicence
## Inputs
| Field | Required | Example |
| ---------------------------- | -------: | ----------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `driver_license_number` | Yes | `SAMPLE-DL-12345` |
| `driver_license_card_number` | Yes | `87654321` |
| `driver_license_state` | Yes | `Sample State` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `driver_license_number`, `driver_license_card_number`, `driver_license_state`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_australia_driver_licence`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Driver licence number.
Example: `SAMPLE-DL-12345`
`driver_license_card_number` value required by this database service.
Example: `87654321`
State or region that issued the driver licence.
Example: `Sample State`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_australia_driver_licence" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "driver_license_number=SAMPLE-DL-12345" \
-F "driver_license_card_number=87654321" \
-F "driver_license_state=Sample State"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_australia_driver_licence",
"service_name": "DVS - Australia Driver Licence",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_australia_driver_licence",
"service_name": "DVS - Australia Driver Licence",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_australia_driver_licence",
"service_name": "DVS - Australia Driver Licence",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_australia_driver_licence` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
DVS - Australia Driver Licence queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia Electoral Roll
Source: https://docs.didit.me/api-reference/database-validation/australia/electoral-roll
POST /v3/database-validation/
The official Government Electoral Roll service. Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
The official Government Electoral Roll service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_electoral_roll`
* **Data domain:** Government
* **Category:** ElectoralRoll
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_electoral_roll`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_electoral_roll" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_electoral_roll",
"service_name": "Australia Electoral Roll",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_electoral_roll",
"service_name": "Australia Electoral Roll",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_electoral_roll",
"service_name": "Australia Electoral Roll",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_electoral_roll` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
Australia Electoral Roll queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS - ImmiCard
Source: https://docs.didit.me/api-reference/database-validation/australia/immicard
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_immicard`
* **Data domain:** Document
* **Category:** Visa
## Inputs
| Field | Required | Example |
| ----------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `immi_card_number` | Yes | `SAMPLE-IMMI-12345` |
| `immi_card_expiry_date` | Yes | `2030-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `immi_card_number`, `immi_card_expiry_date`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_immicard`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`immi_card_number` value required by this database service.
Example: `SAMPLE-IMMI-12345`
`immi_card_expiry_date` value required by this database service.
Example: `2030-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_immicard" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "immi_card_number=SAMPLE-IMMI-12345" \
-F "immi_card_expiry_date=2030-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_immicard",
"service_name": "Australia - DVS - ImmiCard",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_immicard",
"service_name": "Australia - DVS - ImmiCard",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_immicard",
"service_name": "Australia - DVS - ImmiCard",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_immicard` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Australia - DVS - ImmiCard queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS – Marriage Certificate
Source: https://docs.didit.me/api-reference/database-validation/australia/marriage-certificate
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_marriage_certificate`
* **Data domain:** Identity
* **Category:** CivilRegistry
## Inputs
| Field | Required | Example |
| ----------------------------- | -------: | ----------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `first_partner_name` | Yes | `Jamie` |
| `last_partner_name` | Yes | `Example` |
| `marriage_certificate_number` | Yes | `SAMPLE-MARRIAGE-12345` |
| `birth_registration_number` | Yes | `SAMPLE-BIRTH-12345` |
| `birth_registration_state` | Yes | `Sample State` |
| `birth_registration_date` | Yes | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `first_partner_name`, `last_partner_name`, `marriage_certificate_number`, `birth_registration_number`, `birth_registration_state`, `birth_registration_date`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
In a workflow, `first_partner_name`, `last_partner_name`, `birth_registration_state` and `birth_registration_date` are not printed on an identity document, so no step produces them on their own. Collect them from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map each one on the Database Validation step. Until they are mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_marriage_certificate`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
`first_partner_name` value required by this database service.
Example: `Jamie`
`last_partner_name` value required by this database service.
Example: `Example`
`marriage_certificate_number` value required by this database service.
Example: `SAMPLE-MARRIAGE-12345`
`birth_registration_number` value required by this database service.
Example: `SAMPLE-BIRTH-12345`
`birth_registration_state` value required by this database service.
Example: `Sample State`
`birth_registration_date` value required by this database service.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_marriage_certificate" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "first_partner_name=Jamie" \
-F "last_partner_name=Example" \
-F "marriage_certificate_number=SAMPLE-MARRIAGE-12345" \
-F "birth_registration_number=SAMPLE-BIRTH-12345" \
-F "birth_registration_state=Sample State" \
-F "birth_registration_date=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_marriage_certificate",
"service_name": "Australia - DVS – Marriage Certificate",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_marriage_certificate",
"service_name": "Australia - DVS – Marriage Certificate",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_marriage_certificate",
"service_name": "Australia - DVS – Marriage Certificate",
"source_data": {
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_marriage_certificate` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Australia - DVS – Marriage Certificate queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DVS - Australia Medicare Card
Source: https://docs.didit.me/api-reference/database-validation/australia/medicare-card
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_australia_medicare_card`
* **Data domain:** Health
* **Category:** Healthcare
## Inputs
| Field | Required | Example |
| ---------------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `medicare_card_number` | Yes | `1111111111` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `medicare_card_number`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_australia_medicare_card`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Medicare card number.
Example: `1111111111`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Australian Medicare card number (exactly 10 digits)
* `medicare_card_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `medicare_card_number` must be exactly 10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_australia_medicare_card" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "medicare_card_number=1111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_australia_medicare_card",
"service_name": "DVS - Australia Medicare Card",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_australia_medicare_card",
"service_name": "DVS - Australia Medicare Card",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_australia_medicare_card",
"service_name": "DVS - Australia Medicare Card",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_australia_medicare_card` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `middle_name`
## Pricing & SLAs
DVS - Australia Medicare Card queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DVS - Australia Passport
Source: https://docs.didit.me/api-reference/database-validation/australia/passport
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_australia_passport`
* **Data domain:** Document
* **Category:** Passport
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `middle_name` | No | `Demo` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `passport_number`
* **Optional inputs:** `middle_name`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_australia_passport`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Passport number.
Example: `SAMPLE-PASS-12345`
Middle name, when available.
Example: `Demo`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_australia_passport" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "passport_number=SAMPLE-PASS-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_australia_passport",
"service_name": "DVS - Australia Passport",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_australia_passport",
"service_name": "DVS - Australia Passport",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"middle_name": "Demo"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_australia_passport",
"service_name": "DVS - Australia Passport",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_australia_passport` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `middle_name`
## Pricing & SLAs
DVS - Australia Passport queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia Superannuation & Payroll
Source: https://docs.didit.me/api-reference/database-validation/australia/superannuation-payroll
POST /v3/database-validation/
Verifies input data to an authorised Superannuation and Payroll administrator and services provider. Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data to an authorised Superannuation and Payroll administrator and services provider. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 50%
* **Country:** Australia
* **Service ID:** `aus_superannuation_payroll`
* **Data domain:** Financial
* **Category:** Superannuation
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 50%
* **Price:** \$0.67 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_superannuation_payroll`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_superannuation_payroll" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_superannuation_payroll",
"service_name": "Australia Superannuation & Payroll",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_superannuation_payroll",
"service_name": "Australia Superannuation & Payroll",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_superannuation_payroll",
"service_name": "Australia Superannuation & Payroll",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_superannuation_payroll` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Australia Superannuation & Payroll queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.67 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia - DVS - Travel Document
Source: https://docs.didit.me/api-reference/database-validation/australia/travel-document
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_travel_document`
* **Data domain:** Document
* **Category:** Passport
## Inputs
| Field | Required | Example |
| ------------------------ | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `passport_issue_country` | Yes | `USA` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `passport_number`, `passport_issue_country`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_travel_document`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Passport number.
Example: `SAMPLE-PASS-12345`
ISO 3166-1 alpha-3 country that issued the passport.
Example: `USA`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* ISO 3166-1 alpha-3 country code
* `passport_issue_country` must use uppercase letters.
* `passport_issue_country` must match `[A-Za-z]{3}`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_travel_document" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "passport_number=SAMPLE-PASS-12345" \
-F "passport_issue_country=USA"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_travel_document",
"service_name": "Australia - DVS - Travel Document",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_travel_document",
"service_name": "Australia - DVS - Travel Document",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_travel_document",
"service_name": "Australia - DVS - Travel Document",
"source_data": {
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_travel_document` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Australia - DVS - Travel Document queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Australia VEVO
Source: https://docs.didit.me/api-reference/database-validation/australia/vevo
POST /v3/database-validation/
Verifies input data against the Department of Home Affairs Visa database. Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Department of Home Affairs Visa database. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_vevo`
* **Data domain:** Document
* **Category:** Visa
## Inputs
| Field | Required | Example |
| ------------------------ | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `passport_issue_country` | Yes | `USA` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `passport_number`, `passport_issue_country`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.32 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_vevo`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Passport number.
Example: `SAMPLE-PASS-12345`
ISO 3166-1 alpha-3 country that issued the passport.
Example: `USA`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* ISO 3166-1 alpha-3 country code
* `passport_issue_country` must use uppercase letters.
* `passport_issue_country` must match `[A-Za-z]{3}`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_vevo" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "passport_number=SAMPLE-PASS-12345" \
-F "passport_issue_country=USA"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_vevo",
"service_name": "Australia VEVO",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_vevo",
"service_name": "Australia VEVO",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_vevo",
"service_name": "Australia VEVO",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_vevo` currently documents this normalized shape:
* `date_of_birth`
* `identification_number`
* `name_match_score`
## Pricing & SLAs
Australia VEVO queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.32 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DVS - Australia Visa
Source: https://docs.didit.me/api-reference/database-validation/australia/visa
POST /v3/database-validation/
Verifies input data against the Document Verification Service (DVS). Authoritative real-time identity lookup for Australia. Real-time lookup, pay-per-call.
Verifies input data against the Document Verification Service (DVS). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** Australia
* **Service ID:** `aus_australia_visa`
* **Data domain:** Document
* **Category:** Visa
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `passport_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aus_australia_visa`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Passport number.
Example: `SAMPLE-PASS-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUS" \
-F "services=aus_australia_visa" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "passport_number=SAMPLE-PASS-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aus_australia_visa",
"service_name": "DVS - Australia Visa",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aus_australia_visa",
"service_name": "DVS - Australia Visa",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aus_australia_visa",
"service_name": "DVS - Australia Visa",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aus_australia_visa` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
DVS - Australia Visa queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Australia Database Validation overview](/api-reference/database-validation/australia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Austria Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/austria/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Austria. Authoritative real-time identity lookup for Austria. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Austria. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 80%
* **Country:** Austria
* **Service ID:** `aut_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 80%
* **Price:** \$1.60 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `AUT`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `aut_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=AUT" \
-F "services=aut_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "AUT",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "aut_credit_bureau",
"service_name": "Austria Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "AUT",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "aut_credit_bureau",
"service_name": "Austria Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "AUT",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "aut_credit_bureau",
"service_name": "Austria Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `aut_credit_bureau` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Austria Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.60 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Austria Database Validation overview](/api-reference/database-validation/austria)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Bolivia - CI verification
Source: https://docs.didit.me/api-reference/database-validation/bolivia/cedula
POST /v3/database-validation/
Verifies Bolivian Carnet de Identidad data against SEGIP government records. Authoritative real-time identity lookup for Bolivia. Real-time lookup, pay-per-call.
Verifies Bolivian Carnet de Identidad data against SEGIP government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Bolivia
* **Service ID:** `bol_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `document_number` | Yes | `111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`, `date_of_birth`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BOL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bol_cedula`
Document number extracted from or provided by the user.
Example: `111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Bolivian CI (6-10 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be 6-10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BOL" \
-F "services=bol_cedula" \
-F "vendor_data=user-1234" \
-F "document_number=111111" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BOL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bol_cedula",
"service_name": "Bolivia - CI verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BOL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "bol_cedula",
"service_name": "Bolivia - CI verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BOL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bol_cedula",
"service_name": "Bolivia - CI verification",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bol_cedula` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Bolivia - CI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Bolivia Database Validation overview](/api-reference/database-validation/bolivia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Brazil - CNH QR code + face match (Datavalid)
Source: https://docs.didit.me/api-reference/database-validation/brazil/cnh-facial-qrcode
POST /v3/database-validation/
SERPRO Datavalid v4 pf-facial-qrcode — decodes the QR code printed on Brazilian CNH driver licences (physical or digital, issued since May 2017), validates CPF + name + date of birth against Receita Federal, and matches the live selfie against the CNH portrait stored at Senatran. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
SERPRO Datavalid v4 pf-facial-qrcode — decodes the QR code printed on Brazilian CNH driver licences (physical or digital, issued since May 2017), validates CPF + name + date of birth against Receita Federal, and matches the live selfie against the CNH portrait stored at Senatran. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Brazil
* **Service ID:** `bra_cnh_facial_qrcode`
* **Data domain:** Biometric
* **Category:** DriverLicence
## Inputs
| Field | Required | Example |
| ------------------- | -------: | -------------------------------------------------------- |
| `tax_number` | Yes | `11111111111` |
| `selfie` | Yes | `@./selfie.jpg` |
| `cnh_qr_code_image` | Yes | `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `tax_number`, `selfie`, `cnh_qr_code_image`
* **Optional inputs:** `first_name`, `last_name`, `date_of_birth`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.50 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_cnh_facial_qrcode`
Explicit end-user consent for this service.
Example: `true`
Tax or fiscal identification number.
Example: `11111111111`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
`cnh_qr_code_image` value required by this database service.
Example: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_cnh_facial_qrcode" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "tax_number=11111111111" \
-F "selfie=@./selfie.jpg" \
-F "cnh_qr_code_image=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_cnh_facial_qrcode",
"service_name": "Brazil - CNH QR code + face match (Datavalid)",
"source_data": {
"cnh_qrcode": "sample_value",
"date_of_birth": "1990-01-01",
"face_match_probability": "Altíssima probabilidade",
"face_match_score": "0.990",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"identification_number": true
}
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bra_cnh_facial_qrcode",
"service_name": "Brazil - CNH QR code + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "bra_cnh_facial_qrcode",
"service_name": "Brazil - CNH QR code + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "bra_cnh_facial_qrcode",
"service_name": "Brazil - CNH QR code + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_cnh_facial_qrcode` currently documents this normalized shape:
* `cnh_qrcode`
* `date_of_birth`
* `face_match_probability`
* `face_match_score`
* `first_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Brazil - CNH QR code + face match (Datavalid) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.50 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Brazil - CPF status check
Source: https://docs.didit.me/api-reference/database-validation/brazil/cpf
POST /v3/database-validation/
Returns CPF status (regular, suspended, cancelled), name, social name and registration date from Receita Federal via SERPRO. The lookup is keyed by CPF and date of birth. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
Returns CPF status (regular, suspended, cancelled), name, social name and registration date from Receita Federal via SERPRO. The lookup is keyed by CPF and date of birth. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Brazil
* **Service ID:** `bra_cpf`
* **Data domain:** Identity
* **Category:** TaxRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------- |
| `tax_number` | Yes | `11111111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `tax_number`, `date_of_birth`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.20 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_cpf`
Tax or fiscal identification number.
Example: `11111111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_cpf" \
-F "vendor_data=user-1234" \
-F "tax_number=11111111111" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_cpf",
"service_name": "Brazil - CPF status check",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"lgpd_minor": "sample_value",
"minor_under_16": "sample_value",
"minor_under_18": "sample_value",
"registration_date": "1990-01-01"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "bra_cpf",
"service_name": "Brazil - CPF status check",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"lgpd_minor": "sample_value",
"minor_under_16": "sample_value",
"minor_under_18": "sample_value",
"registration_date": "1990-01-01"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bra_cpf",
"service_name": "Brazil - CPF status check",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_cpf` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
* `lgpd_minor`
* `minor_under_16`
* `minor_under_18`
* `registration_date`
## Pricing & SLAs
Brazil - CPF status check queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Brazil - CPF + face match (Datavalid)
Source: https://docs.didit.me/api-reference/database-validation/brazil/cpf-facial
POST /v3/database-validation/
SERPRO Datavalid v4 pf-facial — validates CPF (mandatory) against Receita Federal and matches the live selfie (mandatory) against the CNH portrait stored at Senatran. Optionally screens name and date of birth. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
SERPRO Datavalid v4 pf-facial — validates CPF (mandatory) against Receita Federal and matches the live selfie (mandatory) against the CNH portrait stored at Senatran. Optionally screens name and date of birth. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~50%
* **Country:** Brazil
* **Service ID:** `bra_cpf_facial`
* **Data domain:** Biometric
* **Category:** NationalIDRegistry
Roughly 50% of Brazilian adults — the face-match step relies on the CNH portrait held at Senatran, so only driver-licence holders can be matched.
## Inputs
| Field | Required | Example |
| --------------- | -------: | --------------- |
| `tax_number` | Yes | `11111111111` |
| `selfie` | Yes | `@./selfie.jpg` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `tax_number`, `selfie`
* **Optional inputs:** `first_name`, `last_name`, `date_of_birth`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~50%
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_cpf_facial`
Explicit end-user consent for this service.
Example: `true`
Tax or fiscal identification number.
Example: `11111111111`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_cpf_facial" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "tax_number=11111111111" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_cpf_facial",
"service_name": "Brazil - CPF + face match (Datavalid)",
"source_data": {
"date_of_birth": "1990-01-01",
"face_match_probability": "Altíssima probabilidade",
"face_match_score": "0.990",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"identification_number": true
}
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bra_cpf_facial",
"service_name": "Brazil - CPF + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "bra_cpf_facial",
"service_name": "Brazil - CPF + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "bra_cpf_facial",
"service_name": "Brazil - CPF + face match (Datavalid)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_cpf_facial` currently documents this normalized shape:
* `date_of_birth`
* `face_match_probability`
* `face_match_score`
* `first_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Brazil - CPF + face match (Datavalid) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## About this source
This is the official government source: [SERPRO](https://www.serpro.gov.br) Datavalid, operated by the Brazilian federal data-processing service. The CPF is checked against Receita Federal, and the selfie is matched against the portrait held for that person at Senatran — the national driver-licensing registry.
Because the portrait comes from the CNH (driver's licence) record, only people who hold a CNH have a face on file. That is what caps coverage at roughly 50% of the adult population: the data is authoritative, but it is missing for every Brazilian who has never held a driver's licence.
**Need broader coverage?** Use [BRA - Unico IDCloud (CPF + selfie)](/api-reference/database-validation/brazil/unico-idcloud) instead. It reaches about 96% of Brazilian adults through Unico's private biometric registry, and costs \$0.20 against \$0.45 here. Choose Datavalid when you specifically need the government registry as your source of truth; choose IDCloud when you need to verify everyone.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Brazil Residential
Source: https://docs.didit.me/api-reference/database-validation/brazil/residential
POST /v3/database-validation/
Verifies against a combined database of electoral registrations. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
Verifies against a combined database of electoral registrations. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 95%
* **Country:** Brazil
* **Service ID:** `bra_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `tax_number` | Yes | `11111111111` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`, `tax_number`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 95%
* **Price:** \$0.35 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Tax or fiscal identification number.
Example: `11111111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "tax_number=11111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_residential",
"service_name": "Brazil Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "bra_residential",
"service_name": "Brazil Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bra_residential",
"service_name": "Brazil Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_residential` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Brazil Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Brazil Tax Registration (CPF/CNPJ)
Source: https://docs.didit.me/api-reference/database-validation/brazil/tax-registration
POST /v3/database-validation/
Verifies against the CPF/CNPJ database. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
Verifies against the CPF/CNPJ database. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Brazil
* **Service ID:** `bra_tax_registration`
* **Data domain:** Financial
* **Category:** TaxRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `tax_number` | Yes | `11111111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `tax_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.15 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_tax_registration`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Tax or fiscal identification number.
Example: `11111111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_tax_registration" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "tax_number=11111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_tax_registration",
"service_name": "Brazil Tax Registration (CPF/CNPJ)",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "bra_tax_registration",
"service_name": "Brazil Tax Registration (CPF/CNPJ)",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "bra_tax_registration",
"service_name": "Brazil Tax Registration (CPF/CNPJ)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_tax_registration` currently documents this normalized shape:
* `date_of_birth`
* `identification_number`
* `name_match_score`
## Pricing & SLAs
Brazil Tax Registration (CPF/CNPJ) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# BRA - Unico IDCloud (CPF + selfie)
Source: https://docs.didit.me/api-reference/database-validation/brazil/unico-idcloud
POST /v3/database-validation/
Unico IDCloud biometric validation: matches the user's selfie against the face on record for their CPF and returns whether it is the same person, not the same person, or inconclusive. Authoritative real-time identity lookup for Brazil. Real-time lookup, pay-per-call.
Unico IDCloud biometric validation: matches the user's selfie against the face on record for their CPF and returns whether it is the same person, not the same person, or inconclusive. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 96%
* **Country:** Brazil
* **Service ID:** `bra_unico_idcloud`
* **Data domain:** Identity
* **Category:** BiometricRiskScore
About 96% of Brazilian CPF holders resolve to a biometric record in Unico's registry — roughly double the reach of the government face-match source, [Datavalid](/api-reference/database-validation/brazil/cpf-facial), at about 50%.
## Inputs
| Field | Required | Example |
| --------------- | -------: | --------------- |
| `tax_number` | Yes | `11111111111` |
| `selfie` | Yes | `@./selfie.jpg` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `full_name` | No | `John Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `tax_number`, `selfie`
* **Optional inputs:** `first_name`, `last_name`, `full_name`, `date_of_birth`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 96%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `BRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `bra_unico_idcloud`
Explicit end-user consent for this service.
Example: `true`
Tax or fiscal identification number.
Example: `11111111111`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Brazilian CPF (exactly 11 digits)
* `tax_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `tax_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "services=bra_unico_idcloud" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "tax_number=11111111111" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "BRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "bra_unico_idcloud",
"service_name": "BRA - Unico IDCloud (CPF + selfie)",
"source_data": {
"unico_face_match": "yes"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "BRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "bra_unico_idcloud",
"service_name": "BRA - Unico IDCloud (CPF + selfie)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`INCONCLUSIVE`** — The registry could not determine whether the person matches - the result is genuinely uncertain (they may or may not be in the registry). This is NOT a no-match and NOT a technical image problem; no field is asserted, so match\_type is null and the check is sent to review.
```json 200 OK — INCONCLUSIVE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "BRA",
"match_type": null,
"validations": [
{
"outcome_code": "INCONCLUSIVE",
"service_id": "bra_unico_idcloud",
"service_name": "BRA - Unico IDCloud (CPF + selfie)",
"source_data": {},
"validation": {}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `bra_unico_idcloud` currently documents this normalized shape:
* `unico_face_match`
## Pricing & SLAs
BRA - Unico IDCloud (CPF + selfie) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## About this source
[Unico](https://unico.io) is a Brazilian IDtech that runs the country's largest private facial-biometric registry. Its IDCloud platform holds more than a billion facial embeddings and adds roughly 35 million new faces every month, fed by the onboarding and re-authentication flows of over 800 Brazilian companies — including four of the five largest banks. That scale is what produces the coverage figure above: a CPF lookup resolves to a biometric record for about 96% of Brazilian adults.
Didit has a direct partnership with Unico, so you can query IDCloud through the standard Database Validation API — pay-per-call, with no separate Unico contract, no minimum volume, and no onboarding process.
**How this differs from the government registry.** Didit also offers [Brazil - CPF + face match (Datavalid)](/api-reference/database-validation/brazil/cpf-facial), which is the official SERPRO source and the right choice when you specifically need the government registry as your source of truth. IDCloud is the stronger choice for everything else, on two axes you can check: it reaches about 96% of adults against Datavalid's roughly 50%, and it matches against a biometric record the registry maintains itself rather than a portrait taken from a document the user hands you — so it resists both document forgery and stolen-CPF fraud. It is also less than half the price per query.
## Recommended flow for Brazil
For most Brazilian use cases you do not need document capture at all. The user gives you a CPF, and a liveness-checked selfie confirms they are the person behind it.
Your only text input is the 11-digit CPF — no document photo, and no manually typed name or date of birth.
Because no document is read in this flow, Didit has nothing to take the CPF from unless you say where it comes from. Send it in the session's `metadata` when you create the session, and map it as the `tax_number` input of the Database Validation step. In the Business Console that is the step's input mapping; through the workflows API it is the node's `database_validation_field_sources`:
```json theme={null}
{
"database_validation_countries": {"BRA": {"services": ["bra_unico_idcloud"]}},
"database_validation_field_sources": {
"tax_number": {"source": "expected_data", "key": "metadata.cpf"}
}
}
```
Then create each session with the value under that key:
```json theme={null}
{"workflow_id": "", "metadata": {"cpf": "12345678909"}}
```
Without the mapping the step still runs, but it reports `tax_number` as missing and never queries the registry — nothing is billed, and the session's `database_validations` entry carries the reason.
Add a [liveness](/core-technology/liveness/overview) step to the same workflow and pick the method that matches your risk appetite — **Passive Liveness** (no user action at all), **3D Flash**, or **3D Action & Flash** (active methods that project light patterns, the last one adding a randomized action). Set it with `face_liveness_method` (`PASSIVE`, `FLASHING`, or `ACTIVE_3D`) in the Business Console or through the workflows API. Whichever you pick, Didit renders the capture step and confirms a real, present person — this is what stops a stored photo or a screen replay from being submitted.
Inside a session flow Didit re-uses the liveness selfie automatically — you never handle the image — and reads the CPF from the mapping above. IDCloud then confirms whether that face belongs to the person the CPF belongs to. The step needs only these two inputs; if you also send `full_name` or `date_of_birth` in `metadata` and map them, they are passed to Unico as cross-checks, but they are not required.
This CPF-only flow applies to **session and workflow** integrations, where Didit performs the capture and passes the liveness selfie to this service for you. If you call `POST /v3/database-validation/` **directly**, `selfie` is a required file input and you supply the image yourself — see [Database Validation overview](/core-technology/database-validation/overview) for both modes.
**Cost:** about \$0.30 per verified user with Passive Liveness — \$0.20 for the IDCloud query plus \$0.10 for the liveness check — or about \$0.35 with an active method, where the liveness check costs \$0.15. Liveness includes a free monthly tier, so early volume costs less; see [pricing](/getting-started/pricing).
**Fallback for the roughly 4% without coverage.** When IDCloud returns `INCONCLUSIVE`, the registry could not resolve that CPF to a usable biometric record — typically because the person is not in it. Treat this as "no answer" rather than a failed check, and fall back to full [ID Verification](/core-technology/id-verification/overview): document capture plus face match against the document portrait. Everyone stays verifiable, and you pay for document verification only on the small remainder.
**Handling declines.** `BIOMETRIC_NO_MATCH` means the registry actively disagreed — the face does not belong to that CPF. Treat it as a strong negative rather than a prompt to retry: sending the user straight to document capture is exactly the path an impostor wants, since the document is the artefact they control. Genuine false negatives do happen, though — a changed appearance, an old registry photo, or a poor capture — so route these to manual review instead of either auto-approving or dead-ending the user. `BIOMETRIC_IMAGE_UNUSABLE` is different: the selfie simply could not be read, so ask for a fresh one. Decide what each result does to the session with [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings), and see [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes) for the full list.
**Returning users.** You only need to pay for a registry lookup once. After a user passes this flow, the liveness selfie is stored against their `vendor_data`, so you can re-verify them later with [Biometric Authentication](/core-technology/biometric-auth/overview) — a liveness check plus a face match against that stored face — for \$0.10, with the same choice of liveness methods. Note that this is for **returning** users specifically: a biometric-authentication session needs a stored face (or a `portrait_image` you supply) and fails at creation if the user has neither, so first-time users still go through the flow above.
## Continue reading
* [Brazil Database Validation overview](/api-reference/database-validation/brazil)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Cambodia National ID
Source: https://docs.didit.me/api-reference/database-validation/cambodia/national-id
POST /v3/database-validation/
Verifies input data against a Government agency database consisting of national identity data of citizens. Authoritative real-time identity lookup for Cambodia. Real-time lookup, pay-per-call.
Verifies input data against a Government agency database consisting of national identity data of citizens. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 80%
* **Country:** Cambodia
* **Service ID:** `khm_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | -------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `voter_id` | Yes | `SAMPLE-VOTER-12345` |
| `gender` | No | `M` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `voter_id`
* **Optional inputs:** `gender`, `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 80%
* **Price:** \$0.35 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `KHM`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `khm_national_id`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Voter identifier.
Example: `SAMPLE-VOTER-12345`
Gender value, when required by the database.
Allowed values: `M` (male), `F` (female), `X` (other or unknown).
Example: `M`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* `voter_id` must match `(?:\d{8,9}|\d{2}-\d{3}-\d{3})`.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=KHM" \
-F "services=khm_national_id" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "voter_id=SAMPLE-VOTER-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "KHM",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "khm_national_id",
"service_name": "Cambodia National ID",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"building_number": "SAMPLE-12345",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "KHM",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "khm_national_id",
"service_name": "Cambodia National ID",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"building_number": "SAMPLE-12345",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "KHM",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "khm_national_id",
"service_name": "Cambodia National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `khm_national_id` currently documents this normalized shape:
* `address`
* `address_match_score`
* `building_number`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
* `state`
* `street`
## Pricing & SLAs
Cambodia National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Cambodia Database Validation overview](/api-reference/database-validation/cambodia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Canada Credit Bureau 2 (Non-FINTRAC)
Source: https://docs.didit.me/api-reference/database-validation/canada/credit-bureau-2-non-fintrac
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Canada. This service does not provide FINTRAC required data but can provide additional fraud detection flags. An agency application is required. Authoritative real-time identity lookup for Canada. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Canada. This service does not provide FINTRAC required data but can provide additional fraud detection flags. An agency application is required. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** Canada
* **Service ID:** `can_credit_bureau_2_non_fintrac`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$1.05 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CAN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `can_credit_bureau_2_non_fintrac`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CAN" \
-F "services=can_credit_bureau_2_non_fintrac" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "can_credit_bureau_2_non_fintrac",
"service_name": "Canada Credit Bureau 2 (Non-FINTRAC)",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CAN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "can_credit_bureau_2_non_fintrac",
"service_name": "Canada Credit Bureau 2 (Non-FINTRAC)",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "can_credit_bureau_2_non_fintrac",
"service_name": "Canada Credit Bureau 2 (Non-FINTRAC)",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `can_credit_bureau_2_non_fintrac` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Canada Credit Bureau 2 (Non-FINTRAC) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.05 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Canada Database Validation overview](/api-reference/database-validation/canada)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Canada Credit Bureau (FINTRAC)
Source: https://docs.didit.me/api-reference/database-validation/canada/credit-bureau-fintrac
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Canada and responds with data sufficient to meet FINTRAC requirements. An agency application is required. This service cannot currently be included in a Sequence Package. Authoritative real-time identity lookup for Canada. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Canada and responds with data sufficient to meet FINTRAC requirements. An agency application is required. This service cannot currently be included in a Sequence Package. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** Canada
* **Service ID:** `can_credit_bureau_fintrac`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `date_of_birth`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$2.90 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CAN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `can_credit_bureau_fintrac`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CAN" \
-F "services=can_credit_bureau_fintrac" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "can_credit_bureau_fintrac",
"service_name": "Canada Credit Bureau (FINTRAC)",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CAN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "can_credit_bureau_fintrac",
"service_name": "Canada Credit Bureau (FINTRAC)",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "can_credit_bureau_fintrac",
"service_name": "Canada Credit Bureau (FINTRAC)",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `can_credit_bureau_fintrac` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Canada Credit Bureau (FINTRAC) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.90 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Canada Database Validation overview](/api-reference/database-validation/canada)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Canada Residential
Source: https://docs.didit.me/api-reference/database-validation/canada/residential
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. An agency application is required. Authoritative real-time identity lookup for Canada. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. An agency application is required. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Canada
* **Service ID:** `can_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `phone`
* **Optional inputs:** `date_of_birth`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.25 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CAN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `can_residential`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CAN" \
-F "services=can_residential" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "can_residential",
"service_name": "Canada Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CAN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "can_residential",
"service_name": "Canada Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "can_residential",
"service_name": "Canada Residential",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `can_residential` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Canada Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.25 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Canada Database Validation overview](/api-reference/database-validation/canada)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Chile - RUT verification
Source: https://docs.didit.me/api-reference/database-validation/chile/rut
POST /v3/database-validation/
Verifies Chilean RUT against the Servicio de Registro Civil records. Authoritative real-time identity lookup for Chile. Real-time lookup, pay-per-call.
Verifies Chilean RUT against the Servicio de Registro Civil records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Chile
* **Service ID:** `chl_rut`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------ |
| `personal_number` | Yes | `SAMPLE-PER-12345` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `first_name`, `last_name`, `date_of_birth`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `chl_rut`
Country-specific personal identity number.
Example: `SAMPLE-PER-12345`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Chilean RUT (e.g. 12345678-9)
* `personal_number` must be 8-12 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHL" \
-F "services=chl_rut" \
-F "vendor_data=user-1234" \
-F "personal_number=SAMPLE-PER-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "chl_rut",
"service_name": "Chile - RUT verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"gender": "M",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "chl_rut",
"service_name": "Chile - RUT verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `chl_rut` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `gender`
* `identification_number`
* `last_name`
## Pricing & SLAs
Chile - RUT verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Chile Database Validation overview](/api-reference/database-validation/chile)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# China Bank Card
Source: https://docs.didit.me/api-reference/database-validation/china/bank-card
POST /v3/database-validation/
Verifies input data against the official data service in China. Authoritative real-time identity lookup for China. Real-time lookup, pay-per-call.
Verifies input data against the official data service in China. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** China
* **Service ID:** `chn_bank_card`
* **Data domain:** Financial
* **Category:** Banking
## Inputs
| Field | Required | Example |
| ------------------ | -------: | -------------------- |
| `full_name` | Yes | `王新伟` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `11010519900101003X` |
| `bank_card_number` | Yes | `4111111111111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`, `bank_card_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** \~ 100%
* **Price:** \$0.40 per successful query
In a workflow, `bank_card_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `chn_bank_card`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `王新伟`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `11010519900101003X`
Bank card number.
Example: `4111111111111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Date of birth must match the birth date encoded in the Chinese national ID (digits 7-14 of an 18-character number, 7-12 of a legacy 15-digit one)
* `date_of_birth` must match the birth date encoded in `national_id`; the source rejects the request outright when the two disagree, so a mismatch is refused before the lookup and is not charged.
* Chinese resident identity card number (15 digits or 18 characters with checksum)
* `national_id` must match `(?:\d{15}|\d{17}[\dXx])`.
* The final Chinese Resident Identity Card check character is validated.
* `full_name` must be the original Chinese-script name as it appears in the registry or on the document. Do not send the Latin transliteration for China database checks.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHN" \
-F "services=chn_bank_card" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=王新伟" \
-F "date_of_birth=1990-01-01" \
-F "national_id=11010519900101003X" \
-F "bank_card_number=4111111111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "chn_bank_card",
"service_name": "China Bank Card",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "王新伟",
"identification_number": "11010519900101003X"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CHN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "chn_bank_card",
"service_name": "China Bank Card",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "王新伟",
"identification_number": "11010519900101003X"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "chn_bank_card",
"service_name": "China Bank Card",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `chn_bank_card` currently documents this normalized shape:
* `date_of_birth`
* `full_name`
* `identification_number`
## Pricing & SLAs
China Bank Card queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.40 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [China Database Validation overview](/api-reference/database-validation/china)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# China National ID
Source: https://docs.didit.me/api-reference/database-validation/china/national-id
POST /v3/database-validation/
Verifies input data against the official data service in China. Authoritative real-time identity lookup for China. Real-time lookup, pay-per-call.
Verifies input data against the official data service in China. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** China
* **Service ID:** `chn_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | -------------------- |
| `full_name` | Yes | `王新伟` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `11010519900101003X` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.30 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `chn_national_id`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `王新伟`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `11010519900101003X`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Date of birth must match the birth date encoded in the Chinese national ID (digits 7-14 of an 18-character number, 7-12 of a legacy 15-digit one)
* `date_of_birth` must match the birth date encoded in `national_id`; the source rejects the request outright when the two disagree, so a mismatch is refused before the lookup and is not charged.
* `national_id` must match `(\d{15}|\d{17}[\dXx])`.
* The final Chinese Resident Identity Card check character is validated.
* `full_name` must be the original Chinese-script name as it appears in the registry or on the document. Do not send the Latin transliteration for China database checks.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHN" \
-F "services=chn_national_id" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=王新伟" \
-F "date_of_birth=1990-01-01" \
-F "national_id=11010519900101003X"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "chn_national_id",
"service_name": "China National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "11010519900101003X",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CHN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "chn_national_id",
"service_name": "China National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "11010519900101003X",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "chn_national_id",
"service_name": "China National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `chn_national_id` currently documents this normalized shape:
* `date_of_birth`
* `identification_number`
## Pricing & SLAs
China National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.30 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [China Database Validation overview](/api-reference/database-validation/china)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# China Passport Verification
Source: https://docs.didit.me/api-reference/database-validation/china/passport-verification
POST /v3/database-validation/
Verifies input data against the official data service in China. Authoritative real-time identity lookup for China. Real-time lookup, pay-per-call.
Verifies input data against the official data service in China. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** China
* **Service ID:** `chn_passport_verification`
* **Data domain:** Document
* **Category:** Passport
## Inputs
| Field | Required | Example |
| ----------------- | -------: | -------------------- |
| `full_name` | Yes | `王新伟` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `11010519900101003X` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`, `passport_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$1.00 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `chn_passport_verification`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `王新伟`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `11010519900101003X`
Passport number.
Example: `SAMPLE-PASS-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Date of birth must match the birth date encoded in the Chinese national ID (digits 7-14 of an 18-character number, 7-12 of a legacy 15-digit one)
* `date_of_birth` must match the birth date encoded in `national_id`; the source rejects the request outright when the two disagree, so a mismatch is refused before the lookup and is not charged.
* `national_id` must match `(?:\d{15}|\d{17}[\dXx])`.
* The final Chinese Resident Identity Card check character is validated.
* `full_name` must be the original Chinese-script name as it appears in the registry or on the document. Do not send the Latin transliteration for China database checks.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHN" \
-F "services=chn_passport_verification" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=王新伟" \
-F "date_of_birth=1990-01-01" \
-F "national_id=11010519900101003X" \
-F "passport_number=SAMPLE-PASS-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "chn_passport_verification",
"service_name": "China Passport Verification",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "11010519900101003X",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CHN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "chn_passport_verification",
"service_name": "China Passport Verification",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "11010519900101003X",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "chn_passport_verification",
"service_name": "China Passport Verification",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `chn_passport_verification` currently documents this normalized shape:
* `date_of_birth`
* `identification_number`
## Pricing & SLAs
China Passport Verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [China Database Validation overview](/api-reference/database-validation/china)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# China Phone
Source: https://docs.didit.me/api-reference/database-validation/china/phone
POST /v3/database-validation/
Verifies input data against the official data service in China. Authoritative real-time identity lookup for China. Real-time lookup, pay-per-call.
Verifies input data against the official data service in China. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** China
* **Service ID:** `chn_phone`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------- | -------: | -------------------- |
| `full_name` | Yes | `王新伟` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `11010519900101003X` |
| `phone` | Yes | `+15550101000` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`, `phone`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.30 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `chn_phone`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `王新伟`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `11010519900101003X`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Date of birth must match the birth date encoded in the Chinese national ID (digits 7-14 of an 18-character number, 7-12 of a legacy 15-digit one)
* `date_of_birth` must match the birth date encoded in `national_id`; the source rejects the request outright when the two disagree, so a mismatch is refused before the lookup and is not charged.
* Chinese resident identity card number (15 digits or 18 characters with checksum)
* `national_id` must match `(?:\d{15}|\d{17}[\dXx])`.
* The final Chinese Resident Identity Card check character is validated.
* `full_name` must be the original Chinese-script name as it appears in the registry or on the document. Do not send the Latin transliteration for China database checks.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHN" \
-F "services=chn_phone" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=王新伟" \
-F "date_of_birth=1990-01-01" \
-F "national_id=11010519900101003X" \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "chn_phone",
"service_name": "China Phone",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "王新伟",
"identification_number": "11010519900101003X"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CHN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "chn_phone",
"service_name": "China Phone",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "王新伟",
"identification_number": "11010519900101003X"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "chn_phone",
"service_name": "China Phone",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `chn_phone` currently documents this normalized shape:
* `date_of_birth`
* `full_name`
* `identification_number`
## Pricing & SLAs
China Phone queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.30 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [China Database Validation overview](/api-reference/database-validation/china)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Colombia - Cédula verification
Source: https://docs.didit.me/api-reference/database-validation/colombia/cedula
POST /v3/database-validation/
Verifies Colombian citizen identity data against Registraduría General de la Nación / ANI government records. Authoritative real-time identity lookup for Colombia. Real-time lookup, pay-per-call.
Verifies Colombian citizen identity data against Registraduría General de la Nación / ANI government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Colombia
* **Service ID:** `col_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `personal_number` | Yes | `111111` |
| `date_of_issue` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`, `date_of_issue`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `COL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `col_cedula`
Country-specific personal identity number.
Example: `111111`
`date_of_issue` value required by this database service.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Colombian Cédula (6-10 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be 6-10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=COL" \
-F "services=col_cedula" \
-F "vendor_data=user-1234" \
-F "personal_number=111111" \
-F "date_of_issue=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "COL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "col_cedula",
"service_name": "Colombia - Cédula verification",
"source_data": {
"cedula_status": "sample_value",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"verifications": {
"identification_number": true
}
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "COL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "col_cedula",
"service_name": "Colombia - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `col_cedula` currently documents this normalized shape:
* `cedula_status`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Colombia - Cédula verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Colombia Database Validation overview](/api-reference/database-validation/colombia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Colombia - Migración Colombia foreigner status
Source: https://docs.didit.me/api-reference/database-validation/colombia/migracion
POST /v3/database-validation/
Verifies a foreigner's migratory status and identity against Migración Colombia government records (cédula de extranjería). Authoritative real-time identity lookup for Colombia. Real-time lookup, pay-per-call.
Verifies a foreigner's migratory status and identity against Migración Colombia government records (cédula de extranjería). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Colombia
* **Service ID:** `col_migracion`
* **Data domain:** Identity
* **Category:** ResidencePermit
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `personal_number` | Yes | `111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `nationality` | Yes | `MEX` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`, `date_of_birth`, `nationality`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$1.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `COL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `col_migracion`
Country-specific personal identity number.
Example: `111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`nationality` value required by this database service.
Example: `MEX`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Colombian Cédula (6-10 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be 6-10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=COL" \
-F "services=col_migracion" \
-F "vendor_data=user-1234" \
-F "personal_number=111111" \
-F "date_of_birth=1990-01-01" \
-F "nationality=MEX"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "COL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "col_migracion",
"service_name": "Colombia - Migración Colombia foreigner status",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "COL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "col_migracion",
"service_name": "Colombia - Migración Colombia foreigner status",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "COL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "col_migracion",
"service_name": "Colombia - Migración Colombia foreigner status",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `col_migracion` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Colombia - Migración Colombia foreigner status queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Colombia Database Validation overview](/api-reference/database-validation/colombia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Costa Rica - Cédula verification
Source: https://docs.didit.me/api-reference/database-validation/costa-rica/cedula
POST /v3/database-validation/
Verifies Costa Rican national identity card data against Tribunal Supremo de Elecciones government records. Authoritative real-time identity lookup for Costa Rica. Real-time lookup, pay-per-call.
Verifies Costa Rican national identity card data against Tribunal Supremo de Elecciones government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Costa Rica
* **Service ID:** `cri_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ----------- |
| `personal_number` | Yes | `111111111` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CRI`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `cri_cedula`
Country-specific personal identity number.
Example: `111111111`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Costa Rican Cédula (9-12 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be 9-12 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CRI" \
-F "services=cri_cedula" \
-F "vendor_data=user-1234" \
-F "personal_number=111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CRI",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "cri_cedula",
"service_name": "Costa Rica - Cédula verification",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CRI",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "cri_cedula",
"service_name": "Costa Rica - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`DOCUMENT_NOT_FOUND`** — The submitted document number does not correspond to any record in the registry.
```json 200 OK — DOCUMENT_NOT_FOUND theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CRI",
"match_type": "no_match",
"validations": [
{
"outcome_code": "DOCUMENT_NOT_FOUND",
"service_id": "cri_cedula",
"service_name": "Costa Rica - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `cri_cedula` currently documents this normalized shape:
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Costa Rica - Cédula verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Costa Rica Database Validation overview](/api-reference/database-validation/costa-rica)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Denmark Consumer 2
Source: https://docs.didit.me/api-reference/database-validation/denmark/consumer-2
POST /v3/database-validation/
Input is verified against a source comprising multiple datasets from consumer, telephone and utility records. Authoritative real-time identity lookup for Denmark. Real-time lookup, pay-per-call.
Input is verified against a source comprising multiple datasets from consumer, telephone and utility records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 50%
* **Country:** Denmark
* **Service ID:** `dnk_consumer_2`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `phone` | No | `+15550101000` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `phone`, `email`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 50%
* **Price:** \$0.97 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `DNK`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `dnk_consumer_2`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=DNK" \
-F "services=dnk_consumer_2" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "DNK",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "dnk_consumer_2",
"service_name": "Denmark Consumer 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "DNK",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "dnk_consumer_2",
"service_name": "Denmark Consumer 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "DNK",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "dnk_consumer_2",
"service_name": "Denmark Consumer 2",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `dnk_consumer_2` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Denmark Consumer 2 queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.97 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Denmark Database Validation overview](/api-reference/database-validation/denmark)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Denmark National ID
Source: https://docs.didit.me/api-reference/database-validation/denmark/national-id
POST /v3/database-validation/
Verifies input data against the Civil Registration service. Authoritative real-time identity lookup for Denmark. Real-time lookup, pay-per-call.
Verifies input data against the Civil Registration service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Denmark
* **Service ID:** `dnk_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$1.39 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `DNK`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `dnk_national_id`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* `national_id` must match `\d{6}-?\d{4}`.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=DNK" \
-F "services=dnk_national_id" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "DNK",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "dnk_national_id",
"service_name": "Denmark National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "DNK",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "dnk_national_id",
"service_name": "Denmark National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "DNK",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "dnk_national_id",
"service_name": "Denmark National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `dnk_national_id` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Denmark National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.39 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Denmark Database Validation overview](/api-reference/database-validation/denmark)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Dominican Republic - Cédula verification
Source: https://docs.didit.me/api-reference/database-validation/dominican-republic/cedula
POST /v3/database-validation/
Verifies Dominican Cédula directly against the Junta Central Electoral civil registry. Authoritative real-time identity lookup for Dominican Republic. Real-time lookup, pay-per-call.
Verifies Dominican Cédula directly against the Junta Central Electoral civil registry. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Dominican Republic
* **Service ID:** `dom_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------- |
| `personal_number` | Yes | `11111111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.05 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `DOM`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `dom_cedula`
Country-specific personal identity number.
Example: `11111111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Dominican Cédula (exactly 11 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=DOM" \
-F "services=dom_cedula" \
-F "vendor_data=user-1234" \
-F "personal_number=11111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "DOM",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "dom_cedula",
"service_name": "Dominican Republic - Cédula verification",
"source_data": {
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "DOM",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "dom_cedula",
"service_name": "Dominican Republic - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `dom_cedula` currently documents this normalized shape:
* `identification_number`
## Pricing & SLAs
Dominican Republic - Cédula verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.05 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Dominican Republic Database Validation overview](/api-reference/database-validation/dominican-republic)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Ecuador - Cédula verification
Source: https://docs.didit.me/api-reference/database-validation/ecuador/cedula
POST /v3/database-validation/
Verifies Ecuadorian Cédula against the Registro Civil records. Authoritative real-time identity lookup for Ecuador. Real-time lookup, pay-per-call.
Verifies Ecuadorian Cédula against the Registro Civil records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Ecuador
* **Service ID:** `ecu_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `personal_number` | Yes | `1111111111` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ECU`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ecu_cedula`
Country-specific personal identity number.
Example: `1111111111`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Ecuadorian Cédula (exactly 10 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be exactly 10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ECU" \
-F "services=ecu_cedula" \
-F "vendor_data=user-1234" \
-F "personal_number=1111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ECU",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ecu_cedula",
"service_name": "Ecuador - Cédula verification",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ECU",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ecu_cedula",
"service_name": "Ecuador - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ecu_cedula` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Ecuador - Cédula verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Ecuador Database Validation overview](/api-reference/database-validation/ecuador)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# El Salvador - DUI verification
Source: https://docs.didit.me/api-reference/database-validation/el-salvador/dui
POST /v3/database-validation/
Verifies Salvadoran DUI data against RNPN government records. Authoritative real-time identity lookup for El Salvador. Real-time lookup, pay-per-call.
Verifies Salvadoran DUI data against RNPN government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** El Salvador
* **Service ID:** `slv_dui`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `document_number` | Yes | `111111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`, `date_of_birth`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `SLV`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `slv_dui`
Document number extracted from or provided by the user.
Example: `111111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Salvadoran DUI (exactly 9 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be exactly 9 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=SLV" \
-F "services=slv_dui" \
-F "vendor_data=user-1234" \
-F "document_number=111111111" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "SLV",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "slv_dui",
"service_name": "El Salvador - DUI verification",
"source_data": {
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "SLV",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "slv_dui",
"service_name": "El Salvador - DUI verification",
"source_data": {
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "SLV",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "slv_dui",
"service_name": "El Salvador - DUI verification",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `slv_dui` currently documents this normalized shape:
* `full_name`
* `identification_number`
## Pricing & SLAs
El Salvador - DUI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [El Salvador Database Validation overview](/api-reference/database-validation/el-salvador)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Finland Consumer 2
Source: https://docs.didit.me/api-reference/database-validation/finland/consumer-2
POST /v3/database-validation/
Verifies Finland Consumer 2. Authoritative real-time identity lookup for Finland. Real-time lookup, pay-per-call.
Verifies Finland Consumer 2. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Finland
* **Service ID:** `fin_consumer_2`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `phone` | No | `+15550101000` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `phone`, `email`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.97 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `FIN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `fin_consumer_2`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=FIN" \
-F "services=fin_consumer_2" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "FIN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "fin_consumer_2",
"service_name": "Finland Consumer 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "FIN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "fin_consumer_2",
"service_name": "Finland Consumer 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "FIN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "fin_consumer_2",
"service_name": "Finland Consumer 2",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `fin_consumer_2` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Finland Consumer 2 queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.97 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Finland Database Validation overview](/api-reference/database-validation/finland)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Finland National ID
Source: https://docs.didit.me/api-reference/database-validation/finland/national-id
POST /v3/database-validation/
Verifies input data against the official issuing agency of the Finnish Identity Card. Authoritative real-time identity lookup for Finland. Real-time lookup, pay-per-call.
Verifies input data against the official issuing agency of the Finnish Identity Card. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Finland
* **Service ID:** `fin_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$2.10 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `FIN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `fin_national_id`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* `national_id` must match `(?:\d{6}[-+A]\d{3}[0-9A-Z]|\d{6}\d{3}[0-9A-Z])`.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=FIN" \
-F "services=fin_national_id" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "FIN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "fin_national_id",
"service_name": "Finland National ID",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "FIN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "fin_national_id",
"service_name": "Finland National ID",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "FIN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "fin_national_id",
"service_name": "Finland National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `fin_national_id` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Finland National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.10 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Finland Database Validation overview](/api-reference/database-validation/finland)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# France Phone
Source: https://docs.didit.me/api-reference/database-validation/france/phone
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. Authoritative real-time identity lookup for France. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** France
* **Service ID:** `fra_phone`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `phone`
* **Optional inputs:** `date_of_birth`, `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.67 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `FRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `fra_phone`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=FRA" \
-F "services=fra_phone" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "FRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "fra_phone",
"service_name": "France Phone",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"full_name": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "FRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "fra_phone",
"service_name": "France Phone",
"source_data": {
"full_name": "NO_MATCH"
},
"validation": {
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `fra_phone` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
France Phone queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.67 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [France Database Validation overview](/api-reference/database-validation/france)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# France Residential
Source: https://docs.didit.me/api-reference/database-validation/france/residential
POST /v3/database-validation/
Verifies data against government agency sourced census data. Authoritative real-time identity lookup for France. Real-time lookup, pay-per-call.
Verifies data against government agency sourced census data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** France
* **Service ID:** `fra_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$0.89 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `FRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `fra_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=FRA" \
-F "services=fra_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "FRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "fra_residential",
"service_name": "France Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "FRA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "fra_residential",
"service_name": "France Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "FRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "fra_residential",
"service_name": "France Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `fra_residential` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `street`
## Pricing & SLAs
France Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.89 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [France Database Validation overview](/api-reference/database-validation/france)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# France Utility
Source: https://docs.didit.me/api-reference/database-validation/france/utility
POST /v3/database-validation/
Aggregated service of public records, background records, and public professional profiles. Authoritative real-time identity lookup for France. Real-time lookup, pay-per-call.
Aggregated service of public records, background records, and public professional profiles. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** France
* **Service ID:** `fra_utility`
* **Data domain:** Address
* **Category:** Utility
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.65 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `FRA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `fra_utility`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=FRA" \
-F "services=fra_utility" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "FRA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "fra_utility",
"service_name": "France Utility",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "FRA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "fra_utility",
"service_name": "France Utility",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "FRA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "fra_utility",
"service_name": "France Utility",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `fra_utility` currently documents this normalized shape:
* `address_match_score`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
## Pricing & SLAs
France Utility queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.65 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [France Database Validation overview](/api-reference/database-validation/france)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Germany Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/germany/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Germany. Authoritative real-time identity lookup for Germany. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Germany. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 80%
* **Country:** Germany
* **Service ID:** `deu_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 80%
* **Price:** \$0.84 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `DEU`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `deu_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=DEU" \
-F "services=deu_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "DEU",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "deu_credit_bureau",
"service_name": "Germany Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "DEU",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "deu_credit_bureau",
"service_name": "Germany Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "DEU",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "deu_credit_bureau",
"service_name": "Germany Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `deu_credit_bureau` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `street`
## Pricing & SLAs
Germany Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.84 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Germany Database Validation overview](/api-reference/database-validation/germany)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Germany Phone 2
Source: https://docs.didit.me/api-reference/database-validation/germany/phone-2
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. Authoritative real-time identity lookup for Germany. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** Germany
* **Service ID:** `deu_phone_2`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `phone`
* **Optional inputs:** `date_of_birth`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.59 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `DEU`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `deu_phone_2`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=DEU" \
-F "services=deu_phone_2" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "DEU",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "deu_phone_2",
"service_name": "Germany Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "DEU",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "deu_phone_2",
"service_name": "Germany Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "DEU",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "deu_phone_2",
"service_name": "Germany Phone 2",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `deu_phone_2` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Germany Phone 2 queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.59 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Germany Database Validation overview](/api-reference/database-validation/germany)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Global - Identity Enrichment
Source: https://docs.didit.me/api-reference/database-validation/global/identity-enrichment
POST /v3/database-validation/
Resolves persons (names, DOB, addresses, phones, emails, national IDs) and a name-match score from an email and/or phone number via a global risk-insights network. Charged per request. Third-party identity-network lookup for Global. Real-time lookup, pay-per-call.
Resolves persons (names, DOB, addresses, phones, emails, national IDs) and a name-match score from an email and/or phone number via a global risk-insights network. Charged per request. Didit exposes this service through `POST /v3/database-validation/` so you can screen the submitted data against the connected identity network and receive normalized match results. This is a third-party enrichment network, not an authoritative government registry — do not present its results as primary-source verification.
## Coverage
* **Coverage:** 50+ markets
* **Country:** Global
* **Service ID:** `glb_identity_enrichment`
* **Data domain:** Identity
* **Category:** IdentityEnrichment
## Inputs
| Field | Required | Example |
| ---------------------- | -------: | ---------------------- |
| `email` | No | `john.doe@example.com` |
| `phone` | No | `+15550101000` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `full_name` | No | `John Doe` |
| `country_of_residence` | No | `US` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** —
* **Optional inputs:** `email`, `phone`, `first_name`, `last_name`, `full_name`, `country_of_residence`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Standalone API only
* **Coverage:** 50+ markets
* **Price:** \$0.20 per successful query
This service needs an input no workflow step can produce (a biometric capture, a signed artifact, a live one-time code), so it is available only on the standalone API, where you supply the values directly.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GLB`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `glb_identity_enrichment`
Explicit end-user consent for this service.
Example: `true`
Email address.
Example: `john.doe@example.com`
Phone number in international format.
Example: `+15550101000`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Full legal name to validate.
Example: `John Doe`
Optional ISO 3166-1 alpha-2 country of residence. Only used to focus the identity lookup.
Example: `US`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Provide at least one of `email` or `phone` - they are the lookup keys. A request with neither cannot perform an enrichment lookup and is rejected before source lookup (not charged).
* Add `full_name` (or `first_name` + `last_name`) whenever you have it - the name is what the returned match score is computed against.
* `country_of_residence` is an ISO 3166-1 alpha-2 code used to focus the lookup.
* Every completed lookup is charged, including one that resolves no person at all (`persons_found: 0`). Only a request rejected before the lookup - neither `email` nor `phone` supplied - is free.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GLB" \
-F "services=glb_identity_enrichment" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "email=john.doe@example.com" \
-F "phone=+15550101000" \
-F "full_name=John Doe" \
-F "country_of_residence=US"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GLB",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "glb_identity_enrichment",
"service_name": "Global - Identity Enrichment",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GLB",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "glb_identity_enrichment",
"service_name": "Global - Identity Enrichment",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GLB",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "glb_identity_enrichment",
"service_name": "Global - Identity Enrichment",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`INCONCLUSIVE`** — The registry could not determine whether the person matches - the result is genuinely uncertain (they may or may not be in the registry). This is NOT a no-match and NOT a technical image problem; no field is asserted, so match\_type is null and the check is sent to review.
```json 200 OK — INCONCLUSIVE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GLB",
"match_type": null,
"validations": [
{
"outcome_code": "INCONCLUSIVE",
"service_id": "glb_identity_enrichment",
"service_name": "Global - Identity Enrichment",
"source_data": {},
"validation": {}
}
]
}
```
**`DOCUMENT_NOT_FOUND`** — The submitted document number does not correspond to any record in the registry.
```json 200 OK — DOCUMENT_NOT_FOUND theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GLB",
"match_type": "no_match",
"validations": [
{
"outcome_code": "DOCUMENT_NOT_FOUND",
"service_id": "glb_identity_enrichment",
"service_name": "Global - Identity Enrichment",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `glb_identity_enrichment` currently documents this normalized shape:
* `first_name`
* `full_name`
* `last_name`
## Pricing & SLAs
Global - Identity Enrichment queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Global Database Validation overview](/getting-started/database-validation-pricing)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Global - Core Intelligence Service
Source: https://docs.didit.me/api-reference/database-validation/global/intelligence-service
POST /v3/database-validation/
Performs a series of checks across multiple data partners and returns risks associated with each data point. Authoritative real-time identity lookup for Global. Real-time lookup, pay-per-call.
Performs a series of checks across multiple data partners and returns risks associated with each data point. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Global
* **Service ID:** `glb_intelligence_service`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| ------------- | -------: | -------------- |
| `phone` | Yes | `+15550101000` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `phone`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.41 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GLB`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `glb_intelligence_service`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GLB" \
-F "services=glb_intelligence_service" \
-F "vendor_data=user-1234" \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GLB",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "glb_intelligence_service",
"service_name": "Global - Core Intelligence Service",
"source_data": {},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GLB",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "glb_intelligence_service",
"service_name": "Global - Core Intelligence Service",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `glb_intelligence_service` currently documents this normalized shape:
* Varies by registry response.
## Pricing & SLAs
Global - Core Intelligence Service queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.41 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Global Database Validation overview](/getting-started/database-validation-pricing)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Global - Sim Swap
Source: https://docs.didit.me/api-reference/database-validation/global/swap
POST /v3/database-validation/
Returns data points associated with any Sim Swap activity on the input phone number. Authoritative real-time identity lookup for Global. Real-time lookup, pay-per-call.
Returns data points associated with any Sim Swap activity on the input phone number. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Global
* **Service ID:** `glb_swap`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| ------------- | -------: | -------------- |
| `phone` | Yes | `+15550101000` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `phone`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.18 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GLB`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `glb_swap`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GLB" \
-F "services=glb_swap" \
-F "vendor_data=user-1234" \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GLB",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "glb_swap",
"service_name": "Global - Sim Swap",
"source_data": {},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GLB",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "glb_swap",
"service_name": "Global - Sim Swap",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `glb_swap` currently documents this normalized shape:
* Varies by registry response.
## Pricing & SLAs
Global - Sim Swap queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.18 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Global Database Validation overview](/getting-started/database-validation-pricing)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Guatemala - DPI verification
Source: https://docs.didit.me/api-reference/database-validation/guatemala/dpi
POST /v3/database-validation/
Verifies Guatemalan unique identification code data against SAT government records. Authoritative real-time identity lookup for Guatemala. Real-time lookup, pay-per-call.
Verifies Guatemalan unique identification code data against SAT government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Guatemala
* **Service ID:** `gtm_dpi`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | --------------- |
| `document_number` | Yes | `1111111111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`, `date_of_birth`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GTM`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `gtm_dpi`
Document number extracted from or provided by the user.
Example: `1111111111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Guatemalan DPI (exactly 13 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be exactly 13 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GTM" \
-F "services=gtm_dpi" \
-F "vendor_data=user-1234" \
-F "document_number=1111111111111" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GTM",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "gtm_dpi",
"service_name": "Guatemala - DPI verification",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GTM",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "gtm_dpi",
"service_name": "Guatemala - DPI verification",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GTM",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "gtm_dpi",
"service_name": "Guatemala - DPI verification",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `gtm_dpi` currently documents this normalized shape:
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Guatemala - DPI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Guatemala Database Validation overview](/api-reference/database-validation/guatemala)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Honduras - DNI verification
Source: https://docs.didit.me/api-reference/database-validation/honduras/dni
POST /v3/database-validation/
Verifies Honduran DNI data against CNE electoral registry records. Authoritative real-time identity lookup for Honduras. Real-time lookup, pay-per-call.
Verifies Honduran DNI data against CNE electoral registry records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Honduras
* **Service ID:** `hnd_dni`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | --------------- |
| `document_number` | Yes | `1111111111111` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `HND`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `hnd_dni`
Document number extracted from or provided by the user.
Example: `1111111111111`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Honduran DNI (exactly 13 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be exactly 13 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=HND" \
-F "services=hnd_dni" \
-F "vendor_data=user-1234" \
-F "document_number=1111111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "HND",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "hnd_dni",
"service_name": "Honduras - DNI verification",
"source_data": {
"document_type": "sample_value",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "HND",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "hnd_dni",
"service_name": "Honduras - DNI verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `hnd_dni` currently documents this normalized shape:
* `document_type`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Honduras - DNI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Honduras Database Validation overview](/api-reference/database-validation/honduras)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# India Aadhaar (UIDAI)
Source: https://docs.didit.me/api-reference/database-validation/india/aadhaar
POST /v3/database-validation/
Verifies the input Aadhaar number against UIDAI identity records. Authoritative real-time identity lookup for India. Real-time lookup, pay-per-call.
Verifies the input Aadhaar number against UIDAI identity records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** India
* **Service ID:** `ind_aadhaar`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | -------------- |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `personal_number` | Yes | `111111111111` |
| `pan` | Yes | `ABCDE1234F` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `personal_number`, `pan`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.25 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `IND`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ind_aadhaar`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Country-specific personal identity number.
Example: `111111111111`
Permanent Account Number.
Example: `ABCDE1234F`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `pan` must use uppercase letters.
* `pan` must match `[A-Z]{5}\d{4}[A-Z]`.
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be exactly 12 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=IND" \
-F "services=ind_aadhaar" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "personal_number=111111111111" \
-F "pan=ABCDE1234F"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "IND",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ind_aadhaar",
"service_name": "India Aadhaar (UIDAI)",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "IND",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ind_aadhaar",
"service_name": "India Aadhaar (UIDAI)",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "IND",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ind_aadhaar",
"service_name": "India Aadhaar (UIDAI)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ind_aadhaar` currently documents this normalized shape:
* `date_of_birth`
* `full_name`
* `identification_number`
## Pricing & SLAs
India Aadhaar (UIDAI) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.25 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [India Database Validation overview](/api-reference/database-validation/india)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# India Drivers Licence (RTO)
Source: https://docs.didit.me/api-reference/database-validation/india/drivers-licence
POST /v3/database-validation/
Verifies input data against the databases of the Road Transport Offices of the States of India. Authoritative real-time identity lookup for India. Real-time lookup, pay-per-call.
Verifies input data against the databases of the Road Transport Offices of the States of India. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** India
* **Service ID:** `ind_drivers_licence`
* **Data domain:** Document
* **Category:** DriverLicence
## Inputs
| Field | Required | Example |
| ----------------------- | -------: | ------------------- |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `driver_license_number` | Yes | `SAMPLE-DL-12345` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `driver_license_number`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.15 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `IND`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ind_drivers_licence`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Driver licence number.
Example: `SAMPLE-DL-12345`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=IND" \
-F "services=ind_drivers_licence" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "driver_license_number=SAMPLE-DL-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "IND",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ind_drivers_licence",
"service_name": "India Drivers Licence (RTO)",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "IND",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ind_drivers_licence",
"service_name": "India Drivers Licence (RTO)",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "IND",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ind_drivers_licence",
"service_name": "India Drivers Licence (RTO)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ind_drivers_licence` currently documents this normalized shape:
* `address`
* `address_match_score`
* `date_of_birth`
* `identification_number`
## Pricing & SLAs
India Drivers Licence (RTO) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [India Database Validation overview](/api-reference/database-validation/india)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# India EPIC (Voter's Registration) (ECI)
Source: https://docs.didit.me/api-reference/database-validation/india/epic-voter-s-registration
POST /v3/database-validation/
Verifies input data against the Election Commission of India database. Authoritative real-time identity lookup for India. Real-time lookup, pay-per-call.
Verifies input data against the Election Commission of India database. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** India
* **Service ID:** `ind_epic_voter_s_registration`
* **Data domain:** Government
* **Category:** ElectoralRoll
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `epic_card` | Yes | `ABC1234567` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `epic_card`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** > 75%
* **Price:** \$0.15 per successful query
In a workflow, `epic_card` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `IND`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ind_epic_voter_s_registration`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
EPIC voter card number.
Example: `ABC1234567`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `epic_card` must match `[A-Za-z]{3}\d{7,14}`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=IND" \
-F "services=ind_epic_voter_s_registration" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "epic_card=ABC1234567"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "IND",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ind_epic_voter_s_registration",
"service_name": "India EPIC (Voter's Registration) (ECI)",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "IND",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ind_epic_voter_s_registration",
"service_name": "India EPIC (Voter's Registration) (ECI)",
"source_data": {
"date_of_birth": "1990-01-01",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "IND",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ind_epic_voter_s_registration",
"service_name": "India EPIC (Voter's Registration) (ECI)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ind_epic_voter_s_registration` currently documents this normalized shape:
* `date_of_birth`
* `full_name`
* `identification_number`
## Pricing & SLAs
India EPIC (Voter's Registration) (ECI) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [India Database Validation overview](/api-reference/database-validation/india)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# India PAN (Permanent Account Number) (ITD)
Source: https://docs.didit.me/api-reference/database-validation/india/pan-permanent-account-number
POST /v3/database-validation/
Verifies input data against the database of the Income Tax Department of India. Authoritative real-time identity lookup for India. Real-time lookup, pay-per-call.
Verifies input data against the database of the Income Tax Department of India. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** India
* **Service ID:** `ind_pan_permanent_account_number`
* **Data domain:** Financial
* **Category:** TaxRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `pan` | Yes | `ABCDE1234F` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `pan`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.25 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `IND`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ind_pan_permanent_account_number`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Permanent Account Number.
Example: `ABCDE1234F`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `pan` must use uppercase letters.
* `pan` must match `[A-Z]{5}\d{4}[A-Z]`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=IND" \
-F "services=ind_pan_permanent_account_number" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "pan=ABCDE1234F"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "IND",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ind_pan_permanent_account_number",
"service_name": "India PAN (Permanent Account Number) (ITD)",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "IND",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ind_pan_permanent_account_number",
"service_name": "India PAN (Permanent Account Number) (ITD)",
"source_data": {
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "IND",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ind_pan_permanent_account_number",
"service_name": "India PAN (Permanent Account Number) (ITD)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ind_pan_permanent_account_number` currently documents this normalized shape:
* `date_of_birth`
* `identification_number`
## Pricing & SLAs
India PAN (Permanent Account Number) (ITD) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.25 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [India Database Validation overview](/api-reference/database-validation/india)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Indonesia Residential Identity Card
Source: https://docs.didit.me/api-reference/database-validation/indonesia/residential-identity-card
POST /v3/database-validation/
Verifies input data against a Government agency. Authoritative real-time identity lookup for Indonesia. Real-time lookup, pay-per-call.
Verifies input data against a Government agency. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Indonesia
* **Service ID:** `idn_residential_identity_card`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `1111111111111111` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `gender` | No | `M` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `gender`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.35 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `IDN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `idn_residential_identity_card`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `1111111111111111`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Gender value, when required by the database.
Allowed values: `M` (male), `F` (female), `X` (other or unknown).
Example: `M`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `national_id` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `national_id` must be exactly 16 characters long.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=IDN" \
-F "services=idn_residential_identity_card" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=1111111111111111" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "IDN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "idn_residential_identity_card",
"service_name": "Indonesia Residential Identity Card",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "IDN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "idn_residential_identity_card",
"service_name": "Indonesia Residential Identity Card",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "IDN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "idn_residential_identity_card",
"service_name": "Indonesia Residential Identity Card",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `idn_residential_identity_card` currently documents this normalized shape:
* `address_match_score`
* `date_of_birth`
* `identification_number`
## Pricing & SLAs
Indonesia Residential Identity Card queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Indonesia Database Validation overview](/api-reference/database-validation/indonesia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Italy Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/italy/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Italy. Authoritative real-time identity lookup for Italy. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Italy. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 80%
* **Country:** Italy
* **Service ID:** `ita_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `tax_number` | Yes | `SAMPLE-TAX-12345` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `tax_number`
* **Optional inputs:** `date_of_birth`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 80%
* **Price:** \$1.95 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ITA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ita_credit_bureau`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Tax or fiscal identification number.
Example: `SAMPLE-TAX-12345`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `tax_number` must be exactly 16 characters long.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ITA" \
-F "services=ita_credit_bureau" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "tax_number=SAMPLE-TAX-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ITA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ita_credit_bureau",
"service_name": "Italy Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ITA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ita_credit_bureau",
"service_name": "Italy Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ITA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ita_credit_bureau",
"service_name": "Italy Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ita_credit_bureau` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Italy Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.95 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Italy Database Validation overview](/api-reference/database-validation/italy)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Italy Residential
Source: https://docs.didit.me/api-reference/database-validation/italy/residential
POST /v3/database-validation/
Input is verified against a source comprising multiple datasets from consumer, telephone and postal records. Authoritative real-time identity lookup for Italy. Real-time lookup, pay-per-call.
Input is verified against a source comprising multiple datasets from consumer, telephone and postal records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** Italy
* **Service ID:** `ita_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.45 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ITA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ita_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ITA" \
-F "services=ita_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ITA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ita_residential",
"service_name": "Italy Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ITA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ita_residential",
"service_name": "Italy Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ITA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ita_residential",
"service_name": "Italy Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ita_residential` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `street`
## Pricing & SLAs
Italy Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.45 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Italy Database Validation overview](/api-reference/database-validation/italy)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Kenya National ID
Source: https://docs.didit.me/api-reference/database-validation/kenya/national-id
POST /v3/database-validation/
Verifies data against the National Registration Bureau. Authoritative real-time identity lookup for Kenya. Real-time lookup, pay-per-call.
Verifies data against the National Registration Bureau. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Kenya
* **Service ID:** `ken_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$3.15 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `KEN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ken_national_id`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `national_id` must match `\d{8,9}`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=KEN" \
-F "services=ken_national_id" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "KEN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ken_national_id",
"service_name": "Kenya National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "KEN",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ken_national_id",
"service_name": "Kenya National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "KEN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ken_national_id",
"service_name": "Kenya National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ken_national_id` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Kenya National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$3.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Kenya Database Validation overview](/api-reference/database-validation/kenya)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Malaysia National ID
Source: https://docs.didit.me/api-reference/database-validation/malaysia/national-id
POST /v3/database-validation/
Verifies input data against a Government agency database consisting of Malaysian citizens data. Authoritative real-time identity lookup for Malaysia. Real-time lookup, pay-per-call.
Verifies input data against a Government agency database consisting of Malaysian citizens data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** Malaysia
* **Service ID:** `mys_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `111111111111` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `phone` | No | `+15550101000` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `phone`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.35 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `MYS`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `mys_national_id`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `111111111111`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* `national_id` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `national_id` must be exactly 12 characters long.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=MYS" \
-F "services=mys_national_id" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=111111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "MYS",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "mys_national_id",
"service_name": "Malaysia National ID",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "MYS",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "mys_national_id",
"service_name": "Malaysia National ID",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "MYS",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "mys_national_id",
"service_name": "Malaysia National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `mys_national_id` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `identification_number`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
Malaysia National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Malaysia Database Validation overview](/api-reference/database-validation/malaysia)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Mexico - CURP verification
Source: https://docs.didit.me/api-reference/database-validation/mexico/curp
POST /v3/database-validation/
Verifies Mexican CURP against the RENAPO national identifier registry. Authoritative real-time identity lookup for Mexico. Real-time lookup, pay-per-call.
Verifies Mexican CURP against the RENAPO national identifier registry. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Mexico
* **Service ID:** `mex_curp`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | -------------------- |
| `personal_number` | Yes | `ABCD900101HDFABC09` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `MEX`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `mex_curp`
Country-specific personal identity number.
Example: `ABCD900101HDFABC09`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Mexican CURP (exactly 18 alphanumeric characters)
* `personal_number` must be exactly 18 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=MEX" \
-F "services=mex_curp" \
-F "vendor_data=user-1234" \
-F "personal_number=ABCD900101HDFABC09"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "MEX",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "mex_curp",
"service_name": "Mexico - CURP verification",
"source_data": {
"curp_status": "sample_value",
"date_of_birth": "1990-01-01",
"doc_probatorio": "sample_value",
"first_name": "John",
"full_name": "John Doe",
"gender": "M",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"nationality": "USA",
"num_acta": "sample_value",
"registration_municipality": "sample_value",
"registration_state": "sample_value",
"registration_year": "sample_value",
"state_of_birth": "sample_value"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "MEX",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "mex_curp",
"service_name": "Mexico - CURP verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `mex_curp` currently documents this normalized shape:
* `curp_status`
* `date_of_birth`
* `doc_probatorio`
* `first_name`
* `full_name`
* `gender`
* `identification_number`
* `last_name`
* `nationality`
* `num_acta`
* `registration_municipality`
* `registration_state`
* `registration_year`
* `state_of_birth`
## Pricing & SLAs
Mexico - CURP verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Mexico Database Validation overview](/api-reference/database-validation/mexico)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Mexico - INE credential validity verification
Source: https://docs.didit.me/api-reference/database-validation/mexico/ine-vigencia
POST /v3/database-validation/
Verifies a Mexican INE / IFE voter ID credential against the INE registry (modelo, vigencia, válida-como-identificación, derecho-a-votar). Authoritative real-time identity lookup for Mexico. Real-time lookup, pay-per-call.
Verifies a Mexican INE / IFE voter ID credential against the INE registry (modelo, vigencia, válida-como-identificación, derecho-a-votar). Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Mexico
* **Service ID:** `mex_ine_vigencia`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ------------------------- | -------: | --------------- |
| `cic` | No | `123456789` |
| `identificador_ciudadano` | No | `123456789` |
| `ocr` | No | `1234567890123` |
| `voter_number` | No | `ABCDEF123456` |
| `emission_number` | No | `01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** —
* **Optional inputs:** `cic`, `identificador_ciudadano`, `ocr`, `voter_number`, `emission_number`, `first_name`, `last_name`, `date_of_birth`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `MEX`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `mex_ine_vigencia`
`cic` value required by this database service.
Example: `123456789`
`identificador_ciudadano` value required by this database service.
Example: `123456789`
`ocr` value required by this database service.
Example: `1234567890123`
`voter_number` value required by this database service.
Example: `ABCDEF123456`
`emission_number` value required by this database service.
Example: `01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Provide at least one credential identifier: `cic`, `identificador_ciudadano`, `ocr`, or `voter_number`. If all are absent, Didit returns HTTP `400` with the accepted fields instead of calling the provider. Requests rejected before source lookup are not charged.
* For a legacy credential identified by `voter_number`, include `emission_number`.
* A provider rejection remains a completed HTTP `200` validation and carries the real normalized result. For example, provider code `4` is returned as `outcome_code: "INVALID_DOCUMENT_FORMAT"`, `outcome_detail: "4"`, and `source_data.message: "Error de datos"`. Use these fields rather than treating every rejection as a generic availability error.
* Provider code `0` means the submitted credential was not found. It is returned as `outcome_code: "DOCUMENT_NOT_FOUND"`, with `outcome_detail: "0"` and the original explanation in `source_data.message`.
* Provider codes `1` and `10` mean the submitted credential data did not match the INE record. They are returned as `outcome_code: "NO_MATCH"`, with the provider code in `outcome_detail` and the original explanation in `source_data.message`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=MEX" \
-F "services=mex_ine_vigencia" \
-F "vendor_data=user-1234"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "MEX",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "mex_ine_vigencia",
"service_name": "Mexico - INE credential validity verification",
"source_data": {
"emission_number": "SAMPLE-12345",
"emission_year": "sample_value",
"expiration_date": "1990-01-01",
"federal_district": "sample_value",
"identification_number": "SAMPLE-ID-12345",
"message": "sample_value",
"message_code": "sample_value",
"ocr": "sample_value",
"registration_year": "sample_value",
"validation_code": "SAMPLE-12345",
"voter_number": "SAMPLE-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "MEX",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "mex_ine_vigencia",
"service_name": "Mexico - INE credential validity verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `mex_ine_vigencia` currently documents this normalized shape:
* `emission_number`
* `emission_year`
* `expiration_date`
* `federal_district`
* `identification_number`
* `message`
* `message_code`
* `ocr`
* `registration_year`
* `validation_code`
* `voter_number`
## Pricing & SLAs
Mexico - INE credential validity verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Mexico Database Validation overview](/api-reference/database-validation/mexico)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Netherlands Residential
Source: https://docs.didit.me/api-reference/database-validation/netherlands/residential
POST /v3/database-validation/
Verifies input data to a data source containing Government records, postal, and public records. Authoritative real-time identity lookup for Netherlands. Real-time lookup, pay-per-call.
Verifies input data to a data source containing Government records, postal, and public records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 80%
* **Country:** Netherlands
* **Service ID:** `nld_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `national_id` | No | `SAMPLE-NID-12345` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `national_id`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 80%
* **Price:** \$0.90 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NLD`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nld_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NLD" \
-F "services=nld_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NLD",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nld_residential",
"service_name": "Netherlands Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NLD",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nld_residential",
"service_name": "Netherlands Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NLD",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nld_residential",
"service_name": "Netherlands Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nld_residential` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Netherlands Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.90 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Netherlands Database Validation overview](/api-reference/database-validation/netherlands)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DIA - New Zealand Births
Source: https://docs.didit.me/api-reference/database-validation/new-zealand/births
POST /v3/database-validation/
DIA Birth Certificate verification service. Authoritative real-time identity lookup for New Zealand. Real-time lookup, pay-per-call.
DIA Birth Certificate verification service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** New Zealand
* **Service ID:** `nzl_new_zealand_births`
* **Data domain:** Identity
* **Category:** BirthRecord
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$1.00 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NZL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nzl_new_zealand_births`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NZL" \
-F "services=nzl_new_zealand_births" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NZL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nzl_new_zealand_births",
"service_name": "DIA - New Zealand Births",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NZL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nzl_new_zealand_births",
"service_name": "DIA - New Zealand Births",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NZL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nzl_new_zealand_births",
"service_name": "DIA - New Zealand Births",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nzl_new_zealand_births` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
DIA - New Zealand Births queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [New Zealand Database Validation overview](/api-reference/database-validation/new-zealand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DIA - New Zealand Citizenship
Source: https://docs.didit.me/api-reference/database-validation/new-zealand/citizenship
POST /v3/database-validation/
DIA Citizenship verification service. Authoritative real-time identity lookup for New Zealand. Real-time lookup, pay-per-call.
DIA Citizenship verification service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 10%
* **Country:** New Zealand
* **Service ID:** `nzl_new_zealand_citizenship`
* **Data domain:** Identity
* **Category:** Citizenship
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** > 10%
* **Price:** \$1.00 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NZL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nzl_new_zealand_citizenship`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NZL" \
-F "services=nzl_new_zealand_citizenship" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NZL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nzl_new_zealand_citizenship",
"service_name": "DIA - New Zealand Citizenship",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NZL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nzl_new_zealand_citizenship",
"service_name": "DIA - New Zealand Citizenship",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NZL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nzl_new_zealand_citizenship",
"service_name": "DIA - New Zealand Citizenship",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nzl_new_zealand_citizenship` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
DIA - New Zealand Citizenship queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [New Zealand Database Validation overview](/api-reference/database-validation/new-zealand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# New Zealand Death Check (BDM)
Source: https://docs.didit.me/api-reference/database-validation/new-zealand/death-check
POST /v3/database-validation/
The official New Zealand Birth, Deaths, Marriages Register. Authoritative real-time identity lookup for New Zealand. Real-time lookup, pay-per-call.
The official New Zealand Birth, Deaths, Marriages Register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** New Zealand
* **Service ID:** `nzl_zealand_death_check`
* **Data domain:** Other
* **Category:** DeathRecord
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.16 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NZL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nzl_zealand_death_check`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NZL" \
-F "services=nzl_zealand_death_check" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NZL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nzl_zealand_death_check",
"service_name": "New Zealand Death Check (BDM)",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NZL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nzl_zealand_death_check",
"service_name": "New Zealand Death Check (BDM)",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NZL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nzl_zealand_death_check",
"service_name": "New Zealand Death Check (BDM)",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nzl_zealand_death_check` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
New Zealand Death Check (BDM) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.16 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [New Zealand Database Validation overview](/api-reference/database-validation/new-zealand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# New Zealand Drivers Licence (NZTA)
Source: https://docs.didit.me/api-reference/database-validation/new-zealand/drivers-licence
POST /v3/database-validation/
Verifies input data to the NZTA verification service. Authoritative real-time identity lookup for New Zealand. Real-time lookup, pay-per-call.
Verifies input data to the NZTA verification service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** New Zealand
* **Service ID:** `nzl_zealand_drivers_licence`
* **Data domain:** Document
* **Category:** DriverLicence
## Inputs
| Field | Required | Example |
| ------------------------ | -------: | ----------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `driver_license_number` | Yes | `SAMPLE-DL-12345` |
| `driver_license_version` | Yes | `1` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `driver_license_number`, `driver_license_version`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.35 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NZL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nzl_zealand_drivers_licence`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Driver licence number.
Example: `SAMPLE-DL-12345`
`driver_license_version` value required by this database service.
Example: `1`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NZL" \
-F "services=nzl_zealand_drivers_licence" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "driver_license_number=SAMPLE-DL-12345" \
-F "driver_license_version=1"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NZL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nzl_zealand_drivers_licence",
"service_name": "New Zealand Drivers Licence (NZTA)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NZL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nzl_zealand_drivers_licence",
"service_name": "New Zealand Drivers Licence (NZTA)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NZL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nzl_zealand_drivers_licence",
"service_name": "New Zealand Drivers Licence (NZTA)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nzl_zealand_drivers_licence` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
New Zealand Drivers Licence (NZTA) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [New Zealand Database Validation overview](/api-reference/database-validation/new-zealand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# DIA - New Zealand Passport
Source: https://docs.didit.me/api-reference/database-validation/new-zealand/passport
POST /v3/database-validation/
DIA Passport verification service. Authoritative real-time identity lookup for New Zealand. Real-time lookup, pay-per-call.
DIA Passport verification service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** New Zealand
* **Service ID:** `nzl_new_zealand_passport`
* **Data domain:** Document
* **Category:** Passport
## Inputs
| Field | Required | Example |
| -------------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `passport_number` | Yes | `SAMPLE-PASS-12345` |
| `passport_expiration_date` | Yes | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `passport_number`, `passport_expiration_date`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$1.00 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NZL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nzl_new_zealand_passport`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Passport number.
Example: `SAMPLE-PASS-12345`
Passport expiration date in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NZL" \
-F "services=nzl_new_zealand_passport" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "passport_number=SAMPLE-PASS-12345" \
-F "passport_expiration_date=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NZL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nzl_new_zealand_passport",
"service_name": "DIA - New Zealand Passport",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NZL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nzl_new_zealand_passport",
"service_name": "DIA - New Zealand Passport",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NZL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nzl_new_zealand_passport",
"service_name": "DIA - New Zealand Passport",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nzl_new_zealand_passport` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
DIA - New Zealand Passport queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [New Zealand Database Validation overview](/api-reference/database-validation/new-zealand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Nigeria Bank Verification Number (BVN)
Source: https://docs.didit.me/api-reference/database-validation/nigeria/bank-verification-number
POST /v3/database-validation/
Verifies input data against the Nigerian Banking Industry database. Requires a selfie: the enrolment portrait held against the BVN is face-matched with it, and the record is only returned when the two are the same person. Authoritative real-time identity lookup for Nigeria. Real-time lookup, pay-per-call.
Verifies input data against the Nigerian Banking Industry database. Requires a selfie: the enrolment portrait held against the BVN is face-matched with it, and the record is only returned when the two are the same person. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Nigeria
* **Service ID:** `nga_bank_verification_number`
* **Data domain:** Financial
* **Category:** Banking
## Inputs
| Field | Required | Example |
| --------------- | -------: | --------------- |
| `bvn` | Yes | `11111111111` |
| `selfie` | Yes | `@./selfie.jpg` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `bvn`, `selfie`
* **Optional inputs:** `first_name`, `last_name`, `date_of_birth`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** \~ 100%
* **Price:** \$0.35 per successful query
In a workflow, `bvn` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NGA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nga_bank_verification_number`
Bank Verification Number.
Example: `11111111111`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Nigerian Bank Verification Number (exactly 11 digits)
* `bvn` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `bvn` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NGA" \
-F "services=nga_bank_verification_number" \
-F "vendor_data=user-1234" \
-F "bvn=11111111111" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NGA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nga_bank_verification_number",
"service_name": "Nigeria Bank Verification Number (BVN)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NGA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "nga_bank_verification_number",
"service_name": "Nigeria Bank Verification Number (BVN)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`INCONCLUSIVE`** — The registry could not determine whether the person matches - the result is genuinely uncertain (they may or may not be in the registry). This is NOT a no-match and NOT a technical image problem; no field is asserted, so match\_type is null and the check is sent to review.
```json 200 OK — INCONCLUSIVE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NGA",
"match_type": null,
"validations": [
{
"outcome_code": "INCONCLUSIVE",
"service_id": "nga_bank_verification_number",
"service_name": "Nigeria Bank Verification Number (BVN)",
"source_data": {},
"validation": {}
}
]
}
```
**`DOCUMENT_NOT_FOUND`** — The submitted document number does not correspond to any record in the registry.
```json 200 OK — DOCUMENT_NOT_FOUND theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NGA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "DOCUMENT_NOT_FOUND",
"service_id": "nga_bank_verification_number",
"service_name": "Nigeria Bank Verification Number (BVN)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nga_bank_verification_number` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Nigeria Bank Verification Number (BVN) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Nigeria Database Validation overview](/api-reference/database-validation/nigeria)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Nigeria National ID (NIMC)
Source: https://docs.didit.me/api-reference/database-validation/nigeria/national-id
POST /v3/database-validation/
Verifies data against the National Identity database. Authoritative real-time identity lookup for Nigeria. Real-time lookup, pay-per-call.
Verifies data against the National Identity database. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Nigeria
* **Service ID:** `nga_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `national_id` | Yes | `11111111111` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `national_id`
* **Optional inputs:** `date_of_birth`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.20 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NGA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nga_national_id`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
National identity number for this service.
Example: `11111111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `first_name` must match `[A-Za-zÀ-ÖØ-öø-ÿ .'-]+`.
* `national_id` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `national_id` must be exactly 11 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NGA" \
-F "services=nga_national_id" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "national_id=11111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NGA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nga_national_id",
"service_name": "Nigeria National ID (NIMC)",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000"
},
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NGA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nga_national_id",
"service_name": "Nigeria National ID (NIMC)",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000"
},
"validation": {
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NGA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nga_national_id",
"service_name": "Nigeria National ID (NIMC)",
"source_data": {
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nga_national_id` currently documents this normalized shape:
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Nigeria National ID (NIMC) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Nigeria Database Validation overview](/api-reference/database-validation/nigeria)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Norway Residential
Source: https://docs.didit.me/api-reference/database-validation/norway/residential
POST /v3/database-validation/
Verifies input data against the Norwegian Register service. Authoritative real-time identity lookup for Norway. Real-time lookup, pay-per-call.
Verifies input data against the Norwegian Register service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Norway
* **Service ID:** `nor_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `email` | No | `john.doe@example.com` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `email`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$2.42 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `NOR`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `nor_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=NOR" \
-F "services=nor_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "NOR",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "nor_residential",
"service_name": "Norway Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "NOR",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "nor_residential",
"service_name": "Norway Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "NOR",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "nor_residential",
"service_name": "Norway Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `nor_residential` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Norway Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.42 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Norway Database Validation overview](/api-reference/database-validation/norway)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Panama - Cédula with biometric face-match (SIB)
Source: https://docs.didit.me/api-reference/database-validation/panama/sib
POST /v3/database-validation/
Verifies Panamanian Cédula with biometric face-match against the Tribunal Electoral SIB biometric service. Standard tier. Authoritative real-time identity lookup for Panama. Real-time lookup, pay-per-call.
Verifies Panamanian Cédula with biometric face-match against the Tribunal Electoral SIB biometric service. Standard tier. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Panama
* **Service ID:** `pan_cedula_sib`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------ |
| `personal_number` | Yes | `SAMPLE-PER-12345` |
| `selfie` | Yes | `@./selfie.jpg` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`, `selfie`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.75 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PAN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `pan_cedula_sib`
Country-specific personal identity number.
Example: `SAMPLE-PER-12345`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Panamanian Cédula (min 5 characters)
* `personal_number` must be 5-20 characters long.
* `personal_number` must use uppercase letters.
* `personal_number` must match `(?:\d{1,2}(?:-?(?:AV|PI))?|PE|E|N)[\d-]*`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PAN" \
-F "services=pan_cedula_sib" \
-F "vendor_data=user-1234" \
-F "personal_number=SAMPLE-PER-12345" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "pan_cedula_sib",
"service_name": "Panama - Cédula with biometric face-match (SIB)",
"source_data": {
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "pan_cedula_sib",
"service_name": "Panama - Cédula with biometric face-match (SIB)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "pan_cedula_sib",
"service_name": "Panama - Cédula with biometric face-match (SIB)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "pan_cedula_sib",
"service_name": "Panama - Cédula with biometric face-match (SIB)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`DECEASED`** — The identity matched, but the registry flags the person as deceased.
```json 200 OK — DECEASED theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "DECEASED",
"service_id": "pan_cedula_sib",
"service_name": "Panama - Cédula with biometric face-match (SIB)",
"source_data": {
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `pan_cedula_sib` currently documents this normalized shape:
* `identification_number`
## Pricing & SLAs
Panama - Cédula with biometric face-match (SIB) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.75 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Panama Database Validation overview](/api-reference/database-validation/panama)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Panama - Cédula with biometric face-match (SIB Plus, elevated tier)
Source: https://docs.didit.me/api-reference/database-validation/panama/sib-plus
POST /v3/database-validation/
Verifies Panamanian Cédula with biometric face-match — elevated-tier SIB Plus. Same data source as the standard SIB tier with stronger biometric thresholds and richer match metadata. Authoritative real-time identity lookup for Panama. Real-time lookup, pay-per-call.
Verifies Panamanian Cédula with biometric face-match — elevated-tier SIB Plus. Same data source as the standard SIB tier with stronger biometric thresholds and richer match metadata. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Panama
* **Service ID:** `pan_cedula_sib_plus`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------ |
| `personal_number` | Yes | `SAMPLE-PER-12345` |
| `selfie` | Yes | `@./selfie.jpg` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`, `selfie`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$1.50 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PAN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `pan_cedula_sib_plus`
Country-specific personal identity number.
Example: `SAMPLE-PER-12345`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Panamanian Cédula (min 5 characters)
* `personal_number` must be 5-20 characters long.
* `personal_number` must use uppercase letters.
* `personal_number` must match `(?:\d{1,2}(?:-?(?:AV|PI))?|PE|E|N)[\d-]*`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PAN" \
-F "services=pan_cedula_sib_plus" \
-F "vendor_data=user-1234" \
-F "personal_number=SAMPLE-PER-12345" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "pan_cedula_sib_plus",
"service_name": "Panama - Cédula with biometric face-match (SIB Plus, elevated tier)",
"source_data": {
"date_of_birth": "1990-01-01",
"expiration_date": "1990-01-01",
"full_name": "John Doe",
"gender": "M",
"identification_number": "SAMPLE-ID-12345",
"issue_date": "1990-01-01",
"last_name": "Doe",
"place_of_birth": "sample_value",
"signature": "sample_value"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "pan_cedula_sib_plus",
"service_name": "Panama - Cédula with biometric face-match (SIB Plus, elevated tier)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "pan_cedula_sib_plus",
"service_name": "Panama - Cédula with biometric face-match (SIB Plus, elevated tier)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PAN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "pan_cedula_sib_plus",
"service_name": "Panama - Cédula with biometric face-match (SIB Plus, elevated tier)",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`DECEASED`** — The identity matched, but the registry flags the person as deceased.
```json 200 OK — DECEASED theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PAN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "DECEASED",
"service_id": "pan_cedula_sib_plus",
"service_name": "Panama - Cédula with biometric face-match (SIB Plus, elevated tier)",
"source_data": {
"date_of_birth": "1990-01-01",
"expiration_date": "1990-01-01",
"full_name": "John Doe",
"gender": "M",
"identification_number": "SAMPLE-ID-12345",
"issue_date": "1990-01-01",
"last_name": "Doe",
"place_of_birth": "sample_value",
"signature": "sample_value"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `pan_cedula_sib_plus` currently documents this normalized shape:
* `date_of_birth`
* `expiration_date`
* `full_name`
* `gender`
* `identification_number`
* `issue_date`
* `last_name`
* `place_of_birth`
* `signature`
## Pricing & SLAs
Panama - Cédula with biometric face-match (SIB Plus, elevated tier) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.50 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Panama Database Validation overview](/api-reference/database-validation/panama)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Paraguay - CI verification
Source: https://docs.didit.me/api-reference/database-validation/paraguay/cedula
POST /v3/database-validation/
Verifies Paraguayan Cédula data against Registro del Estado Civil government records. Authoritative real-time identity lookup for Paraguay. Real-time lookup, pay-per-call.
Verifies Paraguayan Cédula data against Registro del Estado Civil government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Paraguay
* **Service ID:** `pry_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ----------- |
| `document_number` | Yes | `111111` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PRY`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `pry_cedula`
Document number extracted from or provided by the user.
Example: `111111`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Paraguayan CI (6-10 digits)
* `document_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `document_number` must be 6-10 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PRY" \
-F "services=pry_cedula" \
-F "vendor_data=user-1234" \
-F "document_number=111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PRY",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "pry_cedula",
"service_name": "Paraguay - CI verification",
"source_data": {
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PRY",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "pry_cedula",
"service_name": "Paraguay - CI verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `pry_cedula` currently documents this normalized shape:
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Paraguay - CI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Paraguay Database Validation overview](/api-reference/database-validation/paraguay)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Peru - DNI verification
Source: https://docs.didit.me/api-reference/database-validation/peru/dni
POST /v3/database-validation/
Verifies Peruvian DNI against the RENIEC civil registry. Authoritative real-time identity lookup for Peru. Real-time lookup, pay-per-call.
Verifies Peruvian DNI against the RENIEC civil registry. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** Peru
* **Service ID:** `per_dni`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ----------- |
| `personal_number` | Yes | `11111111` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PER`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `per_dni`
Country-specific personal identity number.
Example: `11111111`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Peruvian DNI (exactly 8 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be exactly 8 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PER" \
-F "services=per_dni" \
-F "vendor_data=user-1234" \
-F "personal_number=11111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PER",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "per_dni",
"service_name": "Peru - DNI verification",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"maternal_name": "Doe Maternal",
"paternal_name": "Doe Paternal",
"verification_letter": "G",
"verification_number": 2
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PER",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "per_dni",
"service_name": "Peru - DNI verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`DOCUMENT_NOT_FOUND`** — The submitted document number does not correspond to any record in the registry.
```json 200 OK — DOCUMENT_NOT_FOUND theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PER",
"match_type": "no_match",
"validations": [
{
"outcome_code": "DOCUMENT_NOT_FOUND",
"service_id": "per_dni",
"service_name": "Peru - DNI verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `per_dni` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
* `maternal_name`
* `paternal_name`
* `verification_letter`
* `verification_number`
## Pricing & SLAs
Peru - DNI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Peru Database Validation overview](/api-reference/database-validation/peru)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Peru Tax Registration (SUNAT)
Source: https://docs.didit.me/api-reference/database-validation/peru/tax-registration
POST /v3/database-validation/
Verifies input data against SUNAT. Authoritative real-time identity lookup for Peru. Real-time lookup, pay-per-call.
Verifies input data against SUNAT. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 90%
* **Country:** Peru
* **Service ID:** `per_tax_registration`
* **Data domain:** Financial
* **Category:** TaxRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `personal_number` | Yes | `11111111` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `personal_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 90%
* **Price:** \$1.75 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PER`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `per_tax_registration`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Country-specific personal identity number.
Example: `11111111`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Peruvian DNI (exactly 8 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be exactly 8 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PER" \
-F "services=per_tax_registration" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "personal_number=11111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PER",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "per_tax_registration",
"service_name": "Peru Tax Registration (SUNAT)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PER",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "per_tax_registration",
"service_name": "Peru Tax Registration (SUNAT)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PER",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "per_tax_registration",
"service_name": "Peru Tax Registration (SUNAT)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `per_tax_registration` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Peru Tax Registration (SUNAT) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.75 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Peru Database Validation overview](/api-reference/database-validation/peru)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Philippines Residential
Source: https://docs.didit.me/api-reference/database-validation/philippines/residential
POST /v3/database-validation/
Verifies input data against a Government agency database consisting of Filipino citizens data. Authoritative real-time identity lookup for Philippines. Real-time lookup, pay-per-call.
Verifies input data against a Government agency database consisting of Filipino citizens data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 80%
* **Country:** Philippines
* **Service ID:** `phl_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** > 80%
* **Price:** \$0.35 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `PHL`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `phl_residential`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=PHL" \
-F "services=phl_residential" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "PHL",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "phl_residential",
"service_name": "Philippines Residential",
"source_data": {
"date_of_birth": "1990-01-01",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "PHL",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "phl_residential",
"service_name": "Philippines Residential",
"source_data": {
"date_of_birth": "1990-01-01",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "PHL",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "phl_residential",
"service_name": "Philippines Residential",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `phl_residential` currently documents this normalized shape:
* `date_of_birth`
* `name_match_score`
## Pricing & SLAs
Philippines Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Philippines Database Validation overview](/api-reference/database-validation/philippines)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Singapore Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/singapore/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in Singapore. Authoritative real-time identity lookup for Singapore. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in Singapore. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** undisclosed
* **Country:** Singapore
* **Service ID:** `sgp_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `full_name` | Yes | `John Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `full_name`, `date_of_birth`, `address.street_1`, `address.postal_code`, `national_id`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** undisclosed
* **Price:** \$2.61 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `SGP`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `sgp_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Full legal name to validate.
Example: `John Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=SGP" \
-F "services=sgp_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "full_name=John Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "SGP",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "sgp_credit_bureau",
"service_name": "Singapore Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "SGP",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "sgp_credit_bureau",
"service_name": "Singapore Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "SGP",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "sgp_credit_bureau",
"service_name": "Singapore Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `sgp_credit_bureau` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `identification_number`
* `postal_code`
* `street`
## Pricing & SLAs
Singapore Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.61 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Singapore Database Validation overview](/api-reference/database-validation/singapore)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Singapore Utility
Source: https://docs.didit.me/api-reference/database-validation/singapore/utility
POST /v3/database-validation/
Verifies input data to telco billing records and phone data. Authoritative real-time identity lookup for Singapore. Real-time lookup, pay-per-call.
Verifies input data to telco billing records and phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 80%
* **Country:** Singapore
* **Service ID:** `sgp_utility`
* **Data domain:** Address
* **Category:** Utility
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `date_of_birth` | No | `1990-01-01` |
| `phone` | No | `+15550101000` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `date_of_birth`, `phone`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 80%
* **Price:** \$1.69 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `SGP`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `sgp_utility`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=SGP" \
-F "services=sgp_utility" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "SGP",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "sgp_utility",
"service_name": "Singapore Utility",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "SGP",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "sgp_utility",
"service_name": "Singapore Utility",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"street": "sample_value",
"verifications": {
"address": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "SGP",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "sgp_utility",
"service_name": "Singapore Utility",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `sgp_utility` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `street`
## Pricing & SLAs
Singapore Utility queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.69 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Singapore Database Validation overview](/api-reference/database-validation/singapore)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Bank Account Holder Verification
Source: https://docs.didit.me/api-reference/database-validation/south-africa/bank-account-holder
POST /v3/database-validation/
Confirms a South African bank account belongs to the named holder via the inter-bank Account Holder Verification service. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Confirms a South African bank account belongs to the named holder via the inter-bank Account Holder Verification service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_bank_account_holder`
* **Data domain:** Financial
* **Category:** Banking
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `national_id` | Yes | `8708150847085` |
| `bank_account_number` | Yes | `1234567890` |
| `bank_name` | Yes | `ABSA` |
| `account_type` | No | `current` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `initials` | No | `JD` |
| `email` | No | `john.doe@example.com` |
| `phone_number` | No | `+15550101000` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`, `bank_account_number`, `bank_name`
* **Optional inputs:** `account_type`, `first_name`, `last_name`, `initials`, `email`, `phone_number`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$0.40 per successful query
In a workflow, `bank_account_number` and `bank_name` are not printed on an identity document, so no step produces them on their own. Collect them from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map each one on the Database Validation step. Until they are mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_bank_account_holder`
Explicit end-user consent for this service.
Example: `true`
National identity number for this service.
Example: `8708150847085`
`bank_account_number` value required by this database service.
Example: `1234567890`
`bank_name` value required by this database service.
Example: `ABSA`
`account_type` value required by this database service.
Example: `current`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
`initials` value required by this database service.
Example: `JD`
Email address.
Example: `john.doe@example.com`
`phone_number` value required by this database service.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `account_type` must be one of the South African AVS account types: `current`, `savings`, `transmission`, `subscriptionShare`, `notKnown`. Matching is case-insensitive. Omit the field when you do not know the account type - Didit then sends `notKnown` and the account-type cross-check is reported as not confirmed instead of failing the request.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_bank_account_holder" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "national_id=8708150847085" \
-F "bank_account_number=1234567890" \
-F "bank_name=ABSA" \
-F "account_type=current" \
-F "last_name=Doe" \
-F "initials=JD"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_bank_account_holder",
"service_name": "South Africa - Bank Account Holder Verification",
"source_data": {
"accepts_credits": true,
"accepts_debits": true,
"account_found": true,
"account_number_length_valid": true,
"account_open": true,
"account_type_match": true,
"id_match": true,
"identification_number": "SAMPLE-ID-12345",
"initials_match": true,
"surname_match": true
},
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ZAF",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "zaf_bank_account_holder",
"service_name": "South Africa - Bank Account Holder Verification",
"source_data": {
"accepts_credits": true,
"accepts_debits": true,
"account_found": true,
"account_number_length_valid": false,
"account_open": true,
"account_type_match": true,
"id_match": true,
"identification_number": "SAMPLE-ID-12345",
"initials_match": true,
"surname_match": false
},
"validation": {
"full_name": "partial_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_bank_account_holder",
"service_name": "South Africa - Bank Account Holder Verification",
"source_data": {
"accepts_credits": false,
"accepts_debits": false,
"account_found": false,
"account_number_length_valid": false,
"account_open": false,
"account_type_match": false,
"id_match": false,
"identification_number": "NO_MATCH",
"initials_match": false,
"surname_match": false
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_bank_account_holder` currently documents this normalized shape:
* `accepts_credits`
* `accepts_debits`
* `account_found`
* `account_number_length_valid`
* `account_open`
* `account_type_match`
* `id_match`
* `identification_number`
* `initials_match`
* `surname_match`
## Pricing & SLAs
South Africa - Bank Account Holder Verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.40 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## What a MATCH confirms
Account Holder Verification (AVS) is a set of yes/no cross-checks, not a record lookup. The source never returns a name, an ID number or an account balance - it takes the details you send, compares them against what the bank holds for that account, and answers each comparison with a flag. `source_data` carries those flags:
* **`account_found`** - the bank holds an account with this number. Everything else is meaningless when this is `false`.
* **`account_open`** - the account is open. An account can be found but closed.
* **`id_match`** - the account is held by the national id you submitted. This is the check the service exists for.
* **`initials_match`**, **`surname_match`** - the submitted `initials` and `last_name` match the account holder on file.
* **`account_type_match`** - the submitted `account_type` matches the account's real type.
* **`email_match`**, **`phone_match`** - the submitted `email` and `phone_number` match the contact details the bank holds.
* **`accepts_debits`**, **`accepts_credits`** - the account can receive debit orders and credits.
* **`account_number_length_valid`** - the account number is the length the bank expects.
The outcome code summarises them:
* **`MATCH`** - the account exists and every field you submitted matched, `id_match` included.
* **`PARTIAL_MATCH`** - the account is held by the submitted national id, but at least one other field you sent did not match (a surname spelled differently on the bank's records, for example). `outcome_detail` names the fields that failed.
* **`NO_MATCH`** - either the bank holds no such account, or the account exists but belongs to someone else. Both are conclusive answers about the person.
A cross-check you did not ask for is neither counted nor reported. AVS answers `N` for every field that was not supplied, so `surname_match`, `initials_match`, `email_match`, `phone_match` and `account_type_match` appear in `source_data` only when you sent the matching input. Sending only `national_id`, `bank_account_number` and `bank_name` therefore still produces a full `MATCH`.
## Accepted bank\_name and account\_type values
`bank_name` and `account_type` are enumerations. A value outside either list is refused before the query runs, so a typo costs you a round-trip rather than a charge.
`account_type` is optional and accepts, case-insensitively:
`current` · `savings` · `transmission` · `subscriptionShare` · `notKnown`
Omit it when you do not know the account type. Didit then sends `notKnown`, the request succeeds, and `account_type_match` comes back `false` because there was nothing to compare. Any other value - `checking`, `cheque`, `bond` - is rejected with a `400` that lists the accepted set.
`bank_name` is required, and these values are confirmed accepted (case-insensitive):
`ABSA` · `Capitec` · `FNB` · `Nedbank` · `Standard Bank` · `Investec` · `African Bank` · `Discovery Bank` · `Old Mutual` · `Bank Zero` · `Tyme` · `Grindrod`
Consumer accounts at all of these are in scope, Capitec included. Send the short form exactly as listed - `Capitec`, not `Capitec Bank`; `Tyme`, not `TymeBank`. An unrecognised bank comes back as an outcome of `REGISTRY_ERROR` whose `outcome_detail` names the field the source refused.
## Telling a missing account apart from an unavailable source
These are different HTTP outcomes, so no parsing of error strings is needed:
* **The account does not exist** - `200 OK`, `match_type: "no_match"`, `outcome_code: "NO_MATCH"`, `source_data.account_found: false`. A real answer from the bank. Billed, and your no-match action applies.
* **The source did not answer** - `502 Bad Gateway` with `validation_errors[].code` = `empty_provider_response` and `retryable: true`. Nothing was established about the person, nothing is billed, and the request is safe to retry.
* **The source refused to run the query** - `400 Bad Request` with `validation_errors[].code` = `provider_rejected_input` and `retryable: false`. The source never searched, because it would not accept what it was sent. Correct the data; retrying the same values cannot succeed. Nothing is billed.
* **The source answered and rejected your input** - `502 Bad Gateway` with `validation_errors[].code` = `provider_invalid_input` (for example an account number that fails the bank's own format check). Retrying is pointless until the input is corrected; nothing is billed.
* **Your request never left Didit** - `400 Bad Request`, listing the field to fix. Nothing is billed.
Only the first of these is an answer about the person. Treat a `502` as transient - AVS relays to the account holder's bank rather than reading a registry, so it is the slowest of the South African services - and retry with backoff.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Company Directors Lookup
Source: https://docs.didit.me/api-reference/database-validation/south-africa/company-directors
POST /v3/database-validation/
Returns the current directors of a South African company from the CIPC commercial register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Returns the current directors of a South African company from the CIPC commercial register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_company_directors`
* **Data domain:** Government
* **Category:** CompanyRegister
## Inputs
| Field | Required | Example |
| ----------------------------- | -------: | ------------ |
| `company_registration_number` | Yes | `REG-123456` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `company_registration_number`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$0.65 per successful query
In a workflow, `company_registration_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_company_directors`
`company_registration_number` value required by this database service.
Example: `REG-123456`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_company_directors" \
-F "vendor_data=user-1234" \
-F "company_registration_number=REG-123456"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_company_directors",
"service_name": "South Africa - Company Directors Lookup",
"source_data": {},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_company_directors",
"service_name": "South Africa - Company Directors Lookup",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_company_directors` currently documents this normalized shape:
* Varies by registry response.
## Pricing & SLAs
South Africa - Company Directors Lookup queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.65 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Company Registry Lookup
Source: https://docs.didit.me/api-reference/database-validation/south-africa/company-registry
POST /v3/database-validation/
Looks up a South African company by registration number against the CIPC commercial register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Looks up a South African company by registration number against the CIPC commercial register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_company_registry`
* **Data domain:** Government
* **Category:** CompanyRegister
## Inputs
| Field | Required | Example |
| ----------------------------- | -------: | -------------------- |
| `company_registration_number` | Yes | `REG-123456` |
| `company_name` | No | `Sample Company LLC` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `company_registration_number`
* **Optional inputs:** `company_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$0.65 per successful query
In a workflow, `company_registration_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_company_registry`
`company_registration_number` value required by this database service.
Example: `REG-123456`
`company_name` value required by this database service.
Example: `Sample Company LLC`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_company_registry" \
-F "vendor_data=user-1234" \
-F "company_registration_number=REG-123456"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_company_registry",
"service_name": "South Africa - Company Registry Lookup",
"source_data": {},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_company_registry",
"service_name": "South Africa - Company Registry Lookup",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_company_registry` currently documents this normalized shape:
* Varies by registry response.
## Pricing & SLAs
South Africa - Company Registry Lookup queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.65 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Contactability Lookup
Source: https://docs.didit.me/api-reference/database-validation/south-africa/contactability
POST /v3/database-validation/
Returns known phone numbers, emails, and addresses tied to a South African individual. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Returns known phone numbers, emails, and addresses tied to a South African individual. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_contactability`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| ------------- | -------: | ------------------ |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.35 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_contactability`
Explicit end-user consent for this service.
Example: `true`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_contactability" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_contactability",
"service_name": "South Africa - Contactability Lookup",
"source_data": {
"contact_email": "alex.sample@example.com",
"contact_phone_numbers": "+15550101000",
"date_of_birth": "1990-01-01",
"deceased": "sample_value",
"first_name": "John",
"gender": "M",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"phone_history_count": "+15550101000"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_contactability",
"service_name": "South Africa - Contactability Lookup",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_contactability` currently documents this normalized shape:
* `contact_email`
* `contact_phone_numbers`
* `date_of_birth`
* `deceased`
* `first_name`
* `gender`
* `identification_number`
* `last_name`
* `phone_history_count`
## Pricing & SLAs
South Africa - Contactability Lookup queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Criminal Screening by Face
Source: https://docs.didit.me/api-reference/database-validation/south-africa/criminal-face-screening
POST /v3/database-validation/
Screens a facial image against published criminal-record datasets to surface potential matches. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Screens a facial image against published criminal-record datasets to surface potential matches. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_criminal_face_screening`
* **Data domain:** Criminal
* **Category:** Criminal
## Inputs
| Field | Required | Example |
| ------------- | -------: | --------------- |
| `selfie` | Yes | `@./selfie.jpg` |
| `score_level` | No | `standard` |
| `max_matches` | No | `10` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `selfie`
* **Optional inputs:** `score_level`, `max_matches`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$7.00 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_criminal_face_screening`
Explicit end-user consent for this service.
Example: `true`
Selfie image file to upload for biometric database validation. Accepted formats: JPEG, PNG, or WebP.
Example: `@./selfie.jpg`
`score_level` value required by this database service.
Example: `standard`
`max_matches` value required by this database service.
Example: `10`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_criminal_face_screening" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "selfie=@./selfie.jpg"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_criminal_face_screening",
"service_name": "South Africa - Criminal Screening by Face",
"source_data": {
"criminal_matches": "sample_value",
"face_quality": "sample_value",
"request_id": "SAMPLE-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_criminal_face_screening",
"service_name": "South Africa - Criminal Screening by Face",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_NO_MATCH`** — The face-match score was below the acceptance threshold - the selfie is not the same person as the registry photo.
```json 200 OK — BIOMETRIC_NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_NO_MATCH",
"service_id": "zaf_criminal_face_screening",
"service_name": "South Africa - Criminal Screening by Face",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
**`BIOMETRIC_IMAGE_UNUSABLE`** — The selfie could not be processed (empty, no face, low quality, or the registry could not read it). Prompt the user to retake the selfie.
```json 200 OK — BIOMETRIC_IMAGE_UNUSABLE theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "BIOMETRIC_IMAGE_UNUSABLE",
"service_id": "zaf_criminal_face_screening",
"service_name": "South Africa - Criminal Screening by Face",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_criminal_face_screening` currently documents this normalized shape:
* `criminal_matches`
* `face_quality`
* `request_id`
## Pricing & SLAs
South Africa - Criminal Screening by Face queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$7.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - DHA Fingerprint Match
Source: https://docs.didit.me/api-reference/database-validation/south-africa/dha-fingerprint-match
POST /v3/database-validation/
Verifies provided fingerprint images against the Department of Home Affairs fingerprint register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Verifies provided fingerprint images against the Department of Home Affairs fingerprint register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_dha_fingerprint_match`
* **Data domain:** Biometric
* **Category:** Government
## Inputs
| Field | Required | Example |
| ------------- | -------: | ---------------------- |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `right_thumb` | Yes | `RIGHT_THUMB_TEMPLATE` |
| `left_thumb` | Yes | `LEFT_THUMB_TEMPLATE` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`, `right_thumb`, `left_thumb`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Standalone API only
* **Coverage:** —
* **Price:** \$1.10 per successful query
This service needs an input no workflow step can produce (a biometric capture, a signed artifact, a live one-time code), so it is available only on the standalone API, where you supply the values directly.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_dha_fingerprint_match`
Explicit end-user consent for this service.
Example: `true`
National identity number for this service.
Example: `SAMPLE-NID-12345`
`right_thumb` value required by this database service.
Example: `RIGHT_THUMB_TEMPLATE`
`left_thumb` value required by this database service.
Example: `LEFT_THUMB_TEMPLATE`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_dha_fingerprint_match" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "national_id=SAMPLE-NID-12345" \
-F "right_thumb=RIGHT_THUMB_TEMPLATE" \
-F "left_thumb=LEFT_THUMB_TEMPLATE"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_dha_fingerprint_match",
"service_name": "South Africa - DHA Fingerprint Match",
"source_data": {
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_dha_fingerprint_match",
"service_name": "South Africa - DHA Fingerprint Match",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_dha_fingerprint_match` currently documents this normalized shape:
* `identification_number`
## Pricing & SLAs
South Africa - DHA Fingerprint Match queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.10 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - DHA Photo Retrieval
Source: https://docs.didit.me/api-reference/database-validation/south-africa/dha-photo
POST /v3/database-validation/
Retrieves the registered Department of Home Affairs photograph for a South African ID number. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Retrieves the registered Department of Home Affairs photograph for a South African ID number. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_dha_photo`
* **Data domain:** Biometric
* **Category:** Government
## Inputs
| Field | Required | Example |
| ------------- | -------: | ------------------ |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$1.10 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_dha_photo`
Explicit end-user consent for this service.
Example: `true`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_dha_photo" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_dha_photo",
"service_name": "South Africa - DHA Photo Retrieval",
"source_data": {
"birth_place_country_code": "sample_value",
"deceased": "sample_value",
"first_name": "John",
"id_blocked": false,
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"marital_status": "sample_value",
"on_hanis_biometric_register": "sample_value",
"on_national_population_register": "sample_value",
"photo_base64": "sample_value",
"smart_card_issued": "sample_value",
"transaction_number": "SAMPLE-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_dha_photo",
"service_name": "South Africa - DHA Photo Retrieval",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_dha_photo` currently documents this normalized shape:
* `birth_place_country_code`
* `deceased`
* `first_name`
* `id_blocked`
* `identification_number`
* `last_name`
* `marital_status`
* `on_hanis_biometric_register`
* `on_national_population_register`
* `photo_base64`
* `smart_card_issued`
* `transaction_number`
## Pricing & SLAs
South Africa - DHA Photo Retrieval queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.10 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Driving License Verification
Source: https://docs.didit.me/api-reference/database-validation/south-africa/drivers-license
POST /v3/database-validation/
Verifies a South African driving license against the NATIS / Department of Transport register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Verifies a South African driving license against the NATIS / Department of Transport register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_drivers_license`
* **Data domain:** Identity
* **Category:** DriverLicence
## Inputs
| Field | Required | Example |
| ---------------- | -------: | ------------------ |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `last_name` | Yes | `Doe` |
| `initials` | Yes | `JD` |
| `licence_number` | Yes | `LIC123456` |
| `first_name` | No | `John` |
| `date_of_birth` | No | `1990-01-01` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`, `last_name`, `initials`, `licence_number`
* **Optional inputs:** `first_name`, `date_of_birth`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$0.70 per successful query
In a workflow, `initials` and `licence_number` are not printed on an identity document, so no step produces them on their own. Collect them from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map each one on the Database Validation step. Until they are mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_drivers_license`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Family name to validate.
Example: `Doe`
`initials` value required by this database service.
Example: `JD`
`licence_number` value required by this database service.
Example: `LIC123456`
Given name to validate.
Example: `John`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_drivers_license" \
-F "vendor_data=user-1234" \
-F "national_id=SAMPLE-NID-12345" \
-F "last_name=Doe" \
-F "initials=JD" \
-F "licence_number=LIC123456"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_drivers_license",
"service_name": "South Africa - Driving License Verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ZAF",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "zaf_drivers_license",
"service_name": "South Africa - Driving License Verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_drivers_license",
"service_name": "South Africa - Driving License Verification",
"source_data": {
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_drivers_license` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
South Africa - Driving License Verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.70 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## What a MATCH confirms
This is a **record match against the driving-licence register**, not a licence-standing check. You send a South African ID number and a licence number (plus surname and initials as cross-checks); the register is asked whether it holds that licence for that person, and `validation` reports how each submitted field compared with the record.
A `MATCH` therefore means *this licence number belongs to this person on the register*. It does not assert that the licence is currently valid to drive on.
`source_data` carries the record the register returned:
* **`identification_number`** - the licence number held on the record.
* **`first_name`**, **`last_name`** - the forenames and surname on the record.
* **`vehicle_codes`** - the vehicle codes (licence classes) the record carries, e.g. `EB`, `C1`.
* **`expiration_date`** - the expiry date of the licence card on the record.
**What is not returned.** There is no suspension, cancellation, withdrawal, endorsement or demerit-point field: the register does not answer those through this service, so a `MATCH` must never be worded to your users as "the licence is valid and in good standing". `expiration_date` is the one standing-adjacent fact you get - compare it with the session date to decide whether the licence has expired, and treat that as a rule of your own rather than as part of the match outcome.
If you need to state this in terms and conditions, word it as: Didit confirms the licence details against the national register and returns the licence's classes and expiry date - not that Didit confirms the holder is licensed to drive today.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Fraud Prevention Screening
Source: https://docs.didit.me/api-reference/database-validation/south-africa/fraud-prevention
POST /v3/database-validation/
Screens an individual against the South African Fraud Prevention Service (SAFPS) watch-list. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Screens an individual against the South African Fraud Prevention Service (SAFPS) watch-list. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_fraud_prevention`
* **Data domain:** Background
* **Category:** Background
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `national_id` | No | `SAMPLE-NID-12345` |
| `bank_account_number` | No | `1234567890` |
| `phone_number` | No | `+15550101000` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** —
* **Optional inputs:** `national_id`, `bank_account_number`, `phone_number`, `email`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$0.30 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_fraud_prevention`
Explicit end-user consent for this service.
Example: `true`
National identity number for this service.
Example: `SAMPLE-NID-12345`
`bank_account_number` value required by this database service.
Example: `1234567890`
`phone_number` value required by this database service.
Example: `+15550101000`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_fraud_prevention" \
-F "vendor_data=user-1234" \
-F "consent=true"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_fraud_prevention",
"service_name": "South Africa - Fraud Prevention Screening",
"source_data": {
"fraud_listings": "sample_value",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_fraud_prevention",
"service_name": "South Africa - Fraud Prevention Screening",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_fraud_prevention` currently documents this normalized shape:
* `fraud_listings`
* `identification_number`
## Pricing & SLAs
South Africa - Fraud Prevention Screening queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.30 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa National ID (DHA)
Source: https://docs.didit.me/api-reference/database-validation/south-africa/national-id
POST /v3/database-validation/
Verifies input data against the Department of Home Affairs (DHA) registry in South Africa. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Verifies input data against the Department of Home Affairs (DHA) registry in South Africa. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** South Africa
* **Service ID:** `zaf_africa_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$1.10 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_africa_national_id`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_africa_national_id" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_africa_national_id",
"service_name": "South Africa National ID (DHA)",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"marital_status": "sample_value"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ZAF",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "zaf_africa_national_id",
"service_name": "South Africa National ID (DHA)",
"source_data": {
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"marital_status": "sample_value"
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_africa_national_id",
"service_name": "South Africa National ID (DHA)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
**`DOCUMENT_NOT_FOUND`** — The submitted document number does not correspond to any record in the registry.
```json 200 OK — DOCUMENT_NOT_FOUND theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "DOCUMENT_NOT_FOUND",
"service_id": "zaf_africa_national_id",
"service_name": "South Africa National ID (DHA)",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_africa_national_id` currently documents this normalized shape:
* `first_name`
* `identification_number`
* `last_name`
* `marital_status`
## Pricing & SLAs
South Africa National ID (DHA) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.10 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Person Directorships Lookup
Source: https://docs.didit.me/api-reference/database-validation/south-africa/person-directorships
POST /v3/database-validation/
Returns all CIPC-registered directorships held by a South African individual. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Returns all CIPC-registered directorships held by a South African individual. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_person_directorships`
* **Data domain:** Government
* **Category:** CompanyRegister
## Inputs
| Field | Required | Example |
| ------------- | -------: | ------------------ |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** —
* **Price:** \$1.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_person_directorships`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_person_directorships" \
-F "vendor_data=user-1234" \
-F "national_id=SAMPLE-NID-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_person_directorships",
"service_name": "South Africa - Person Directorships Lookup",
"source_data": {
"directorships": "sample_value",
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_person_directorships",
"service_name": "South Africa - Person Directorships Lookup",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_person_directorships` currently documents this normalized shape:
* `directorships`
* `identification_number`
## Pricing & SLAs
South Africa - Person Directorships Lookup queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Refugee File Verification
Source: https://docs.didit.me/api-reference/database-validation/south-africa/refugee
POST /v3/database-validation/
Verifies a refugee file number against the South African Department of Home Affairs refugee register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Verifies a refugee file number against the South African Department of Home Affairs refugee register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_refugee`
* **Data domain:** Identity
* **Category:** Government
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `refugee_file_number` | Yes | `RF123456` |
| `phone_number` | No | `+15550101000` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `refugee_file_number`
* **Optional inputs:** `phone_number`, `email`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$1.10 per successful query
In a workflow, `refugee_file_number` is not printed on an identity document, so no step produces it on its own. Collect it from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step. Until it is mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_refugee`
`refugee_file_number` value required by this database service.
Example: `RF123456`
`phone_number` value required by this database service.
Example: `+15550101000`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_refugee" \
-F "vendor_data=user-1234" \
-F "refugee_file_number=RF123456"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_refugee",
"service_name": "South Africa - Refugee File Verification",
"source_data": {},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_refugee",
"service_name": "South Africa - Refugee File Verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_refugee` currently documents this normalized shape:
* Varies by registry response.
## Pricing & SLAs
South Africa - Refugee File Verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.10 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# South Africa - Vehicle Ownership Verification
Source: https://docs.didit.me/api-reference/database-validation/south-africa/vehicle-ownership
POST /v3/database-validation/
Confirms that a South African individual is the registered owner of a given vehicle via the NATIS vehicle register. Authoritative real-time identity lookup for South Africa. Real-time lookup, pay-per-call.
Confirms that a South African individual is the registered owner of a given vehicle via the NATIS vehicle register. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** —
* **Country:** South Africa
* **Service ID:** `zaf_vehicle_ownership`
* **Data domain:** Government
* **Category:** Other
## Inputs
| Field | Required | Example |
| ------------------------- | -------: | ------------------- |
| `national_id` | Yes | `SAMPLE-NID-12345` |
| `vehicle_register_number` | Yes | `VRN123456` |
| `vehicle_licence_plate` | Yes | `ABC123GP` |
| `vin` | Yes | `1HGCM82633A004352` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `national_id`, `vehicle_register_number`, `vehicle_licence_plate`, `vin`
* **Optional inputs:** `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow with field mapping
* **Coverage:** —
* **Price:** \$2.80 per successful query
In a workflow, `vehicle_register_number`, `vehicle_licence_plate` and `vin` are not printed on an identity document, so no step produces them on their own. Collect them from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map each one on the Database Validation step. Until they are mapped the workflow editor shows this service disabled and names the missing input; at run time a missing input means the check is skipped before any query, so you are never billed for a database that could not be asked.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ZAF`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `zaf_vehicle_ownership`
National identity number for this service.
Example: `SAMPLE-NID-12345`
`vehicle_register_number` value required by this database service.
Example: `VRN123456`
`vehicle_licence_plate` value required by this database service.
Example: `ABC123GP`
`vin` value required by this database service.
Example: `1HGCM82633A004352`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Send the fields listed above exactly as captured from the user or document.
* Didit validates required fields before calling the database. Requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ZAF" \
-F "services=zaf_vehicle_ownership" \
-F "vendor_data=user-1234" \
-F "national_id=SAMPLE-NID-12345" \
-F "vehicle_register_number=VRN123456" \
-F "vehicle_licence_plate=ABC123GP" \
-F "vin=1HGCM82633A004352"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ZAF",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "zaf_vehicle_ownership",
"service_name": "South Africa - Vehicle Ownership Verification",
"source_data": {
"identification_number": "SAMPLE-ID-12345"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ZAF",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "zaf_vehicle_ownership",
"service_name": "South Africa - Vehicle Ownership Verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `zaf_vehicle_ownership` currently documents this normalized shape:
* `identification_number`
## Pricing & SLAs
South Africa - Vehicle Ownership Verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.80 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [South Africa Database Validation overview](/api-reference/database-validation/south-africa)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Spain - DNI/NIE verification
Source: https://docs.didit.me/api-reference/database-validation/spain/dni
POST /v3/database-validation/
Verifies a Spanish DNI / NIE holder's name, date of birth and address against national identity records. Authoritative real-time identity lookup for Spain. Real-time lookup, pay-per-call.
Verifies a Spanish DNI / NIE holder's name, date of birth and address against national identity records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 75%
* **Country:** Spain
* **Service ID:** `esp_dni`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `national_id` | No | `SAMPLE-NID-12345` |
| `phone` | No | `+15550101000` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `national_id`, `phone`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 75%
* **Price:** \$1.26 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `ESP`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `esp_dni`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=ESP" \
-F "services=esp_dni" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "ESP",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "esp_dni",
"service_name": "Spain - DNI/NIE verification",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "ESP",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "esp_dni",
"service_name": "Spain - DNI/NIE verification",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "ESP",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "esp_dni",
"service_name": "Spain - DNI/NIE verification",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `esp_dni` currently documents this normalized shape:
* `address_match_score`
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Spain - DNI/NIE verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$1.26 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Spain Database Validation overview](/api-reference/database-validation/spain)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Sweden National ID
Source: https://docs.didit.me/api-reference/database-validation/sweden/national-id
POST /v3/database-validation/
Verifies input data against the Swedish Police Authority service. Authoritative real-time identity lookup for Sweden. Real-time lookup, pay-per-call.
Verifies input data against the Swedish Police Authority service. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Sweden
* **Service ID:** `swe_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `11111111` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.35 per successful query
Not every document this service accepts carries the identifier it queries by. When the identifier is not extracted from the document the check is recorded as **not applicable** - no database is queried and you are not charged - which for some countries is a large share of real users. To cover them, also collect the identifier from a Questionnaire answer, a Document AI extraction field, or a value you send when you create the session, and map it on the Database Validation step.
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `SWE`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `swe_national_id`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `11111111`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* `national_id` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `national_id` must match `(\d{10}|\d{12})`.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=SWE" \
-F "services=swe_national_id" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=11111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "SWE",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "swe_national_id",
"service_name": "Sweden National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "SWE",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "swe_national_id",
"service_name": "Sweden National ID",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "SWE",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "swe_national_id",
"service_name": "Sweden National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `swe_national_id` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
* `name_match_score`
## Pricing & SLAs
Sweden National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Sweden Database Validation overview](/api-reference/database-validation/sweden)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Sweden Phone
Source: https://docs.didit.me/api-reference/database-validation/sweden/phone
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. Authoritative real-time identity lookup for Sweden. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 100%
* **Country:** Sweden
* **Service ID:** `swe_phone`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `national_id` | No | `SAMPLE-NID-12345` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `phone`
* **Optional inputs:** `date_of_birth`, `national_id`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 100%
* **Price:** \$0.35 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `SWE`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `swe_phone`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `SAMPLE-NID-12345`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=SWE" \
-F "services=swe_phone" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "SWE",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "swe_phone",
"service_name": "Sweden Phone",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "SWE",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "swe_phone",
"service_name": "Sweden Phone",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "SWE",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "swe_phone",
"service_name": "Sweden Phone",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `swe_phone` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Sweden Phone queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Sweden Database Validation overview](/api-reference/database-validation/sweden)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Switzerland Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/switzerland/credit-bureau
POST /v3/database-validation/
Credit Header data obtained from the Credit Bureau in Switzerland. Authoritative real-time identity lookup for Switzerland. Real-time lookup, pay-per-call.
Credit Header data obtained from the Credit Bureau in Switzerland. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** Switzerland
* **Service ID:** `che_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$2.00 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `CHE`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `che_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=CHE" \
-F "services=che_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "CHE",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "che_credit_bureau",
"service_name": "Switzerland Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "CHE",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "che_credit_bureau",
"service_name": "Switzerland Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "CHE",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "che_credit_bureau",
"service_name": "Switzerland Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `che_credit_bureau` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
Switzerland Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$2.00 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Switzerland Database Validation overview](/api-reference/database-validation/switzerland)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Thailand National ID
Source: https://docs.didit.me/api-reference/database-validation/thailand/national-id
POST /v3/database-validation/
Verifies input data against government agency sourced National ID. Authoritative real-time identity lookup for Thailand. Real-time lookup, pay-per-call.
Verifies input data against government agency sourced National ID. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** Thailand
* **Service ID:** `tha_national_id`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `national_id` | Yes | `1111111111111` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `national_id`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.35 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `THA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `tha_national_id`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
National identity number for this service.
Example: `1111111111111`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* Thai national ID number: 13 digits whose last digit is the mod-11 check digit (weights 13 to 2 over the first twelve digits).
* `national_id` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `national_id` must be exactly 13 characters long.
* The final Thai national ID check digit (mod 11) is validated; a number that fails the check is refused before the lookup and is not charged.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=THA" \
-F "services=tha_national_id" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F "national_id=1111111111111"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "THA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "tha_national_id",
"service_name": "Thailand National ID",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": true,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "THA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "tha_national_id",
"service_name": "Thailand National ID",
"source_data": {
"address_match_score": "1.000",
"date_of_birth": "1990-01-01",
"identification_number": "SAMPLE-ID-12345",
"name_match_score": "1.000",
"verifications": {
"date_of_birth": true,
"full_name": false,
"identification_number": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "THA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "tha_national_id",
"service_name": "Thailand National ID",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `tha_national_id` currently documents this normalized shape:
* `address_match_score`
* `date_of_birth`
* `identification_number`
* `name_match_score`
## Pricing & SLAs
Thailand National ID queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.35 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Thailand Database Validation overview](/api-reference/database-validation/thailand)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United Kingdom Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/united-kingdom/credit-bureau
POST /v3/database-validation/
Verifies input data against Credit Header data obtained from a Credit Bureau in the United Kingdom. Authoritative real-time identity lookup for United Kingdom. Real-time lookup, pay-per-call.
Verifies input data against Credit Header data obtained from a Credit Bureau in the United Kingdom. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** United Kingdom
* **Service ID:** `gbr_kingdom_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$0.90 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GBR`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `gbr_kingdom_credit_bureau`
Explicit end-user consent for this service.
Example: `true`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GBR" \
-F "services=gbr_kingdom_credit_bureau" \
-F "vendor_data=user-1234" \
-F "consent=true" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GBR",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "gbr_kingdom_credit_bureau",
"service_name": "United Kingdom Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GBR",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "gbr_kingdom_credit_bureau",
"service_name": "United Kingdom Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GBR",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "gbr_kingdom_credit_bureau",
"service_name": "United Kingdom Credit Bureau",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `gbr_kingdom_credit_bureau` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
United Kingdom Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.90 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United Kingdom Database Validation overview](/api-reference/database-validation/united-kingdom)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United Kingdom Financial Services
Source: https://docs.didit.me/api-reference/database-validation/united-kingdom/financial-services
POST /v3/database-validation/
Aggregated service of government and public records, background records, and other services. Authoritative real-time identity lookup for United Kingdom. Real-time lookup, pay-per-call.
Aggregated service of government and public records, background records, and other services. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** United Kingdom
* **Service ID:** `gbr_kingdom_financial_services`
* **Data domain:** Financial
* **Category:** FinancialServices
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | No | `+15550101000` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `phone`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$0.95 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GBR`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `gbr_kingdom_financial_services`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GBR" \
-F "services=gbr_kingdom_financial_services" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GBR",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "gbr_kingdom_financial_services",
"service_name": "United Kingdom Financial Services",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GBR",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "gbr_kingdom_financial_services",
"service_name": "United Kingdom Financial Services",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GBR",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "gbr_kingdom_financial_services",
"service_name": "United Kingdom Financial Services",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `gbr_kingdom_financial_services` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
United Kingdom Financial Services queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.95 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United Kingdom Database Validation overview](/api-reference/database-validation/united-kingdom)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United Kingdom Phone 2
Source: https://docs.didit.me/api-reference/database-validation/united-kingdom/phone-2
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. Authoritative real-time identity lookup for United Kingdom. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 75%
* **Country:** United Kingdom
* **Service ID:** `gbr_kingdom_phone_2`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `phone`
* **Optional inputs:** `date_of_birth`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 75%
* **Price:** \$0.96 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GBR`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `gbr_kingdom_phone_2`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GBR" \
-F "services=gbr_kingdom_phone_2" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GBR",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "gbr_kingdom_phone_2",
"service_name": "United Kingdom Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GBR",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "gbr_kingdom_phone_2",
"service_name": "United Kingdom Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GBR",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "gbr_kingdom_phone_2",
"service_name": "United Kingdom Phone 2",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `gbr_kingdom_phone_2` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
United Kingdom Phone 2 queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.96 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United Kingdom Database Validation overview](/api-reference/database-validation/united-kingdom)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United Kingdom Residential
Source: https://docs.didit.me/api-reference/database-validation/united-kingdom/residential
POST /v3/database-validation/
Aggregated service of government and public records, background records, and public professional profiles. Authoritative real-time identity lookup for United Kingdom. Real-time lookup, pay-per-call.
Aggregated service of government and public records, background records, and public professional profiles. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** United Kingdom
* **Service ID:** `gbr_kingdom_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `date_of_birth` | No | `1990-01-01` |
| `phone` | No | `+15550101000` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `date_of_birth`, `phone`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.25 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `GBR`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `gbr_kingdom_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Phone number in international format.
Example: `+15550101000`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=GBR" \
-F "services=gbr_kingdom_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "GBR",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "gbr_kingdom_residential",
"service_name": "United Kingdom Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "GBR",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "gbr_kingdom_residential",
"service_name": "United Kingdom Residential",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "GBR",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "gbr_kingdom_residential",
"service_name": "United Kingdom Residential",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `gbr_kingdom_residential` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
United Kingdom Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.25 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United Kingdom Database Validation overview](/api-reference/database-validation/united-kingdom)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United States of America - United States Credit Bureau
Source: https://docs.didit.me/api-reference/database-validation/united-states/credit-bureau
POST /v3/database-validation/
Aggregated service of credit header data and other services. Authoritative real-time identity lookup for United States. Real-time lookup, pay-per-call.
Aggregated service of credit header data and other services. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** United States
* **Service ID:** `usa_states_credit_bureau`
* **Data domain:** Financial
* **Category:** CreditBureau
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | No | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `phone` | No | `+15550101000` |
| `ssn` | No | `123456789` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`
* **Optional inputs:** `date_of_birth`, `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `phone`, `ssn`, `email`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.15 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `USA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `usa_states_credit_bureau`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
`ssn` value required by this database service.
Example: `123456789`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* US Social Security Number or ITIN - full 9 digits (dashes optional) or only the last 4 digits
* `ssn` must match `(?:\d{9}|\d{3}-\d{2}-\d{4}|\d{4})`.
* For `ssn`, send either the full 9-digit SSN/ITIN (dashes optional) or only its last 4 digits.
* Beyond the required `first_name` and `last_name`, include at least one more field from `date_of_birth`, `address`, `phone`, `ssn`, or `email`. A name-only request is rejected before the provider lookup (and not charged); `date_of_birth` gives the most reliable match.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=USA" \
-F "services=usa_states_credit_bureau" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "USA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "usa_states_credit_bureau",
"service_name": "United States of America - United States Credit Bureau",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"full_name": true
}
},
"validation": {
"full_name": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "USA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "usa_states_credit_bureau",
"service_name": "United States of America - United States Credit Bureau",
"source_data": {
"full_name": "NO_MATCH"
},
"validation": {
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `usa_states_credit_bureau` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
United States of America - United States Credit Bureau queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United States Database Validation overview](/api-reference/database-validation/united-states)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United States of America - United States Death Check (SSDMF)
Source: https://docs.didit.me/api-reference/database-validation/united-states/death-check
POST /v3/database-validation/
The official Social Security Death Master File. Authoritative real-time identity lookup for United States. Real-time lookup, pay-per-call.
The official Social Security Death Master File. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 100%\*
* **Country:** United States
* **Service ID:** `usa_states_death_check`
* **Data domain:** Other
* **Category:** DeathRecord
## Inputs
| Field | Required | Example |
| --------------- | -------: | ------------ |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `middle_name` | No | `Demo` |
| `date_of_birth` | No | `1990-01-01` |
| `ssn` | No | `123456789` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`
* **Optional inputs:** `middle_name`, `date_of_birth`, `ssn`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 100%\*
* **Price:** \$0.06 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `USA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `usa_states_death_check`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Middle name, when available.
Example: `Demo`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`ssn` value required by this database service.
Example: `123456789`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* US Social Security Number or ITIN - full 9 digits (dashes optional) or only the last 4 digits
* `ssn` must match `(?:\d{9}|\d{3}-\d{2}-\d{4}|\d{4})`.
* For `ssn`, send either the full 9-digit SSN/ITIN (dashes optional) or only its last 4 digits.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=USA" \
-F "services=usa_states_death_check" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "USA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "usa_states_death_check",
"service_name": "United States of America - United States Death Check (SSDMF)",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"verifications": {
"full_name": true
}
},
"validation": {
"full_name": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "USA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "usa_states_death_check",
"service_name": "United States of America - United States Death Check (SSDMF)",
"source_data": {
"full_name": "NO_MATCH"
},
"validation": {
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `usa_states_death_check` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
## Pricing & SLAs
United States of America - United States Death Check (SSDMF) queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.06 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United States Database Validation overview](/api-reference/database-validation/united-states)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United States of America - United States Financial Services
Source: https://docs.didit.me/api-reference/database-validation/united-states/financial-services
POST /v3/database-validation/
Aggregated service of government and public records, background records, and other services. Authoritative real-time identity lookup for United States. Real-time lookup, pay-per-call.
Aggregated service of government and public records, background records, and other services. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 85%
* **Country:** United States
* **Service ID:** `usa_states_financial_services`
* **Data domain:** Financial
* **Category:** FinancialServices
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | No | `123 Sample Street` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `address.postal_code` | No | `10001` |
| `phone` | No | `+15550101000` |
| `ssn` | No | `123456789` |
| `email` | No | `john.doe@example.com` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`
* **Optional inputs:** `address.street_1`, `address.street_2`, `address.city`, `address.region`, `address.postal_code`, `phone`, `ssn`, `email`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 85%
* **Price:** \$0.12 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `USA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `usa_states_financial_services`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
`ssn` value required by this database service.
Example: `123456789`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* `address` is optional for this service. You can send one complete `address` string and Didit will split it into structured address elements when possible, or you can send the structured fields explicitly.
* US Social Security Number or ITIN - full 9 digits (dashes optional) or only the last 4 digits
* `ssn` must match `(?:\d{9}|\d{3}-\d{2}-\d{4}|\d{4})`.
* For `ssn`, send either the full 9-digit SSN/ITIN (dashes optional) or only its last 4 digits.
* This service requires `first_name`, `last_name`, and `date_of_birth`, which already meets the source's minimum of a name plus one more identity field. Optional fields (`ssn`, `address`, `phone`, `email`) further strengthen the match.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=USA" \
-F "services=usa_states_financial_services" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "USA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "usa_states_financial_services",
"service_name": "United States of America - United States Financial Services",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "USA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "usa_states_financial_services",
"service_name": "United States of America - United States Financial Services",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "USA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "usa_states_financial_services",
"service_name": "United States of America - United States Financial Services",
"source_data": {
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `usa_states_financial_services` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
United States of America - United States Financial Services queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.12 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United States Database Validation overview](/api-reference/database-validation/united-states)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United States of America - United States Phone 2
Source: https://docs.didit.me/api-reference/database-validation/united-states/phone-2
POST /v3/database-validation/
Verifies input data to mobile network operators phone data. Authoritative real-time identity lookup for United States. Real-time lookup, pay-per-call.
Verifies input data to mobile network operators phone data. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** > 90%
* **Country:** United States
* **Service ID:** `usa_states_phone_2`
* **Data domain:** Telecom
* **Category:** Telecom
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | Yes | `+15550101000` |
| `date_of_birth` | No | `1990-01-01` |
| `ssn` | No | `123456789` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `address.street_1`, `address.postal_code`, `phone`
* **Optional inputs:** `date_of_birth`, `ssn`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** > 90%
* **Price:** \$0.15 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `USA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `usa_states_phone_2`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
`ssn` value required by this database service.
Example: `123456789`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* US Social Security Number (9 digits, with optional dashes)
* `ssn` must match `(?:\d{9}|\d{3}-\d{2}-\d{4})`.
* For `ssn`, send the full 9-digit SSN/ITIN (dashes optional). Unlike the US Credit Bureau and Financial Services checks, this service does not accept the last-4-digits form.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=USA" \
-F "services=usa_states_phone_2" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}' \
-F "phone=+15550101000"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "USA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "usa_states_phone_2",
"service_name": "United States of America - United States Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "USA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "usa_states_phone_2",
"service_name": "United States of America - United States Phone 2",
"source_data": {
"address": "123 Sample Street",
"date_of_birth": "1990-01-01",
"first_name": "John",
"last_name": "Doe"
},
"validation": {
"address": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "USA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "usa_states_phone_2",
"service_name": "United States of America - United States Phone 2",
"source_data": {
"address": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `usa_states_phone_2` currently documents this normalized shape:
* `address`
* `date_of_birth`
* `first_name`
* `last_name`
## Pricing & SLAs
United States of America - United States Phone 2 queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.15 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United States Database Validation overview](/api-reference/database-validation/united-states)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# United States of America - United States Residential
Source: https://docs.didit.me/api-reference/database-validation/united-states/residential
POST /v3/database-validation/
Aggregated service of government and public records, background records, and public professional profiles. Authoritative real-time identity lookup for United States. Real-time lookup, pay-per-call.
Aggregated service of government and public records, background records, and public professional profiles. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** \~ 90%
* **Country:** United States
* **Service ID:** `usa_states_residential`
* **Data domain:** Address
* **Category:** Residential
## Inputs
| Field | Required | Example |
| --------------------- | -------: | ---------------------- |
| `first_name` | Yes | `John` |
| `last_name` | Yes | `Doe` |
| `date_of_birth` | Yes | `1990-01-01` |
| `address.street_1` | Yes | `123 Sample Street` |
| `address.postal_code` | Yes | `10001` |
| `phone` | No | `+15550101000` |
| `ssn` | No | `123456789` |
| `email` | No | `john.doe@example.com` |
| `address.street_2` | No | `Unit 4` |
| `address.city` | No | `Sample City` |
| `address.region` | No | `Sample State` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `first_name`, `last_name`, `date_of_birth`, `address.street_1`, `address.postal_code`
* **Optional inputs:** `phone`, `ssn`, `email`, `address.street_2`, `address.city`, `address.region`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** \~ 90%
* **Price:** \$0.60 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `USA`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `usa_states_residential`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Structured residential address object. Use `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country`. A complete legacy address string is still accepted but not recommended.
Example: `{"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}`
Phone number in international format.
Example: `+15550101000`
`ssn` value required by this database service.
Example: `123456789`
Email address.
Example: `john.doe@example.com`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* must be at least 2 characters long
* US Social Security Number (9 digits, with optional dashes)
* `ssn` must match `(?:\d{9}|\d{3}-\d{2}-\d{4})`.
* For `ssn`, send the full 9-digit SSN/ITIN (dashes optional). Unlike the US Credit Bureau and Financial Services checks, this service does not accept the last-4-digits form.
* For address services, send structured address fields instead of a single `address` string when possible.
* `address.street_1` is the street address, including street number and street type.
* `address.street_2` is apartment, unit, building, floor, or extra address line. Send it only when you have it.
* `address.city` is city, suburb, district, locality, or neighborhood.
* `address.region` is state, province, region, or town.
* `address.postal_code` is postcode or postal code.
* Address-based database services require at least street address and postal code; requests rejected before source lookup are not charged.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=USA" \
-F "services=usa_states_residential" \
-F "vendor_data=user-1234" \
-F "first_name=John" \
-F "last_name=Doe" \
-F "date_of_birth=1990-01-01" \
-F 'address={"street_1":"123 Sample Street","street_2":"Unit 4","city":"Sample City","region":"Sample State","postal_code":"10001","country":"US"}'
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "USA",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "usa_states_residential",
"service_name": "United States of America - United States Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": true
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "USA",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "usa_states_residential",
"service_name": "United States of America - United States Residential",
"source_data": {
"address": "123 Sample Street",
"address_match_score": "1.000",
"city": "Sample City",
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"last_name": "Doe",
"name_match_score": "1.000",
"postal_code": "10001",
"state": "Sample State",
"street": "sample_value",
"verifications": {
"address": true,
"date_of_birth": true,
"full_name": false
}
},
"validation": {
"address": "full_match",
"date_of_birth": "full_match",
"full_name": "no_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "USA",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "usa_states_residential",
"service_name": "United States of America - United States Residential",
"source_data": {
"address": "NO_MATCH",
"date_of_birth": "NO_MATCH",
"full_name": "NO_MATCH"
},
"validation": {
"address": "no_match",
"date_of_birth": "no_match",
"full_name": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `usa_states_residential` currently documents this normalized shape:
* `address`
* `address_match_score`
* `city`
* `date_of_birth`
* `first_name`
* `full_name`
* `last_name`
* `name_match_score`
* `postal_code`
* `state`
* `street`
## Pricing & SLAs
United States of America - United States Residential queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.60 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [United States Database Validation overview](/api-reference/database-validation/united-states)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Uruguay - CI verification
Source: https://docs.didit.me/api-reference/database-validation/uruguay/cedula
POST /v3/database-validation/
Verifies Uruguayan Cédula data against Dirección Nacional del Registro de Estado Civil government records. Authoritative real-time identity lookup for Uruguay. Real-time lookup, pay-per-call.
Verifies Uruguayan Cédula data against Dirección Nacional del Registro de Estado Civil government records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Uruguay
* **Service ID:** `ury_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------ |
| `personal_number` | Yes | `1111111` |
| `date_of_birth` | Yes | `1990-01-01` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `personal_number`, `date_of_birth`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `URY`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ury_cedula`
Country-specific personal identity number.
Example: `1111111`
Date of birth in `YYYY-MM-DD` format.
Example: `1990-01-01`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Uruguayan CI (7-9 digits)
* `personal_number` must contain digits only; remove spaces, hyphens, and punctuation before sending the request.
* `personal_number` must be 7-9 characters long.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=URY" \
-F "services=ury_cedula" \
-F "vendor_data=user-1234" \
-F "personal_number=1111111" \
-F "date_of_birth=1990-01-01"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "URY",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ury_cedula",
"service_name": "Uruguay - CI verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "full_match",
"identification_number": "full_match"
}
}
]
}
```
**`PARTIAL_MATCH`** — The identification number was found but one or more personal fields (usually name or date of birth) did not fully match.
```json 200 OK — PARTIAL_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "In Review",
"issuing_state": "URY",
"match_type": "partial_match",
"validations": [
{
"outcome_code": "PARTIAL_MATCH",
"service_id": "ury_cedula",
"service_name": "Uruguay - CI verification",
"source_data": {
"date_of_birth": "1990-01-01",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "URY",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ury_cedula",
"service_name": "Uruguay - CI verification",
"source_data": {
"date_of_birth": "NO_MATCH",
"identification_number": "NO_MATCH"
},
"validation": {
"date_of_birth": "no_match",
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ury_cedula` currently documents this normalized shape:
* `date_of_birth`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Uruguay - CI verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Uruguay Database Validation overview](/api-reference/database-validation/uruguay)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Venezuela - Cédula verification
Source: https://docs.didit.me/api-reference/database-validation/venezuela/cedula
POST /v3/database-validation/
Verifies Venezuelan Cédula data against CNE electoral registry records. Authoritative real-time identity lookup for Venezuela. Real-time lookup, pay-per-call.
Verifies Venezuelan Cédula data against CNE electoral registry records. Didit exposes this service through `POST /v3/database-validation/` so you can verify the submitted data against the authoritative source and receive normalized match results.
## Coverage
* **Coverage:** 90-95%
* **Country:** Venezuela
* **Service ID:** `ven_cedula`
* **Data domain:** Identity
* **Category:** NationalIDRegistry
## Inputs
| Field | Required | Example |
| ----------------- | -------: | ------------------ |
| `document_number` | Yes | `SAMPLE-DOC-12345` |
| `first_name` | No | `John` |
| `last_name` | No | `Doe` |
| `vendor_data` | No | `user-1234` |
* **Required inputs:** `document_number`
* **Optional inputs:** `first_name`, `last_name`, `vendor_data`
* **Consent:** Not required
* **Workflow availability:** Available in workflow
* **Coverage:** 90-95%
* **Price:** \$0.20 per successful query
## Body parameters
ISO 3166-1 alpha-3 country code for this database service.
Example: `VEN`
Array containing this service ID. Pinning the service keeps the request scoped to this exact database.
Example: `ven_cedula`
Document number extracted from or provided by the user.
Example: `SAMPLE-DOC-12345`
Given name to validate.
Example: `John`
Family name to validate.
Example: `Doe`
Your stable user reference for this person, such as your internal user ID. Didit uses it to link standalone checks to the same end user and reduce duplicate-detection noise.
Example: `user-1234`
## Input rules & validation notes
* Venezuelan Cédula (7-10 digits, optional V/E prefix)
* `document_number` must be 7-12 characters long.
* `document_number` must match `[VvEe]?-?\d{7,10}`.
## How to call it
```bash cURL theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=VEN" \
-F "services=ven_cedula" \
-F "vendor_data=user-1234" \
-F "document_number=SAMPLE-DOC-12345"
```
Every successful call returns HTTP `200`. The **`outcome_code`** field tells you what actually happened — distinguishing, for example, a real biometric mismatch (`BIOMETRIC_NO_MATCH`) from a selfie that could not be read (`BIOMETRIC_IMAGE_UNUSABLE`). The `status` shown is the default feature status; your configured [Partial Match / No Match actions](/core-technology/database-validation/database-validation-warnings) can override it.
**`MATCH`** — The registry confirmed the identity and every checked field matched.
```json 200 OK — MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Approved",
"issuing_state": "VEN",
"match_type": "full_match",
"validations": [
{
"outcome_code": "MATCH",
"service_id": "ven_cedula",
"service_name": "Venezuela - Cédula verification",
"source_data": {
"document_type": "sample_value",
"first_name": "John",
"full_name": "John Doe",
"identification_number": "SAMPLE-ID-12345",
"last_name": "Doe"
},
"validation": {
"identification_number": "full_match"
}
}
]
}
```
**`NO_MATCH`** — The registry returned no match for the submitted data.
```json 200 OK — NO_MATCH theme={null}
{
"request_id": "req_01H…",
"status": "Declined",
"issuing_state": "VEN",
"match_type": "no_match",
"validations": [
{
"outcome_code": "NO_MATCH",
"service_id": "ven_cedula",
"service_name": "Venezuela - Cédula verification",
"source_data": {
"identification_number": "NO_MATCH"
},
"validation": {
"identification_number": "no_match"
}
}
]
}
```
## Returned data
The exact fields surfaced in `source_data` depend on what the registry returns. The generated example for `ven_cedula` currently documents this normalized shape:
* `document_type`
* `first_name`
* `full_name`
* `identification_number`
* `last_name`
## Pricing & SLAs
Venezuela - Cédula verification queries are billed only when Didit receives a conclusive result from the validation source.
* **Per-call price:** \$0.20 USD.
* **Billing:** per successful query. You are not charged when the registry is unreachable, when required fields are missing, or when the request is rejected before reaching the source.
* **Latency:** typical p95 \< 2 s.
* **Availability:** 99.9% per quarter on Didit's side; downstream source availability varies by country and dataset.
## Continue reading
* [Venezuela Database Validation overview](/api-reference/database-validation/venezuela)
* [Database Validation overview](/core-technology/database-validation/overview)
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes)
# Healthcheck
Source: https://docs.didit.me/api-reference/healthcheck
openapi-healthcheck.json GET /system/healthcheck
Public unauthenticated liveness probe. Returns `HTTP 200` with `{status: "ok", timestamp}` when service, DB, cache, and broker are healthy.
# API Reference
Source: https://docs.didit.me/api-reference/overview
Complete REST API reference for Didit identity verification. Sessions, standalone endpoints, management API, and OpenAPI spec. Pay-per-use pricing.
Didit offers **two complementary approaches** to identity verification. Understanding the difference is essential for choosing the right integration for your use case.
***
## Hosted Sessions (Recommended for User-Facing Flows)
Hosted sessions are the **recommended approach** for most integrations. When you create a session, Didit generates a `verification_url` that you present to your user. The user completes the entire verification flow in Didit's optimized interface, and you receive the results via webhook or API.
### How It Works
Your server calls `POST /v3/session/` with a workflow ID. Didit returns a `verification_url` and `session_token`.
Redirect the user, embed via iframe, or initialize a native SDK using the session token.
The user follows the guided flow in Didit's optimized UI — ID capture, liveness, and any additional steps.
Didit sends a webhook to your server with the verification decision, or you poll via the retrieve API.
### Why Sessions Are Recommended
| Advantage | Details |
| --------------------------- | ----------------------------------------------------------------------------------------------- |
| **Optimized UX** | The verification interface is continuously A/B tested globally for the highest completion rates |
| **Camera handling** | Smart camera selection, auto-capture, quality checks, and device compatibility built in |
| **Guided flow** | Step-by-step instructions, real-time feedback, and error recovery for the user |
| **Multi-feature workflows** | Chain multiple features (ID + Liveness + AML + Phone + Email) in a single flow |
| **Security** | Anti-spoofing, active liveness detection, and device fingerprinting embedded in the flow |
| **Compliance** | Session-level audit trails, consent capture, and data retention policies handled automatically |
| **Localization** | 49 languages with automatic browser detection |
| **Mobile-native** | Native SDKs for iOS, Android, React Native, and Flutter |
| **No maintenance** | Didit handles all UI updates, browser/device bugs, and camera edge cases |
### When to Use Sessions
* **User onboarding / KYC** — New users sign up and need to verify their identity
* **Age verification** — Confirm users meet minimum age requirements
* **Re-verification** — Periodically re-verify existing users
* **Biometric authentication** — Users authenticate with their face for sensitive actions
* **Multi-step compliance** — Flows requiring ID + Liveness + AML + Phone in one go
* **Any scenario where the end user is present** and interacts directly
### Session Endpoints
| Endpoint | Purpose |
| --------------------------------------------------- | ------------------------------------------------------ |
| [Create Session](/sessions-api/create-session) | Generate a verification URL for a user |
| [Retrieve Session](/sessions-api/retrieve-session) | Get the full results and decision for a session |
| [List Sessions](/sessions-api/list-sessions) | Query and filter all sessions |
| [Delete Session](/sessions-api/delete-session) | Remove a session and all associated data |
| [Generate PDF](/sessions-api/generate-pdf) | Export a verification report as PDF |
| [Share Session](/sessions-api/share-session/share) | Generate a share token for Reusable KYC with a partner |
| [Import Shared](/sessions-api/share-session/import) | Import a session shared by a partner (Reusable KYC) |
***
## Standalone APIs (For Server-to-Server Processing)
Standalone APIs let you call individual verification features **directly from your server** without any end-user UI. You send the data (images, documents, names) and receive structured results.
### How It Works
Your server calls a standalone endpoint (e.g., `POST /v3/id-verification/`) with the required data — images, documents, or JSON fields.
Didit processes the request synchronously and returns structured verification results in the response.
### When to Use Standalone APIs
| Use Case | Example |
| ------------------------------------- | --------------------------------------------------------------------------------- |
| **Backend processing** | You already have document images from your own capture UI and need to verify them |
| **Batch verification** | Processing bulk identity documents uploaded by an operations team |
| **Custom UX** | You've built your own capture flow and want to use Didit only for the analysis |
| **Server-to-server checks** | AML screening a list of customers, without any user interaction |
| **Automated pipelines** | CI/CD or cron-based compliance re-checks (e.g., daily AML monitoring) |
| **Integration with existing systems** | Connecting Didit's verification to your existing document management system |
| **Single-feature calls** | You only need one feature (e.g., just AML screening or just face matching) |
### Available Standalone APIs
**Identity & Documents**
| API | What It Does |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| [ID Verification](/standalone-apis/id-verification) | Extract and validate data from ID documents (front + back images) |
| [Proof of Address](/standalone-apis/proof-of-address) | Verify address documents (utility bills, bank statements, government docs) |
| [Database Validation](/core-technology/database-validation/overview) | Cross-check user data against government databases |
**Document AI**
| API | What It Does |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| [Document AI](/standalone-apis/document-ai) | Extract the fields you define from any document (PDF or image), with name matching and tampering checks |
**Biometrics & Face**
| API | What It Does |
| ----------------------------------------------------- | ------------------------------------------------------------------- |
| [Passive Liveness](/standalone-apis/passive-liveness) | Verify a real person is present (anti-spoofing) from a single image |
| [Face Match](/standalone-apis/face-match) | Compare two faces to determine if they are the same person |
| [Face Search](/standalone-apis/face-search) | Search for a face across all previously verified sessions (1:N) |
| [Age Estimation](/standalone-apis/age-estimation) | Estimate a person's age from a facial image |
**Compliance & Risk**
| API | What It Does |
| ----------------------------------------------- | ------------------------------------------------------------------ |
| [AML Screening](/standalone-apis/aml-screening) | Screen persons or companies against sanctions, PEP, and watchlists |
**Business Verification (KYB)**
| API | What It Does |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [KYB Registry Search](/standalone-apis/kyb-registry-search) | Search company registries by name, registration number, and country; up to \$0.50 per successful request |
| [KYB Registry Select](/standalone-apis/kyb-registry-select) | Retrieve the full registry profile (officers, beneficial owners, addresses) for a selected company |
***
## Side-by-Side Comparison
| | Hosted Sessions | Standalone APIs |
| ----------------------- | ------------------------------------- | ----------------------------------------------- |
| **Integration effort** | Low — redirect, iframe, or SDK | Medium — you handle data capture and submission |
| **User interaction** | Yes — user completes flow in Didit UI | No — server-to-server only |
| **Camera/capture** | Handled by Didit (optimized) | You provide the images/data |
| **Multi-feature flows** | Yes — chain features in a workflow | One feature per API call |
| **UX optimization** | Continuously A/B tested | You control the UX |
| **Localization** | 49 languages, auto-detected | N/A (no UI) |
| **Webhooks** | Automatic status notifications | N/A (synchronous response) |
| **Response time** | Async (user completes at their pace) | Synchronous (seconds) |
| **Best for** | User-facing onboarding & verification | Backend processing & automation |
| **Console visibility** | Full session lifecycle in dashboard | Appears in Manual Checks section |
***
## Can I Use Both?
**Yes.** Many teams combine both approaches:
1. **Sessions for onboarding** — New users go through the full hosted verification flow during sign-up
2. **Standalone AML for ongoing monitoring** — A cron job re-screens all approved users nightly against updated watchlists
3. **Standalone Face Search for duplicate detection** — When a new session is approved, your server calls Face Search to check for duplicates
4. **Standalone ID Verification for operations** — Your compliance team uploads documents manually for edge-case reviews
### Example: Combined Architecture
| Your Application | Didit API | Type |
| ---------------------------- | ------------------------------ | ---------------- |
| **User sign-up flow** | Sessions API (hosted UI) | User-facing |
| **Nightly AML re-screening** | Standalone AML API | Server-to-server |
| **Ops team document review** | Standalone ID Verification API | Server-to-server |
| **Duplicate face detection** | Standalone Face Search API | Server-to-server |
***
## Getting Started
***
# Create Application
Source: https://docs.didit.me/auth-api/create-application
POST /organizations/me/{org_id}/applications/
Create an application inside an organization. The response includes `api_key`; persist it now (recoverable via GET). Requires owner/admin JWT.
Use this endpoint when you need to separate verification traffic, credentials, branding, or reporting inside the same organization.
This is especially useful for **resellers** that create one application per customer. It also works well when your own company has multiple products, brands, regions, staging/production environments, or use cases that should not share the same API key and application-level settings.
All Auth API endpoints use `https://apx.didit.me/auth/v2`. Use the returned `api_key` as `x-api-key` when calling `https://verification.didit.me/v3/...` endpoints such as sessions and workflows.
# Get Application Credentials
Source: https://docs.didit.me/auth-api/get-credentials
GET /organizations/me/{org_id}/applications/{app_id}/
Read one application's full record including `client_id` and `api_key`. Use this to recover a lost `api_key`.
# Programmatic Login
Source: https://docs.didit.me/auth-api/login
POST /programmatic/login/
Authenticate any verified email/password Didit account and receive an RS256 JWT pair (default lifetime 86400s). Console-created email/password accounts can use this endpoint; OAuth-only console accounts must set a password first.
# Register Account
Source: https://docs.didit.me/auth-api/register
POST /programmatic/register/
Step 1 of programmatic onboarding. Creates a pending account and emails a 6-char code (10 min TTL). Rate limit: 5/IP/hour.
# Update Application
Source: https://docs.didit.me/auth-api/update-application
PATCH /organizations/me/{org_id}/applications/{app_id}/
Update application metadata (name, URLs, redirect URIs). `api_key` and `client_id` are never rotated. Requires owner/admin JWT.
Use this endpoint to keep each application aligned with the customer, brand, environment, or internal use case it represents.
For resellers, this lets you rename or update each customer application without sharing credentials across customers. For direct customers, it helps separate products, regions, or environments while keeping everything under one organization.
This endpoint updates application metadata. The `api_key` stays the same unless you rotate credentials separately.
# Verify Email & Get Credentials
Source: https://docs.didit.me/auth-api/verify-email
POST /programmatic/verify-email/
Step 2 of onboarding. Exchanges the 6-char code for a verified account, a default org+app, and a JWT pair. Persist the returned `api_key`.
# Business AML Screening
Source: https://docs.didit.me/business-verification/aml
Screen companies, UBOs, and officers against global sanctions, PEP, and watchlist databases. Entity AML included; $0.20 per person. No contracts.
Business verification includes AML screening at two levels: the **company entity** and all **identified individuals** (beneficial owners and officers). This ensures comprehensive compliance coverage.
The same two-score system used in user verification applies — see [AML screening overview](/core-technology/aml-screening/overview), [risk score](/core-technology/aml-screening/aml-risk-score), and [match score](/core-technology/aml-screening/aml-match-score) for the underlying model.
## Company-level AML
The company itself is screened against global watchlists as a legal entity:
| Check | Description |
| -------------------------- | --------------------------------------------------------------------------------- |
| **Sanctions** | International and national sanctions lists (OFAC, EU, UN, etc.) |
| **Regulatory enforcement** | Regulatory warnings, fines, and enforcement actions |
| **Adverse media** | News articles about financial crime, fraud, or legal issues involving the company |
Company AML uses the same [two-score risk system](/core-technology/aml-screening/overview) as user verification:
* **Match score** — how confident the system is that the match refers to the same entity
* **Risk score** — how risky the matched entity is based on the category, country, and severity
Company-level screening produces:
| Field | Description |
| ------------------------- | ---------------------------------------------- |
| `company_aml_score` | Overall AML risk score for the company (0–100) |
| `company_aml_total_hits` | Total number of watchlist matches found |
| `company_aml_screened_at` | Timestamp of the most recent screening |
## Person-level AML
Every identified beneficial owner and officer is individually screened against:
| Check | Description |
| --------------------- | -------------------------------------------------------------------------------------- |
| **Sanctions** | Individual sanctions across all major lists |
| **PEP** | Politically Exposed Persons at all levels (heads of state through to close associates) |
| **Criminal records** | Global and local criminal databases |
| **Adverse media** | News articles mentioning the individual in connection with financial crime |
| **Fitness & probity** | Financial regulator fitness and probity registers |
## Screening flow
Company name, registration number, country, and all identified persons are extracted from the registry lookup.
The company entity is screened against corporate sanctions, regulatory enforcement, and adverse media databases.
Each beneficial owner and officer is screened individually against PEP, sanctions, criminal, and adverse media databases.
Each match receives a match score and risk score. Matches below the match score threshold are classified as false positives. Remaining matches determine the AML status.
The business session's overall AML status reflects the highest-risk result across the company and all screened individuals.
## Configuration
AML screening for business verification uses the same configurable thresholds as user verification:
| Setting | Description |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| **Match score threshold** | Below this threshold, matches are auto-classified as false positives |
| **Approve threshold** | Risk scores below this value result in an Approved AML status |
| **Review threshold** | Risk scores above this value result in a Declined AML status; between thresholds is In Review |
These thresholds can be set per workflow in the Business Console.
## Ongoing monitoring
If ongoing AML monitoring is enabled, all screened entities (company and individuals) are re-screened daily against updated watchlists. Any new matches trigger a webhook notification so your compliance team can take action.
### Enabling ongoing monitoring
Toggle ongoing monitoring per business session from the [Business Console](/business-verification/console) or via the API:
| Setting | Description |
| ------------------------------- | ------------------------------------------------------ |
| `is_ongoing_monitoring_enabled` | Enable or disable daily re-screening for this business |
| `last_aml_bill_date` | Timestamp of the most recent billed screening cycle |
When enabled, Didit re-screens the company entity and all identified individuals against updated watchlists. New or changed matches generate a `data.updated` webhook event with `session_kind: "business"` (or `business.data.updated` when the entity profile changes).
Ongoing monitoring incurs a per-screening fee for each monitoring cycle. Review your billing settings to understand the cost impact before enabling monitoring across all business sessions.
## Next steps
Two-score system, watchlists, thresholds.
Sanctions, watchlists, and insolvency sources used at the company level.
Ongoing rescans and webhook events.
# AML Coverage
Source: https://docs.didit.me/business-verification/aml-coverage
Global sanctions, regulatory watchlists, and insolvency registers powering Didit's KYB AML screening across 220+ countries. Pay-per-call, no contracts.
Didit's business AML screening leverages a curated set of global publishers tailored to **legal entities**: international sanctions regimes, national regulatory watchlists, and insolvency / bankruptcy registers. Company-level matches surface alongside person-level matches for beneficial owners and officers — see the [AML screening page](/business-verification/aml) for how the two layers combine.
### 1. **Sanctions Screening**
We maintain up‑to‑date coverage of all major **multilateral sanctions, debarment, and exclusion lists** published by international organizations and development banks, including:
* **United Nations Security Council** consolidated list
* **European Union** consolidated financial sanctions and travel-ban lists
* **Multilateral development banks** (World Bank, ADB, AIIB, IDB, AfDB, EBRD, EIB, CDB) debarment / ineligibility registers
* **Specialized international watchlists** (IOSCO investor alerts, EU Air Safety List, Uyghur Human Rights Project)
### 2. **Regulatory & Enforcement Watchlists**
We screen against **government and regulatory watchlists** published by national authorities responsible for financial markets, trade, transport, energy, and consumer protection. Coverage spans agencies in the United States, Canada, Colombia, and Brazil — including securities regulators, central banks, antitrust authorities, customs and trade bodies, and procurement registries.
### 3. **Insolvency & Bankruptcy Registers**
We flag companies listed in **official insolvency, bankruptcy, and liquidation registers** across multiple jurisdictions, including India (IBBI), China's National Enterprise Bankruptcy Information Disclosure Platform, U.S. federal and state insurance guaranty associations, the FDIC, German insolvency announcements, the Polish court-business monitor, and similar registers in Australia, Chile, Indonesia, Brazil, and Belgium.
***
### Granular Taxonomy & Structured Metadata
Every match is enriched with structured metadata to aid remediation and risk prioritization:
* **Categorization**: primary categories (Sanctions, Watchlists, Insolvency) and publisher-level subcategories
* **Identifiers**: company name, registration number, jurisdiction
* **Other fields**: aliases, addresses, listing date, source URL
This ensures easy filtering and supports detailed differential risk workflows.
***
## Summary Table
| **Risk Category** | **Key Coverage** |
| ------------------------- | ------------------------------------------------------------------------------------------------- |
| **Sanctions** | UN, EU, OFAC, World Bank, ADB, AIIB, IDB, AfDB, EBRD, EIB, IOSCO, EU Air Safety List |
| **Regulatory Watchlists** | Securities regulators, central banks, trade authorities, antitrust bodies, procurement registries |
| **Insolvency Registers** | National bankruptcy boards, deposit insurance corporations, court insolvency monitors |
***
### Sanctions
Below is a table of the international sanctions lists included in our business AML screening process:
| Publisher | Country | Category |
| ------------------------------------------------------------------------------- | ---------------- | --------- |
| Anti-Corruption Foundation (ACF International) — Sanctions Tracker | 🌐 International | Sanctions |
| Solomon Islands Government-Ministry of Commerce, Industry, Labour & Immigration | 🌐 International | Sanctions |
| European Investment Bank - Exclusions | 🌐 International | Sanctions |
| Caribbean Development Bank - Sanctions | 🌐 International | Sanctions |
| European National Bank - Supervisory Sanctions | 🌐 International | Sanctions |
| Uyghur Human Rights Project - Sanctions | 🌐 International | Sanctions |
| EU Sanctions - Consolidated List of Travel Bans | 🌐 International | Sanctions |
| EU Consolidated Sanctions | 🌐 International | Sanctions |
| Inter-American Development Bank (IDB) - Sanctioned Firms | 🌐 International | Sanctions |
| Asian Infrastructure Investment Bank - Debarment List | 🌐 International | Sanctions |
| United Nations Security Council - Consolidated List | 🌐 International | Sanctions |
| European Commission - The EU Air Safety List | 🌐 International | Sanctions |
| Asian Development bank - Sanction List | 🌐 International | Sanctions |
| European Bank for Reconstruction and Development - Ineligible Entities | 🌐 International | Sanctions |
| African Development Bank - Debarred Entities | 🌐 International | Sanctions |
| European Union - Consolidated List of EU Financial Sanctions | 🌐 International | Sanctions |
| International Organization of Securities Commission - Investor Alerts Portal | 🌐 International | Sanctions |
| The World Bank - Debarred Firms and Individuals | 🌐 International | Sanctions |
18 sources across 1 jurisdiction
### Regulatory Watchlists
Below is a table of the regulatory and enforcement watchlists included in our business AML screening process:
| Publisher | Country | Category |
| ------------------------------------------------------------------ | ------------------ | ---------- |
| Office of Foreign Assets Control | 🇺🇸 United States | Watchlists |
| Federal Trade Commission | 🇺🇸 United States | Watchlists |
| International Trade Administration, U.S. Department of Commerce | 🇺🇸 United States | Watchlists |
| U.S Securities and Exchange Commission | 🇺🇸 United States | Watchlists |
| Federal Reserve Bank | 🇺🇸 United States | Watchlists |
| Food and Drug Administration | 🇺🇸 United States | Watchlists |
| Federal Energy Regulatory Commission | 🇺🇸 United States | Watchlists |
| Transport Canada | 🇨🇦 Canada | Watchlists |
| Ontario Securities Commission | 🇨🇦 Canada | Watchlists |
| BC Securities Commission | 🇨🇦 Canada | Watchlists |
| Government of Canada | 🇨🇦 Canada | Watchlists |
| Canadian Transportation Agency | 🇨🇦 Canada | Watchlists |
| Canada Energy Regulator | 🇨🇦 Canada | Watchlists |
| Superintendency of Industry and Commerce | 🇨🇴 Colombia | Watchlists |
| National Directorate of Taxes and Customs | 🇨🇴 Colombia | Watchlists |
| Office of the Attorney General; Republic of Colombia | 🇨🇴 Colombia | Watchlists |
| Superintendency of the Solidarity Economy | 🇨🇴 Colombia | Watchlists |
| Superintendence of Ports and Transport | 🇨🇴 Colombia | Watchlists |
| Judicial Branch; Superior Council of the Judiciary | 🇨🇴 Colombia | Watchlists |
| Comptroller General of Santander | 🇨🇴 Colombia | Watchlists |
| Secop Electronic Public Procurement System | 🇨🇴 Colombia | Watchlists |
| Ministry of Justice and Public Security | 🇧🇷 Brazil | Watchlists |
| National Electric Energy Agency | 🇧🇷 Brazil | Watchlists |
| National Civil Aviation Agency | 🇧🇷 Brazil | Watchlists |
| Administrative Council for Economic Defense | 🇧🇷 Brazil | Watchlists |
| Brazilian Institute of Environment and Renewable Natural Resources | 🇧🇷 Brazil | Watchlists |
| Securities and Exchange Commission of Brazil | 🇧🇷 Brazil | Watchlists |
| Federal Government of Brazil | 🇧🇷 Brazil | Watchlists |
| Public Ministry of the State of Minas Gerais | 🇧🇷 Brazil | Watchlists |
| Labor Public Ministry | 🇧🇷 Brazil | Watchlists |
30 sources across 4 countries
### Insolvency Registers
Below is a table of the insolvency, bankruptcy, and liquidation registers included in our business AML screening process:
| Publisher | Country | Category |
| ------------------------------------------------------------------------ | ------------------ | ---------- |
| Insolvency and Bankruptcy Board of India (IBBI) | 🇮🇳 India | Insolvency |
| China National Enterprise Bankruptcy Information Disclosure Platform | 🇨🇳 China | Insolvency |
| U.S Securities and Exchange Commission | 🇺🇸 United States | Insolvency |
| Bankrupt Companies in the United States | 🇺🇸 United States | Insolvency |
| National Organization of Life and Health Insurance Guaranty Associations | 🇺🇸 United States | Insolvency |
| Arkansas Life and Health Insurance Guaranty Association | 🇺🇸 United States | Insolvency |
| Florida Office of Insurance Regulation | 🇺🇸 United States | Insolvency |
| Florida Insurance Guaranty Association | 🇺🇸 United States | Insolvency |
| Des Moines Register | 🇺🇸 United States | Insolvency |
| New York Liquidation Bureau (NYLB) | 🇺🇸 United States | Insolvency |
| North Carolina Insurance Guaranty Association | 🇺🇸 United States | Insolvency |
| Federal Deposit Insurance Corporation (FDIC) | 🇺🇸 United States | Insolvency |
| Indonesia Deposit Insurance Corporation - Bank Liquidation | 🇮🇩 Indonesia | Insolvency |
| Central Bank - Liquidated Companies | 🇧🇷 Brazil | Insolvency |
| German Insolvency Register - insolvenzbekanntmachungen | 🇩🇪 Germany | Insolvency |
| BORSE-Frankfurt | 🇩🇪 Germany | Insolvency |
| Insolvenz Radar | 🇩🇪 Germany | Insolvency |
| Internetowy Monitor Sądowy Gospodarczy | 🇵🇱 Poland | Insolvency |
| Australian Financial Security Authority - Bankruptcy Register | 🇦🇺 Australia | Insolvency |
| Chile The Superintendency of Insolvency and Re-entrepreneurship | 🇨🇱 Chile | Insolvency |
| American Bankruptcy Institute Global Insolvency | 🇧🇪 Belgium | Insolvency |
21 sources across 9 countries
***
## Next steps
How company and person-level AML are combined.
Full person-level watchlist coverage reference.
Ongoing rescans and webhook events.
# Business Profiles
Source: https://docs.didit.me/business-verification/business-profiles
Track verified companies across sessions with auto-created profiles, status management, tags, bulk ops, and CSV import/export. From $2.00 per check, priced per country and tier.
Didit automatically creates a business profile for each company you verify, aggregating verification data and session history into a single entity. Business profiles provide a unified view of each company across all their verification sessions.
Business profiles are the KYB-specific surface of the more general **Business entity** model. For the full entity model — operations, key-people linking to Users, blocklist mechanics, import/export — see [Businesses overview](/entities/businesses/overview).
## What is a business profile
A business profile is created automatically when a [business session](/business-verification/overview) is submitted with a `vendor_data` identifier. If a profile with that identifier already exists, the new session is linked to it.
| Field | Description |
| ----------------------- | -------------------------------------------------------------- |
| **Vendor data** | Your unique identifier for this business (e.g., `company-123`) |
| **Display name** | Display name shown in the console |
| **Legal name** | Official legal name from registry or manual entry |
| **Registration number** | Company registration or incorporation number |
| **Country** | Country of incorporation (ISO 3166-1 alpha-2) |
## Profile statuses
| Status | Description |
| --------- | -------------------------------------------------------------- |
| `ACTIVE` | Business is in good standing — no issues detected |
| `FLAGGED` | Business requires attention — review results or pending checks |
| `BLOCKED` | Business has been blocked from further activity |
Statuses can be changed manually from the console, in bulk, or automatically based on verification results.
## Session metrics
Each profile tracks verification metrics across all linked sessions:
| Metric | Description |
| ------------------ | ---------------------------------------------------------------------- |
| **Total sessions** | Number of verification sessions for this business |
| **Approved** | Sessions that passed all checks |
| **Declined** | Sessions that failed one or more critical checks |
| **In review** | Sessions awaiting analyst review |
| **Last session** | Timestamp of the most recent verification |
| **First session** | Timestamp of the earliest verification |
| **Last activity** | Timestamp of any recent activity (status change, session update, etc.) |
## Tags
Apply tags to business profiles for categorisation and filtering. Tags are application-defined — create custom tags that fit your workflow (e.g., `high-risk`, `vip-client`, `re-verification-needed`).
## Bulk operations
Manage multiple business profiles at once:
| Operation | Description |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **Bulk status update** | Change the status of selected profiles (or all profiles matching current filters) |
| **CSV export** | Export profile data to CSV with configurable column selection |
| **CSV import** | Import business profiles from a CSV file to pre-create profiles before verification |
| **Bulk delete** | Remove selected profiles and their associated data |
### CSV import
To import business profiles in bulk:
1. Download the **import template** from the console to see the expected column format
2. Populate the template with your business data
3. Upload the file — Didit creates an import job and processes rows asynchronously
After completion, review the import summary for created, updated, and failed row counts. Failed rows include specific error details.
## Reviews and comments
Add internal review notes to any business profile. Comments are visible to all team members with access and form part of the audit trail:
* Add notes with free-text content
* Mention team members by email to notify them
* View the full comment history with author and timestamp
## Next steps
How business verification sessions work end to end.
Managing business profiles and sessions in the Business Console.
Business management API endpoints.
# Company Data
Source: https://docs.didit.me/business-verification/company-data
Automated registry lookups, company status validation, and data extraction across 220+ countries. From $2.00 per check, priced per country and tier, no contracts.
When a business verification session starts, Didit queries official company registries to retrieve and validate company information. This automated lookup provides the foundation for the entire business verification process.
## Registry lookup
Didit connects to company registries in supported countries to retrieve official data. In the hosted flow the user selects the country of incorporation and fills in the company name, the registration number, or both — at least one is required. Registration numbers are normalized where registries require it (for example, UK company numbers are zero-padded to eight digits), and when both identifiers are provided the registry lookup combines them.
### Pre-filling the search with `expected_details`
If you already know the company, pass `expected_details` (`company_name`, `registry_country`, `registration_number`) when [creating the session](/sessions-api/create-session). The hosted flow pre-selects and locks the country (and the state for state-level registries like `US-CA`), pre-fills the matching search fields, and runs the asserted search automatically — the user lands directly on their company's result. Both fields stay editable so the user can correct a wrong assertion. Prefill only: selecting a different company than asserted does not produce a warning.
### Search types
Registry searches support two modes:
| Mode | Description |
| ------------ | --------------------------------------------------------------------------------------------------- |
| **Exact** | Matches the company name or registration number precisely |
| **Contains** | Matches companies whose name contains the search term — useful when the exact legal name is unknown |
### Search workflow
The registry lookup follows an asynchronous workflow:
Didit queries the registry with the selected country and the identifiers provided — company name, registration number, or both combined. The response's `searched_by` field reports how results were matched (`name`, `registration_number`, or `registration_number_and_name`), and the hosted UI offers a one-tap "Search as company name instead" override on registration-number results. The search runs asynchronously.
Poll the search status until results are resolved. Large registries may take a few seconds to return results.
Review the returned companies and select the correct match. Didit links the selected company to the KYB check and populates all available data.
If the company is not found in the registry, submit company data manually. The session continues with the provided data, and `is_from_registry` is set to `false`.
### Retrieved data
| Field | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `company_name` | Legal name as registered with the authority |
| `registration_number` | Official company registration or incorporation number |
| `country_code` | Country of incorporation (ISO 3166-1 alpha-3) |
| `company_type` | Legal entity type (Ltd, LLC, PLC, SA, GmbH, SL, etc.) |
| `registry_status` | Current company status from the registry (`active`, `dissolved`, `struck_off`, etc.) |
| `incorporation_date` | Date the company was originally registered |
| `registered_address` | Official registered office address |
| `tax_number` | Tax identification number where available |
| `alternative_names` | Trade names, dbas, former legal names, or original-language names as a text value |
| `legal_entity_identifier` | LEI — for financial institutions and listed entities |
| `nature_of_business` | Short description of the business (in the registry's language) |
| `registered_capital` | Authorized / issued capital as an `{amount, currency}` object |
| `location_of_registration` | City / subdivision of the registry of record |
| `control_scheme` | Control / governance scheme where the registry exposes it |
| `website`, `email`, `phone` | Contact details on file with the registry |
| `addresses[]` | Additional registered or trading addresses |
| `industries[]` | Industry classification codes (SIC, NACE, or local equivalents) |
| `accounts` | Filing dates when reported, including `last_annual_return_date` and `last_agm_date`. Annual returns and meetings are distinct from financial accounts. |
| `financial_summary` | Summary financial data where the registry publishes it (revenue, employees, filing status) |
| `confirmed_by_user_at` | Timestamp when the end user confirmed or edited the registry data |
| `last_console_edit_at` | Timestamp of the last console-side edit by an analyst |
| `is_editable` | `true` while registry-sourced data can still be edited by the end user in the hosted flow |
| `is_from_registry` | `true` for registry-sourced companies, `false` for manual entry |
| `fetch_status` | `pending`, `resolved`, or `failed` — registry lookup state |
| `verification_status` | `verified`, `failed`, or `unknown` |
### Company statuses
| Status | Description |
| ------------------ | ---------------------------------------------------- |
| **Active** | Company is currently active and in good standing |
| **Dissolved** | Company has been formally dissolved |
| **Struck off** | Removed from the register (e.g., for non-compliance) |
| **In liquidation** | Company is in the process of being wound up |
| **Dormant** | Registered but not currently trading |
Companies with a status other than **Active** may be automatically flagged or declined depending on your workflow configuration.
### Data source indicator
Each company record includes an `is_from_registry` flag:
* **`true`** — data was retrieved from an official company registry
* **`false`** — data was manually submitted because the company was not found in the registry
The `fetch_status` field tracks whether the registry lookup has completed, failed, or is still in progress.
## Data tiers and pricing
The registry check is billed once per selected company, at the price of the tier delivered for that company's country. Hosted workflow searches have no separate search fee.
Standalone API searches can incur a \$0.50 fee per successful request; see [search billing](/standalone-apis/kyb-registry-search). See [KYB registry pricing](/getting-started/kyb-registry-pricing) for the full per-country table.
| Tier | What you get |
| ---------------- | ----------------------------------------------------------------------------------------------------- |
| **Lite** | The fields above: company profile, identifiers, status, registered address, activity, and officers |
| **Shareholders** | Lite plus the direct shareholders (natural persons and corporate entities) with their ownership bands |
| **UBOs** | Shareholders plus the ultimate beneficial owners resolved through the ownership chain |
A hosted workflow picks the tier per country from *Workflows → KYB Registry Check → Countries*. Where the registry holds no ownership record for a company, a Shareholders or UBO request is delivered and billed at the highest tier the country's registries can serve — usually Lite.
A company that is not found and is entered manually (`is_from_registry: false`) is billed a flat **\$0.75** — the search still ran, even though no registry record was returned.
Lite returns the company profile only — no shareholders, UBOs, or ownership structure:
```json theme={null}
{
"company_name": "ACME HOLDINGS PTY LTD",
"registration_number": "094447122",
"country_code": "AU",
"registry_status": "active",
"incorporation_date": "2000-09-12",
"company_type": "Proprietary Limited",
"registered_address": "Sydney, NSW, 2000",
"tax_number": null,
"vat_number": null,
"website": null,
"is_from_registry": true
}
```
The Shareholders tier adds `ownership_structure` and shareholder entries plus directors/officers. The UBOs tier additionally resolves beneficial owners with ownership percentages. See [Ownership](/business-verification/ownership) and [Officers & directors](/business-verification/officers).
## Registry data vs user-provided data
Didit keeps registry-returned data and user-confirmed data as **separate audit records**, with a merged canonical view used by downstream checks. Every company carries three related blocks:
| Field | Description |
| -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `registry_data` | Raw payload returned by the registry. Immutable — kept as an audit record of exactly what the registry said, unchanged. |
| `user_provided_data` | What the end user confirmed or edited during the registry-confirmation step in the hosted flow. |
| Top-level canonical fields (`company_name`, `registration_number`, `country_code`, `incorporation_date`, etc.) | The merged truth — user-confirmed data when present, registry data as fallback. Downstream features (document OCR cross-check, AML screening, risk assessment) run against the canonical view. |
The `confirmed_by_user_at` timestamp marks when the end user confirmed or edited the registry data. While that timestamp is still null and `is_editable` is `true`, the end user can continue to modify fields inside the hosted flow.
For **manual-entry companies** (`is_from_registry: false`), `user_provided_data` is the only source and `is_editable` becomes `false` after the user submits.
Analysts can see the two sources side-by-side in the [Business Console](/business-verification/console) session review and diff what the registry said vs what the user confirmed.
## Configurable registry form fields
You control which company-data fields the hosted verification flow asks for during the registry-confirmation step. Configure them in the Business Console: open the workflow editor, select the **KYB Registry check** node, and switch to the **Fields** tab.
Each field has one of three states:
| State | Behavior |
| ------------ | ----------------------------------------------------------------- |
| **Hidden** | The field is not shown in the hosted flow and is not collected. |
| **Optional** | The field is shown; the user can leave it empty. |
| **Required** | The field is shown and must be filled before the user can submit. |
Three fields are always shown and always required — they identify the company and cannot be configured: `company_name`, `country_code`, and `region` (state/region, where the registry is regional).
The 16 configurable fields:
`registration_number`, `incorporation_date`, `legal_address`, `vat_number`, `alternative_names`, `tax_number`, `company_type`, `legal_entity_identifier`, `location_of_registration`, `nature_of_business`, `registered_capital_amount`, `registered_capital_currency`, `website`, `email`, `phone`, `control_scheme`
By default — and for all existing workflows — only `incorporation_date` is **Required**; every other configurable field is **Optional**, so the form behaves exactly as it did before this setting existed.
Requiredness is enforced server-side: submitting the registry form with a required field empty returns `400` with per-field error messages, and the hosted UI renders only the enabled fields. In the workflow API, the setting is the `kyb_registry_fields_config` object on the KYB Registry node — see [feature configs](/management-api/workflows/feature-configs#kyb-registry) for the shape.
`vat_number` marked **Required** is only enforced for companies incorporated in an EU VAT country (see below) — for all other countries the field is treated as optional even when required, since there is no EU VAT number to collect.
## VAT validation (VIES)
When the end user (or the registry) provides a `vat_number` for a company incorporated in the EU, Didit validates it against the European Commission's [VIES](https://ec.europa.eu/taxation_customs/vies/) service when the registry step is submitted.
* **Coverage** — the 27 EU member states plus Northern Ireland (`XI`). Greek VAT numbers are checked under the `EL` prefix automatically. Companies from any other country skip validation entirely and get `vat_validation_status: "not_applicable"`.
* **Prefix handling** — the number is normalized before the check, so it can be submitted with or without the country prefix (`DE123456789` and `123456789` are equivalent for a German company).
The result is stored on the company and exposed in the [KYB decision payload](/business-verification/response-schema#company-block):
| Field | Description |
| ----------------------- | ----------------------------------------------------------------------- |
| `vat_number` | The VAT number as submitted |
| `vat_validation_status` | `valid`, `invalid`, `could_not_validate`, or `not_applicable` |
| `vat_validated_name` | Trader name registered with VIES, when the member state discloses it |
| `vat_validated_address` | Trader address registered with VIES, when the member state discloses it |
| `vat_checked_at` | Timestamp of the VIES check |
| Status | Meaning |
| -------------------- | -------------------------------------------------------------------------- |
| `valid` | VIES confirmed the VAT number is registered |
| `invalid` | VIES rejected the number (not registered or malformed) |
| `could_not_validate` | VIES or the member state was unavailable — the number could not be checked |
| `not_applicable` | No VAT number was provided, or the company is outside the EU VAT area |
**Name cross-check.** When the number is `valid` and VIES discloses the registered trader name, Didit fuzzy-compares it against the submitted company name. A meaningful divergence raises the `KYB_COMPANY_VAT_NAME_MISMATCH` warning (with `vies_name` and `submitted_name` in `additional_data`) without changing the session status — it is a soft signal for reviewers. Some member states do not disclose the name or address; the fields come back empty and no comparison runs.
**Configurable actions.** Two settings on the KYB Registry check node control what an unsuccessful check does to the session (values `REVIEW`, `DECLINE`, `NO_ACTION`):
| Setting | Fires when | Default |
| --------------------------- | ----------------------------------------------- | -------------------------------------- |
| `kyb_vat_invalid_action` | `vat_validation_status` is `invalid` | `REVIEW` — session goes to In Review |
| `kyb_vat_unverified_action` | `vat_validation_status` is `could_not_validate` | `NO_ACTION` — no effect on the session |
Each outcome also raises the matching warning (`KYB_COMPANY_VAT_INVALID`, `KYB_COMPANY_VAT_COULD_NOT_VALIDATE`) on the registry check — see [Business Verification warnings](/business-verification/warnings#kyb-registry-warnings).
## Data cross-referencing
Didit automatically compares the user-provided / canonical data with what the registry returned, and with OCR data extracted from uploaded documents:
* **Company name** — fuzzy matching to account for abbreviations and formatting differences
* **Registration number** — exact match validation
* **Country** — consistency check between provided and registry country
* **Address** — geocoding and normalization for comparison
Any inconsistencies are flagged as warnings in the session and visible to analysts in the console.
## Financial summary
Where available, Didit retrieves summary financial data from the registry:
* Revenue or turnover
* Number of employees
* SIC/NACE industry codes
* Filing dates and compliance status
## Additional registry fields
Depending on jurisdiction coverage, the registry response may also include:
* `alternative_names` - trade names, dbas, former legal names
* `legal_entity_identifier` (LEI) — for financial institutions and listed entities
* `website`, `email`, `phone` — contact information on file with the registry
* `nature_of_business` — short description (in the language of the registry)
* `registered_capital` — authorized / issued capital as an `{amount, currency}` object
All fields land in the [`business`](/business-verification/response-schema) block of the KYB decision.
## Confidence and verification status
Each registry response carries a `verification_status`:
| Value | Meaning |
| ---------- | ---------------------------------------------------------------------------- |
| `VERIFIED` | Registry returned a match and data was retrieved. |
| `FAILED` | Registry returned a match but data retrieval failed (e.g. partial response). |
| `UNKNOWN` | Company could not be located in any registry. |
Combine with `is_from_registry` to drive your own downstream logic:
| `is_from_registry` | `verification_status` | Meaning |
| ------------------ | --------------------- | --------------------------------------------- |
| true | VERIFIED | Fully verified against official registry |
| true | FAILED | Partial — some fields may be stale or missing |
| false | UNKNOWN | User-provided data only; no registry match |
## Cross-referencing outputs
Each cross-reference runs per field and returns one of:
* `MATCH` — provided data agrees with registry.
* `MISMATCH` — meaningful divergence. Flagged as a warning.
* `NO_DATA` — one side was empty; no comparison possible.
Mismatches are visible per-field in the console session review.
## Risk assessment
The company's overall risk level is calculated based on:
| Factor | Impact |
| ------------------------ | --------------------------------------------------------------- |
| **Company status** | Non-active companies increase risk |
| **Country risk** | Incorporation in high-risk jurisdictions increases risk |
| **Age** | Recently incorporated companies may receive additional scrutiny |
| **AML results** | Company-level AML screening results |
| **Ownership complexity** | Multi-layered or opaque ownership structures |
See [risk assessment](/business-verification/risk-assessment) for the full computation.
## Supported countries
Coverage varies by country — see [supported countries](/business-verification/supported-countries) for the per-region tier breakdown.
## Next steps
UBO identification and ownership chains.
Where each field appears in the payload.
Coverage by region.
# Business Verification Console
Source: https://docs.didit.me/business-verification/console
Review KYB sessions in the Business Console: company data, ownership, AML results, documents, key people. From $2.00 per check, priced per country and tier, no contracts.
Business verification sessions are managed in the Business Console alongside user verifications, with KYB-specific sections for registry data, documents, and key people. This page walks through the session detail view.
## Navigating to a business session
From the Business Console, open *Businesses → \[business]* to see all sessions for that business, or open the session directly from the Sessions list. Business sessions show a **business** badge next to the session number so they're easy to spot in a mixed list.
## Session detail — sections
A KYB session detail page shows these sections. Which sections appear depends on the features your workflow enabled.
### Overview
Session summary: status, workflow used, `vendor_data`, session number, creation/expiry timestamps, per-feature status chips (KYB Registry, KYB Company AML, KYB Documents, KYB Key People, and any shared features like AML, Phone, Email, Questionnaires, Device & IP Analysis).
### KYB Registry
The company block extracted from the registry (or submitted manually when not found):
* **Registry data** — registry-returned fields: legal name, registration number, incorporation date, company type, registered address, tax number, industries, accounts / filing dates.
* **User-provided / confirmed data** — what the end user confirmed or edited in the hosted flow, shown side-by-side with the registry data.
* **Data source indicator** — `is_from_registry`, `fetch_status`, `verification_status`, `confirmed_by_user_at`.
* **Financial summary** — where the registry publishes it.
* **Risk level** — composite signal (`LOW`, `MEDIUM`, `HIGH`).
Actions: confirm / edit canonical fields, view the raw registry payload for audit, request the end user to re-confirm.
### KYB Documents
Documents grouped by document type group — Legal presence, Company details, Ownership structure, Representatives authorization.
* **Per-group progress** — approved / pending / missing counts per group, and which groups are required.
* **Document items** — each uploaded document with signed preview URL, OCR extract, cross-check result against the user-provided data.
* **Cross-check diff** — per-field comparison between the OCR data and the canonical company data.
Actions: approve or decline individual documents, request a resubmission, request an additional document, download.
### KYB Key People
The key-people list — UBOs, shareholders, directors, representatives, and other officer roles — shown **side-by-side** in two buckets:
* **Registry** — parties extracted from the registry check, grouped by role category.
* **Submitted** — parties the business admin added during the Key People flow (source = USER).
Each person shows: name, entity type (person or company), role(s) with ownership / voting percentages where relevant, KYC status of the linked child session (`Approved` / `Declined` / `Pending`), AML status, and a link to open the child KYC or KYB sub-session.
The `ubo_kyc_summary` card rolls up UBO KYC progress — total / approved / flagged / pending — at the top of the section.
Actions: open a child session, skip a party's verification (or require it again), request resubmission of a specific child session, view the child AML hits.
**Skipping a party** exempts it from the Key People check so a KYB that cannot progress can still be settled - the usual case is a party whose individual KYC session was deleted, which leaves it with a dead link and a status that can never change. The control needs the `write:businesses` permission and, unlike the applicant-facing skip in the hosted flow, is not limited to roles with `allow_skip`. The same override is available over the API: [`PATCH /v3/session/{id}/kyb/parties/{party_uuid}/`](/sessions-api/update-data#skip-a-key-people-party).
### AML
Company-level AML screening. For each hit: match score, risk score, category (sanctions, PEP, adverse media, criminal, enforcement), watchlist source, screened name. Per-hit actions: mark false positive, confirm, add note.
Person-level AML hits for key people also surface here, each linked back to the relevant key person.
### Device & IP Analysis
Submitter IP, country, ASN, data-center / VPN flags, device fingerprint when collected.
### Phone / Email / Questionnaires
Appear only when the workflow collects them. Same feature-detail surface as on a KYC session.
### Events
Chronological log of everything that happened on the session — status changes, feature completions, analyst actions, webhook deliveries. Filter by event type and date.
### Webhooks
History of webhook deliveries for this session with response codes, retry counts, and raw payloads. You can resend any delivery.
### Chat
Internal analyst notes with `@`-mentions. Not visible to the end user.
## Tag management
Tag any business session for categorisation and later filtering. Tags appear as chips on the Overview and on the Sessions list — useful for tracking risk tiers, review stages, partner source, or any custom axis.
## Reviewer actions
From any section, an analyst with the right role can:
* **Approve** the session when all checks pass.
* **Decline** the session if a critical issue stands.
* **Request more information** — force a resubmission on specific documents or child KYC sessions.
* **Override a feature status** — manually set a feature to `APPROVED` / `DECLINED` / `IN_REVIEW` with a note.
* **Add a chat note** — visible to teammates, not to the user.
* **Tag** the session for downstream filtering.
* **View the full audit trail** of every action on the Events tab.
See [Roles & permissions](/console/roles-permissions) for the matrix of who can do what.
## Business profiles
Business profiles give you the durable cross-session view of each company. From *Businesses* you can search, filter, tag, bulk-update status, export CSV, and import in bulk. See [Business profiles](/business-verification/business-profiles) for details.
## Next steps
The broader Business entity model across sessions.
Deep reviewer walkthroughs across KYB and KYC.
Configuring KYB workflows.
# Documents
Source: https://docs.didit.me/business-verification/documents
Collect and verify corporate documents — incorporation certificates, articles, financials — with OCR and registry cross-referencing. Document collection is billed separately from registry retrieval.
Business verification includes structured document collection to validate company information beyond what's available in registries.
## Pricing
In the current staging release, KYB document collection costs **USD 0.20 per workflow step**, independent of how many documents are uploaded in that step. Re-uploading a document in the same step does not add another fee; a separate document-collection step has its own charge. Production rollout of this billing change is pending. Registry retrieval, company and person AML, and linked KYC are separate charges. The 500 free document-capture ID checks do not cover KYB document collection.
## Document groups
Documents are organized into four structured groups, each representing a category of evidence needed for business verification:
| Group | Description | Examples |
| --------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
| **Legal presence** | Documents proving the company legally exists | Certificate of incorporation, business registration |
| **Company details** | Documents describing the company's operations | Articles of association, memorandum of association |
| **Ownership structure** | Documents proving the ownership chain | Shareholder register, share certificates, UBO declarations |
| **Representatives authorization** | Documents proving authority to act on behalf of the company | Power of attorney, board resolutions, authorization letters |
Each group tracks its own progress with per-group summaries: total documents expected, approved, pending review, and missing. The business session's overall documents status aggregates across all groups.
## Document types
| Category | Examples |
| ------------------------ | -------------------------------------------------------------------------------- |
| **Formation documents** | Certificate of incorporation, articles of association, memorandum of association |
| **Ownership documents** | Shareholder register, share certificates, UBO declarations |
| **Address documents** | Utility bills, bank statements, or lease agreements for the registered address |
| **Financial documents** | Annual accounts, audit reports, financial statements |
| **Tax documents** | Tax registration certificates, tax compliance certificates |
| **Regulatory documents** | Licenses, permits, regulatory approvals |
| **Custom documents** | Any additional documents required by your compliance process |
## Document workflow
Based on your workflow configuration, Didit automatically identifies which documents are needed for the business verification. Required document types are presented to the business for upload.
Documents are uploaded through the hosted verification flow or via the Business Console. Supported formats include PDF, JPG, PNG, and WebP.
Didit extracts structured data from uploaded documents using OCR:
* Company name and registration number from certificates
* Address data from utility bills
* Financial figures from statements
* Names and roles from shareholder registers
Extracted data is cross-referenced against:
* Registry data from the company lookup
* Information provided at session creation
* Data from other uploaded documents
Inconsistencies are flagged for analyst review.
Analysts review uploaded documents in the console, verify their authenticity, and approve or reject each document individually.
## Analyst document requests
During the review process, analysts can request additional documents:
1. Navigate to the business session in the console
2. Click **Request document** and specify the document type and description
3. The business is notified and can upload the requested document
4. The document appears in the session with a **Requested** status until uploaded
This allows analysts to gather additional evidence without rejecting the entire business session.
## Document statuses
Each document and the aggregate `document_verifications[]` check use the standard KYB feature status set:
| Status | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------- |
| `NOT_FINISHED` | Document is still being processed (upload in progress, OCR running, or cross-check pending). |
| `APPROVED` | Document was reviewed and accepted. |
| `DECLINED` | Document was reviewed and rejected. |
| `IN_REVIEW` | Document requires manual analyst attention. |
| `RESUB_REQUESTED` | Analyst requested a resubmission — the business must re-upload this document before the session can progress. |
## Cross-check results
When a document is processed, Didit cross-references the OCR-extracted data against the **user-provided data** submitted in the Key People flow (and any data confirmed during registry review) plus other uploaded documents. The cross-check produces structured results:
| Category | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Critical fields** | Fields where the document data and the user-provided data do not match — these require analyst review. Each field shows the document value, the user-provided value, and a confidence score. |
| **Non-critical fields** | Fields with minor discrepancies or formatting differences that may not require action (e.g. abbreviation differences in company name). |
| **People overlap** | Analysis of people mentioned in the document vs. people submitted by the user — highlights missing or additional individuals. |
Cross-check results are displayed in the [Business Console](/business-verification/console) alongside the document preview, giving analysts a clear view of what matches and what needs attention.
## Retrieving documents via API
Documents are attached to the Business Verification (KYB) session. Fetch them via the session decision:
```bash theme={null}
curl https://verification.didit.me/v3/session/{session_id}/decision/ \
-H "x-api-key: YOUR_API_KEY"
```
The response includes a `document_verifications[]` array — each item has a `status`, `node_id`, `items[]` (each with a signed `file_url` and OCR extract), `groups`, and `required_groups`. See [response schema](/business-verification/response-schema#document_verifications) for the full structure.
## Next steps
Where documents appear in the payload.
Reviewing documents as an analyst.
# KYB Integration Guide
Source: https://docs.didit.me/business-verification/integration-guide
Production-ready KYB integration: architecture, sessions, hosted flows, webhooks, polling, and decisions. From $2.00 per check, priced per country and tier.
This guide is the companion to the [quickstart](/business-verification/quickstart). It covers the architecture, every integration decision, and the operational patterns that make KYB reliable in production.
## Architecture
```mermaid theme={null}
sequenceDiagram
participant You as Your platform
participant Didit as Didit API
participant Biz as Business contact
participant Webhook as Your webhook endpoint
You->>Didit: POST /v3/session/ (workflow_id, vendor_data)
Didit-->>You: session_id, url, token
You->>Biz: Deliver hosted verification URL (your email/flow)
Biz->>Didit: Completes KYB hosted flow
Didit->>Didit: Registry lookup, AML screening, OCR
Didit->>Webhook: status.updated (session_kind: business)
You->>Didit: GET /v3/session/{id}/decision/
Didit-->>You: Full KYB decision payload
You->>You: Onboard / reject / escalate based on status
```
## Authentication
All requests require the `x-api-key` header with your application's API key. See [API authentication](/getting-started/api-authentication) for key rotation, environment separation, and scopes.
## Session creation
Single endpoint: `POST /v3/session/`. The workflow's type determines whether the session is KYC or KYB — no explicit "business" flag is needed.
**Recommended fields on creation:**
| Field | Purpose |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `workflow_id` | Required — references your KYB workflow. |
| `vendor_data` | Your identifier for the business. Binds the session to a [Business entity](/entities/businesses/overview). |
| `callback_url` | Optional — where to redirect the user after completion. |
| `expected_details` | Optional — `{ company_name, registry_country, registration_number }`. Pre-fills the company registry search: the country of incorporation is pre-selected and locked (`registry_country` as `XX`, or `XX-YY` for state-level registries, e.g. `US-CA`), the company-name and registration-number fields are pre-filled, and the asserted search runs automatically — combining both identifiers when both are present. Prefill only — mismatches do not produce warnings. |
| `metadata` | Optional JSON attached to the session. |
Full schema: [create session](/sessions-api/create-session).
## Delivering the hosted link
The response `url` is a hosted verification link. Options for delivery:
* Email it from your own platform.
* Embed it in your onboarding UI as a link.
* Open it in an in-app webview (iOS / Android / React Native).
You can **customize** the hosted experience — logo, color, domain, email templates — via [White-label](/console/white-label).
## Polling vs webhooks
Webhooks are the recommended pattern. Didit POSTs completion events to your subscribed endpoint as soon as processing finishes.
Polling is supported for development or when your webhook endpoint is unreachable:
* Poll `GET /v3/session/{id}/decision/` with exponential backoff (every 10s initially, up to 60s).
* Stop polling when `status` is one of `APPROVED`, `DECLINED`, `IN_REVIEW`.
Polling at high frequency may hit [rate limits](/integration/rate-limiting). Always prefer webhooks in production.
## Webhook handling
Subscribe to the events you care about — the same events cover both KYC and KYB, with `session_kind: "business"` inside the payload to identify business-session events:
* `status.updated` — session status changed. Filter on `data.session_kind === "business"` for KYB sessions.
* `data.updated` — session data changed (registry refresh, key-people submission, documents, ongoing AML). Same `session_kind` filter applies.
* `business.status.updated` / `business.data.updated` — the linked [Business entity](/entities/businesses/overview) changed.
Full event catalog: [KYB webhooks](/business-verification/webhooks). Signature verification and retry semantics: [webhooks reference](/integration/webhooks).
## Handling decisions
```typescript theme={null}
switch (decision.status) {
case 'APPROVED':
await onboardBusiness(decision);
break;
case 'DECLINED':
await logRejection(decision.decision_reason_code, decision);
break;
case 'IN_REVIEW':
// Wait for analyst decision — another webhook will fire
await markPending(decision);
break;
case 'RESUBMITTED':
// The business was asked to re-upload a document
await notifyBusiness(decision);
break;
}
```
See [KYB statuses](/business-verification/statuses) for the full state machine and reason codes.
## Resubmission flow
When a Business Verification (KYB) session transitions to `RESUB_REQUESTED` (or a feature is marked for resubmission), the business needs to upload corrected or additional data.
You can:
* **Re-deliver** the same `url` — the hosted flow picks up where resubmission is needed.
* **Open a [case](/transaction-monitoring/cases)** in the Business Console to track the back-and-forth internally.
## Business profile aggregation
If you pass `vendor_data` on session creation, Didit aggregates all sessions for that business into a single [Business entity](/entities/businesses/overview). Use the entity's `features` map to answer "is this business fully verified right now?" without iterating sessions.
Benefits:
* Periodic re-KYB flows append to the same profile.
* Transactions monitored against the same `vendor_data` carry the business's risk context.
* UBOs and officers linked to User entities populate cross-entity relationships.
## Best practices
| Practice | Why |
| ----------------------------------- | ----------------------------------------------------------------- |
| Always pass `vendor_data` | Without it, the session is orphaned and can't be aggregated. |
| Use webhooks, not polling | Lower latency, no rate limit pressure. |
| Verify webhook signatures | Prevents spoofed events. |
| Store the `session_id` on your side | Needed to retrieve the decision and generate PDFs. |
| Tag sessions with `metadata` | Simplifies reporting and debugging. |
| Set up a test workflow | Use a sandbox workflow for integration testing before production. |
## Error handling
| Scenario | Response |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invalid `workflow_id` | `400 Bad Request` |
| Duplicate session with same `vendor_data` | Creates a new session — that's by design, since re-verification is expected. |
| Dangerous country | Session is created but immediately `DECLINED` with reason `blocked_country`. |
| Blocklisted `vendor_data` | Session auto-declined with reason `blocked_business`. |
| Registry provider outage during company search | `POST /v3/kyb/search/` returns `502` with `code: "kyb_registry_provider_unavailable"`. Not billed - retry, or branch on `code` to offer manual company entry. In Didit's hosted flow, the business is routed into manual entry automatically instead of a retry-only error. See [KYB Registry Search](/standalone-apis/kyb-registry-search#provider-outages). |
| Rate limit | `429 Too Many Requests` with `Retry-After` header. |
Full [rate limiting](/integration/rate-limiting) reference.
## Next steps
Every KYB event and payload.
State machine reference.
Decoding the KYB decision.
# Key People
Source: https://docs.didit.me/business-verification/key-people
Verify UBOs, shareholders, directors, and officers in one unified KYB flow with role-aware checks and linked KYC sessions. From $2.00 per check, priced per country and tier, no contracts.
**Key people** is Didit's unified model for every natural person and corporate entity tied to the business you're verifying: UBOs, shareholders (individual and corporate), directors, representatives, and other officer roles. A single Key People flow covers the lot — with role-aware verification dispatch and full side-by-side comparison between registry-sourced and user-submitted parties.
For the Business-entity view of key people — how officers and UBOs aggregate across sessions — see [Businesses → Key people](/entities/businesses/key-people).
## How it works
If the workflow enables registry prefill, the Key People screen is pre-populated with UBOs, shareholders and officers extracted from the registry check. The end user can edit, add, or remove parties before submitting.
The end user submits the final list of key people through the hosted verification flow. You don't call this step yourself — it happens inside the KYB flow whose URL you delivered from [Create Session](/sessions-api/create-session).
For each submitted party, Didit checks the role's configured verification settings:
* Applies the workflow's ownership threshold — UBOs and shareholders below the configured percentage don't require individual verification.
* Honours the "skip verification" flag the end user set, if the role allows skipping.
* Spawns a child User Verification (KYC) session for person parties that require KYC.
* Spawns a child KYB sub-session for corporate UBOs when nested KYB is enabled on the workflow.
The Key People feature status becomes `Awaiting User` while any required child session is still in progress. When every child session finishes, the status aggregates to `Approved`, `Declined`, or `In Review` based on the outcomes.
## Roles collected
Key people can carry one or more of the following roles — a single person can hold several at once (for example, director **and** shareholder). The full taxonomy:
| Category | Roles |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Ownership** | `ubo`, `shareholder`, `beneficiary`, `settlor`, `protector`, `investor` |
| **Governance / representation** | `director`, `non_executive_director`, `chairman`, `secretary`, `representative`, `authorized_signatory`, `trustee`, `company_officer`, `founder`, `legal_advisor`, `other` |
Every role can be individually configured in the workflow — enabled or disabled, required or optional for KYC, mapped to a specific KYC workflow, and optionally skippable. See [Ownership](/business-verification/ownership) for the verification dispatch details.
## Data collected per key person
The hosted flow asks for a default set of fields and additional workflow-configured custom fields. See [Ownership → Fields collected](/business-verification/ownership#data-collected-for-each-key-person) for the full list.
## Registry confirmation
Before the Key People step, the end user reviews the company data extracted from the registry check and optionally edits any fields. The hosted flow then stores:
* The **raw registry payload** as an immutable audit record (`registry_data`).
* The **user-confirmed data** (`user_provided_data`) with a timestamp that marks when the user confirmed.
* A merged canonical view on the top-level company fields — user-confirmed when present, registry fallback otherwise.
See [Company data → Registry vs user-provided data](/business-verification/company-data#registry-data-vs-user-provided-data) for the full model.
## Shape in the decision response
After submission, the collected key people surface in the KYB decision under `key_people_checks[]`, split into two buckets — **registry** (what the provider disclosed) and **submitted** (what the business admin confirmed through the flow) — plus a `ubo_kyc_summary` that rolls up UBO KYC progress. See the full [key\_people\_checks reference](/business-verification/response-schema#key_people_checks).
## Resubmit behaviour
* **Key People** and **Registry Check** cannot be resubmitted directly. To re-verify a specific person, resubmit their individual child KYC session.
* When a child KYC session is resubmitted, the parent Key People feature status automatically returns to `Awaiting User`.
* When the resubmitted child finishes, the parent re-aggregates based on all child statuses.
## Configuration options
All Key People behaviour is configured per workflow from the Business Console:
| Setting | What it controls |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Registry prefill** | Pre-populate the Key People form from registry data |
| **Extra fields — persons** | Custom fields to collect for each person party |
| **Extra fields — companies** | Custom fields to collect for each corporate party |
| **UBO ownership threshold** | Minimum ownership percentage to classify someone as a UBO |
| **Shareholder ownership threshold** | Minimum ownership percentage to classify someone as a shareholder |
| **Per-role settings** | For each role: enabled/disabled, require KYC, KYC workflow to use, skippable |
| **Corporate UBO nested KYB** | When enabled, corporate UBOs trigger their own child KYB session |
| **Reuse verified individuals** | See [Reusing verified individuals across KYB checks](#reusing-verified-individuals-across-kyb-checks) below |
| **Notify parties by email** | Automatically email each key person with their verification link |
Configure at *Workflows → \[KYB workflow] → Key People* in the console.
## Reusing verified individuals across KYB checks
When **Reuse verified individuals** is enabled on a workflow (the default), a key person doesn't have to redo KYC every time they show up on a new KYB check. Didit looks for an existing approved KYC from a previous KYB check and links it to the new party instead of spawning a fresh child KYC session.
This is a distinct mechanism from [Reusable KYC](/core-technology/reusable-kyc/overview), which lets an *end user* voluntarily carry one finished session across separate Didit-integrated applications via a share/import token. The KYB reuse setting on this page is automatic, workflow-configured, and scoped to key people within a single application - no share token, and no action from the end user.
**Matching.** A candidate match is found by comparing the submitted person's full name (case-insensitive, whitespace-trimmed) against key people from the application's other KYB checks. There's no additional matching signal - a document number, email, or phone match is not required or checked. A name entered with different spelling, a middle name, or a transliteration won't match, and in rare cases two different people who share a name could match; review your Key People results with this in mind.
**Scope.** Matching looks across every other KYB check that belongs to the *same application*, not just the current KYB check and not other applications in your organization. A person verified as a UBO on Company A can be matched when they later appear as a director on Company B, as long as both checks run under the same application.
**Eligible statuses.** Only a party's most recent **Approved** KYC is eligible for reuse. A **Declined** KYC for that name blocks reuse entirely - the party is flagged and a fresh KYC is requested instead of falling back to the declined one. A KYC that's still **In Review** is never considered a candidate.
**Retention.** Matching only considers KYC sessions created in roughly the last year that haven't been deleted. If the only matching session falls outside that window, or the person or org has deleted it, the check finds no eligible match and silently requests a new KYC - it never fails or blocks Key People submission.
**Billing.** A reused KYC is not billed again. Linking an already-approved session to the new party carries no additional charge; you're only billed once, on the original KYC.
```txt Example theme={null}
Company A: Jane Doe submitted as UBO, KYC approved.
Company B (same application): Jane Doe submitted as a director.
"Reuse verified individuals" is enabled, so Company B's Key People
check links Jane's existing approved KYC instead of requesting a
new one - free, and instantly Approved for that party.
```
## Next steps
Fields collected, custom data, and registry vs submitted reconciliation.
Governance roles within the key-people model.
`key_people_checks[]` field-by-field reference.
# Officers & Representatives
Source: https://docs.didit.me/business-verification/officers
Verify directors, officers, and authorized representatives via Didit's unified Key People model with multi-role tagging and AML screening. From $2.00 per check, priced per country and tier.
Officers, directors, and representatives are **not a separate concept in Didit's KYB model** — they live inside [Key People](/business-verification/key-people) alongside UBOs and shareholders, tagged with one or more roles per person. A single person can be a director **and** a shareholder, or a representative **and** an authorized signatory — the party carries the full role list, not just one role.
## Full role taxonomy
Every key person can hold one or more of these roles:
### Governance / representation
| Role | Description |
| ------------------------ | --------------------------------------------------------------- |
| `director` | Board member with governance responsibilities |
| `non_executive_director` | Board member without day-to-day management duties |
| `chairman` | Chair of the board |
| `secretary` | Company secretary handling administrative and compliance duties |
| `representative` | Legal or appointed representative of the company |
| `authorized_signatory` | Person empowered to sign on behalf of the company |
| `trustee` | Trustee of a structure holding company interests |
| `company_officer` | General officer role |
| `founder` | Founding member of the company |
| `legal_advisor` | Retained legal counsel for the company |
| `other` | Any role outside the standard taxonomy |
### Ownership / beneficiary
| Role | Description |
| ------------- | ---------------------------------------------------------------------------------- |
| `ubo` | Ultimate Beneficial Owner — individual who ultimately owns or controls the company |
| `shareholder` | Registered shareholder (individual or corporate) |
| `beneficiary` | Beneficiary of a trust or similar structure |
| `settlor` | Settlor of a trust |
| `protector` | Protector of a trust |
| `investor` | Investor role (outside formal share ownership structures) |
A single person can carry both categories — for example, a founder who is also a director and a UBO holds three roles on the same key-people record.
## Multi-role records
Each key person record includes a list of roles, each with its own ownership/voting percentages where relevant. This is how a director-plus-shareholder looks in the response:
```json theme={null}
{
"uuid": "...",
"name": "Jane Doe",
"first_name": "John",
"last_name": "Doe",
"entity_type": "person",
"roles": [
{ "role": "director", "ownership_percent": null, "voting_percent": null },
{ "role": "shareholder", "ownership_percent": 15.0, "voting_percent": 15.0 }
],
"kyc_status": "Approved",
"kyc_session_url": "https://verify.didit.me/..."
}
```
## KYC linking
Every key person can be linked to an individual User Verification (KYC) session. The linking happens automatically when the role's workflow settings require KYC — the system spawns the child KYC session, emails the person their verification link (if that option is enabled), and tracks status back on the party.
See [Ownership → How role settings drive verification](/business-verification/ownership#how-role-settings-drive-verification) for the full routing logic. For the B2C-safe KYC status values surfaced on each party (`Approved`, `Declined`, `Pending`), see the [response schema](/business-verification/response-schema#company-block).
## AML screening
Every identified key person is automatically screened against global AML watchlists as part of the business verification process. PEP matches are especially relevant for governance roles — directors and signatories of regulated companies often surface on PEP lists. See [Business AML screening](/business-verification/aml).
## Requires-verification flag
Each party carries a `requires_verification` flag that tells you whether the workflow demands KYC for that person:
| Value | Meaning |
| ------- | ----------------------------------------------------------------------- |
| `true` | KYC must complete for this person before the session can approve. |
| `false` | Person is identified and AML-screened but does not need individual KYC. |
Configure which roles require verification from *Workflows → \[KYB workflow] → Key People → per-role settings*. Typical defaults:
* Directors and UBOs above the workflow's threshold → `requires_verification: true`.
* Authorized signatories and secretaries → optional.
* Non-executive board members → optional.
## Skipping a key person
If a person cannot be verified (deceased, unreachable, historical), they can be marked with `is_skipped: true` — either by the end user in the hosted flow (if the role's settings allow skipping) or by an analyst from the console. Skipped parties are excluded from the verification gate but still surface in the response for audit.
## Cross-referencing across sources
Data about each key person can come from three places, and the console shows all three side-by-side:
* **Registry records** — what the official registry disclosed.
* **User-submitted** — what the business admin confirmed or added in the Key People flow.
* **Uploaded documents** — names extracted via OCR from articles of association, shareholder registers, or power-of-attorney documents.
Inconsistencies surface as warnings on the session decision.
## Effect on the Business entity
Linking a key person to a User entity via KYC creates a lasting relationship on the [Business entity](/entities/businesses/overview). You can then:
* See every business each person serves in a governance or ownership role.
* Propagate a User's status changes (flagged, blocked) into businesses where they serve as a key person.
* Run ongoing AML monitoring on each linked person independent of any specific KYB session.
## Next steps
The full Key People flow.
UBO identification and ownership chains.
Company and person AML screening.
# Business Verification
Source: https://docs.didit.me/business-verification/overview
Verify companies and legal entities with automated registry lookups, UBO identification, officer screening, and AML — from $2.00 per check, priced per country and tier, no contracts.
This Didit Academy lesson follows a business verification from start to finish: the KYB dashboard, the registry check, shareholders and UBOs, AML screening, and what the company representative sees on their side.
Didit's Business Verification (KYB) lets you verify companies and legal entities before onboarding them to your platform. The system automates company registry lookups, identifies beneficial owners and officers, screens all parties against AML watchlists, and collects supporting documents — all within a single workflow. Each verified company is tracked as a [business profile](/business-verification/business-profiles), aggregating results across sessions.
New to Didit KYB? Start with the [quickstart](/business-verification/quickstart) for a 10-minute end-to-end walkthrough, then read the [integration guide](/business-verification/integration-guide) for production patterns. For the underlying Business entity model (linking to Users, blocklists, import/export), see [Businesses](/entities/businesses/overview).
## How it works
Start a business verification by creating a session with a **KYB workflow**. When the workflow type is KYB, Didit automatically creates a business session:
```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workflow_id": "your-kyb-workflow-id",
"vendor_data": "company-123"
}'
```
The session type is determined by the workflow — no extra fields needed. KYB workflows automatically create business sessions with business-specific features enabled.
Didit queries official company registries at the **Lite**, **Shareholders**, or **UBOs** tier — each priced per country, see [registry pricing](/getting-started/kyb-registry-pricing). Lite retrieves:
| Data | Description |
| ----------------------- | ------------------------------------------- |
| **Company name** | Legal name as registered |
| **Registration number** | Official company registration number |
| **Country** | Country of incorporation |
| **Status** | Active, dissolved, struck off, etc. |
| **Company type** | Ltd, LLC, PLC, SA, GmbH, etc. |
| **Incorporation date** | Date the company was registered |
| **Registered address** | Official registered office address |
| **Tax number** | Tax identification number (where available) |
Registry data is cross-referenced with the information provided to detect inconsistencies.
The system identifies every person and corporate entity tied to the business under the unified **Key People** model:
* **UBOs** — individuals who ultimately own or control the company, with ownership and voting percentages
* **Shareholders** (individual and corporate) — each with their ownership percentage
* **Directors, representatives, secretaries, authorized signatories, and other officer roles** — with their specific role tag
* **Corporate ownership layers** — parent companies in the ownership chain, with optional nested KYB
A single person can carry multiple role tags (for example, director **and** shareholder). Each role is individually configured in the workflow — which roles require KYC, which KYC workflow to use, and whether the end user can skip verification. Learn more in [Key People](/business-verification/key-people) and [Ownership](/business-verification/ownership).
All identified parties are automatically screened against global AML watchlists:
* **Company AML** — screens the company entity against sanctions, adverse media, and regulatory enforcement lists
* **Person AML** — screens each key person (UBO, shareholder, director, representative, etc.) individually
* Uses the same [two-score risk system](/core-technology/aml-screening/overview) (match score + risk score) as user verification
Collect and verify supporting documents:
| Document type | Examples |
| -------------------------------- | ------------------------------------------------------------ |
| **Certificate of incorporation** | Company formation certificate |
| **Articles of association** | Bylaws, memorandum of association |
| **Proof of address** | Utility bills, bank statements for the registered address |
| **Shareholder register** | Official register of shareholders |
| **Financial statements** | Annual accounts, audit reports |
| **Tax certificates** | Tax registration or compliance certificates |
| **Custom documents** | Any additional documents required by your compliance process |
Analysts can request additional documents from the business during the review process.
The business session receives an overall status based on all checks:
| Status | Meaning |
| ------------- | -------------------------------------------------------------------- |
| **Approved** | All checks passed — company is verified |
| **In Review** | One or more checks require manual review |
| **Declined** | A critical check failed (e.g., dissolved company, sanctioned entity) |
Analysts can review all collected data in the Business Console, add comments, request additional documents, and make a final decision.
## Business session lifecycle
Each business verification runs within a **business session** that tracks progress through the following statuses:
| Status | Description |
| ------------- | ---------------------------------------------------------------------------------------------- |
| `NOT_STARTED` | Session created but verification has not begun |
| `IN_PROGRESS` | Verification is underway — registry lookups, AML screening, or document collection in progress |
| `APPROVED` | All checks passed — company is verified |
| `DECLINED` | A critical check failed (e.g., dissolved company, sanctioned entity) |
| `IN_REVIEW` | One or more checks require manual analyst review |
| `RESUBMITTED` | Previously declined session resubmitted with updated data |
Each session has a unique `session_id`, a sequential `session_number`, and a `session_token` for the hosted verification link. When a session is created with a `vendor_data` identifier, Didit automatically links it to the corresponding [business profile](/business-verification/business-profiles).
## Feature statuses
Business verification tracks the status of each component independently:
| Feature | Description |
| ------------------ | ------------------------------------------------------------ |
| **Registry check** | Company registry lookup and data extraction |
| **Company AML** | Company-level AML screening against sanctions and watchlists |
| **Documents** | Document collection, OCR, and cross-referencing |
| **Key people** | Beneficial owner and officer identification, AML, and KYC |
Each feature can be: `NOT_FINISHED`, `APPROVED`, `DECLINED`, `IN_REVIEW`, or `RESUB_REQUESTED`. The overall session status aggregates the individual feature statuses — the session is only `APPROVED` when all required features pass.
## Webhook events
Business sessions generate the following webhook events:
| Event | Trigger |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `status.updated` | Session status changed. Payload carries `session_kind: "business"` for KYB sessions. |
| `data.updated` | Session data changed (registry, key people, documents, AML). Payload carries `session_kind: "business"`. |
| `business.status.updated` | The linked [Business entity](/entities/businesses/overview)'s status changed (`ACTIVE` / `FLAGGED` / `BLOCKED`). |
| `business.data.updated` | The linked Business entity's profile fields or aggregate counters changed. |
Webhook payloads include `session_kind: "business"` and the `business_session_id` to distinguish them from user verification events. See [Webhooks](/integration/webhooks) for signature verification and delivery details.
## Key capabilities
| Capability | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Automated registry lookups** | Query official company registries in supported countries at the Lite, Shareholders, or UBOs tier, [priced per country](/getting-started/kyb-registry-pricing) |
| **UBO identification** | Automatically extract beneficial ownership structures with percentages |
| **Officer screening** | Identify and verify directors, secretaries, and other officers |
| **Company AML** | Screen the company entity against sanctions and watchlists |
| **Person AML** | Screen all identified UBOs and officers individually |
| **KYC linking** | Link each identified person to a User Verification (KYC) session for identity verification |
| **Document collection** | Structured document requests with upload tracking |
| **OCR cross-checking** | Extract data from uploaded documents and cross-reference with registry data |
| **Business profiles** | Automatic profile creation and session aggregation for each verified company |
| **Feature-level statuses** | Independent status tracking for registry, AML, documents, and key people |
| **Risk assessment** | Overall risk level based on company status, AML results, and ownership complexity |
| **Webhooks** | Receive session lifecycle and data update events in real time via [webhooks](/integration/webhooks) |
## Business sessions vs. user sessions
| Feature | User session | Business session |
| --------------------- | ----------------------------------- | ------------------------------------------------------------ |
| **Target** | Individual person | Company or legal entity |
| **Session kind** | `user` (default) | `business` |
| **Core checks** | ID, liveness, face match, AML | Registry lookup, UBO, officers, company AML |
| **Additional checks** | Phone, email, proof of address, NFC | Document collection, person AML, KYC linking |
| **Webhook payloads** | Standard session events | Include `session_kind: "business"` and `business_session_id` |
## Next steps
Track companies across sessions with profiles, tags, and bulk operations.
Registry lookups, company status, and data cross-referencing.
UBO identification, corporate shareholders, and ownership chains.
Director identification, roles, and KYC linking.
Company and person-level AML checks.
Document collection, requests, and OCR verification.
# Ownership & UBO
Source: https://docs.didit.me/business-verification/ownership
Extract Ultimate Beneficial Owners (UBOs) from company registries with ownership chains, percentages, and automated AML screening. From $2.00 per check, priced per country and tier.
Identifying who ultimately owns and controls a company is the heart of KYB. Didit pulls the ownership structure from the registry where available, lets the business admin confirm and extend it in the hosted flow, and then runs role-aware verification on each key person.
## Beneficial owners (UBOs)
An **Ultimate Beneficial Owner (UBO)** is a natural person who ultimately owns or controls a legal entity. UBOs surface in the session decision under `key_people_checks[].registry.beneficial_owners[]` (registry-derived) and `key_people_checks[].submitted.parties[]` (user-submitted), with any other role such as shareholder or director in the same list.
## Data collected for each key person
The hosted flow collects a default set of fields plus any workflow-configured custom fields. The defaults:
| Field | Person | Company |
| ------------------------------------------------- | :--------: | :-------------------------: |
| `name` or `first_name` + `last_name` | ✅ required | ✅ required (`company_name`) |
| `email` | ✅ required | ✅ required |
| `nationality` / country | ✅ required | ✅ required |
| `role` (one or more per party) | ✅ required | ✅ required |
| `ownership_percent` / `voting_percent` (per role) | optional | optional |
| `date_of_birth` | optional | — |
| `phone_number` | optional | — |
| `position` (free-text job title) | optional | — |
| `registration_number` | — | optional |
| `entity_type` | `person` | `company` |
`entity_type` is set automatically based on whether the party is a natural person or a corporate entity.
### Custom fields
Workflows can require additional fields per person or per company — any number of them. They're configured in the Business Console on the KYB workflow and end up in the `custom_fields` object on each party in the decision response. Use custom fields for things like source-of-wealth declarations, tax residency, internal client IDs, or industry-specific disclosures.
## Corporate shareholders and nested ownership
When a shareholder is itself a company (not a natural person), Didit records:
* Company name and registration number.
* Ownership percentage held by that corporate shareholder.
* Country of incorporation.
This lets you trace the chain through multiple layers to the ultimate natural persons.
### Nested KYB for corporate UBOs
When the workflow enables nested KYB for corporate UBOs, each corporate party triggers its own child KYB sub-session — the company verifies under the same process you run at the top level, and its result feeds back into the parent. Off by default; opt in per workflow.
## Ownership thresholds
Most jurisdictions classify UBOs at **25% or more**. The threshold is configurable per workflow:
| Jurisdiction | Typical UBO threshold |
| ------------ | ------------------------------------ |
| EU (AMLD) | 25% ownership or control |
| UK | 25% shares or voting rights |
| USA (CTA) | 25% ownership or substantial control |
| Singapore | 25% shares or voting power |
Set it from *Workflows → \[KYB workflow] → Risk → UBO threshold*. Your workflow can also flag or route for review when no UBO meets the threshold (common with widely-held public companies).
## Voting vs economic rights
Ownership disclosures distinguish two kinds of control:
| Right | What it means |
| -------------------------- | ------------------------------------------------------- |
| **Economic (ownership) %** | Share of the company's economic value / dividend rights |
| **Voting %** | Share of voting power on shareholder resolutions |
These are usually equal but can diverge when a company issues dual-class shares or preferred equity. Didit records both. Rules and thresholds can check either — most jurisdictions require UBO disclosure when *either* crosses 25%.
## Effective ownership through a chain
When ownership runs through a corporate shareholder, effective ownership of a downstream natural person is computed by multiplying percentages along the chain.
### Example — 3-layer chain
```
Acme Corp (target)
└─ 60% → Acme Holdings Ltd
└─ 75% → Jane Doe (natural person)
└─ 40% → John Smith (natural person)
```
Computed UBOs:
* **Jane Doe** — effective ownership of Acme Corp = 60% × 75% = **45%** (UBO).
* **John Smith** — effective ownership of Acme Corp = **40%** (UBO).
With a 25% UBO threshold, both Jane and John are disclosed. With a 50% threshold, no UBO meets the cut — Didit flags this as a transparency concern.
## How role settings drive verification
Each role is individually configured per KYB workflow: enabled or disabled, whether it requires KYC, which KYC workflow to use for that role, and whether the end user can choose to skip it. When a key person is submitted:
1. Didit checks the submitted party's roles against the workflow's role settings.
2. For roles marked as requiring verification, a child verification session is started:
* **Person parties** → child User Verification (KYC) session, using either the role-specific KYC workflow or the default.
* **Corporate UBOs** (when nested KYB is enabled) → child Business Verification (KYB) sub-session.
3. For roles marked as skippable, the end user sees a "skip verification" option and can opt out; skipped parties are recorded but don't block the parent session.
4. Parties below the workflow's ownership threshold don't trigger individual verification even if the role would otherwise require it.
Configure everything from *Workflows → \[KYB workflow] → Key People* in the Business Console.
## Monitoring shareholders and UBOs
Registry monitoring is **coming soon**. No country in the staging retail catalog checked on September 7, 2026 has monitoring enabled. The configured rate is USD 2.00 per company per year; a price does not mean the feature is available. The behavior below describes the planned service after rollout.
A company verified at the **Shareholders** or **UBO** tier can be kept under **continuous registry monitoring**. The registry pushes changes of company status, officers, ownership and registered address; on each change Didit:
* moves the business session from *Approved* back to *In Review*, with the change recorded on the session;
* fires the `kyb_registry_change` [business webhook](/business-verification/webhooks) with the fields that changed;
* keeps the company's registry record current.
Monitoring is **only available where the country allows it**: the registry provider must offer a watch for that jurisdiction. The full list is the [continuous monitoring countries table](/business-verification/supported-countries#continuous-registry-monitoring); a company verified at the Lite tier, or in a country outside that list, is not enrolled and the business page shows why.
Enable it per workflow from *Workflows → KYB Registry Check → Advanced → Continuous registry monitoring*. It costs **\$2.00 per company per year**, billed on enrolment and on each yearly renewal; when the wallet cannot cover a renewal, monitoring stops for that company instead of running unpaid. Cancel it at any time from the business page (*KYB Registry Check → Registry monitoring*).
## Registry vs user-submitted reconciliation
The response surfaces both sources side-by-side under `key_people_checks[]`:
* `registry.officers[]` and `registry.beneficial_owners[]` — what the provider disclosed.
* `submitted.parties[]` — what the business admin explicitly submitted through the flow, with `source: "USER"`.
Analysts compare the two buckets visually in the console to spot missing UBOs, extra undisclosed parties, or ownership percentages that don't line up. There's no automated diff endpoint — you can reconcile on your side by matching on name, role, and ownership where applicable.
## AML screening
Every identified key person is automatically screened against global AML watchlists, both from the registry side and the submitted side. See [Business AML screening](/business-verification/aml) for how company-level and person-level AML checks combine.
## Next steps
The Key People flow end-to-end.
Governance roles inside the key-people model.
How ownership complexity rolls into risk level.
# KYB quickstart: business verification in 10 minutes
Source: https://docs.didit.me/business-verification/quickstart
Verify a business end-to-end in under 10 minutes — registry lookup, UBO, officers, AML, and webhooks. From $2.00 per check, priced per country and tier, no contracts.
This guide walks you through a complete KYB verification in under 10 minutes. You'll create a workflow, start a session, deliver the hosted verification link to your business contact, wait for the webhook, and fetch the final decision.
## Prerequisites
* A Didit application with KYB enabled. Create one at [business.didit.me](https://business.didit.me) if needed.
* An API key from *Developer Settings → API Keys*.
* A webhook endpoint reachable from the internet (for the decision webhook).
## Steps
Navigate to *Workflows → Create workflow*. Choose **Business Verification** as the workflow type.
Enable the features you need:
* **Company registry lookup** (required)
* **Company AML** (recommended)
* **Key People** (required for most regulated industries)
* **Documents** (optional — pick document types you'll require)
Save and copy the `workflow_id` — you'll use it in the next step.
```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workflow_id": "YOUR_KYB_WORKFLOW_ID",
"vendor_data": "biz-acme-001"
}'
```
The response contains the `session_id`, `session_number`, and `url` — the hosted verification link you deliver to the business contact. The workflow's type (KYB) automatically flags the session as a business session.
Deliver the `url` to the company admin via your own email or chat. They open it, fill in the registry data, add key people and UBOs, and upload documents.
You can customize the hosted experience with branding, logo, and domain — see [White-label](/console/white-label).
When the business finishes the flow and KYB processing reaches a terminal status, Didit POSTs a `status.updated` event to your webhook endpoint. Business-session events carry `session_kind: "business"` inside `data` — filter on that to route the event to your KYB handler.
Verify the `X-Signature-V2` HMAC signature before trusting the event (see [webhooks](/integration/webhooks)), then trigger your downstream logic. Store the destination's `secret_shared_key` (returned when you register the webhook) as `DIDIT_WEBHOOK_SECRET` in your `.env`.
Example payload:
```json theme={null}
{
"event": "status.updated",
"application_id": "app_abc123",
"timestamp": "2026-04-18T12:30:00Z",
"data": {
"session_id": "bs_01H...",
"session_kind": "business",
"vendor_data": "biz-acme-001",
"status": "APPROVED",
"previous_status": "IN_PROGRESS"
}
}
```
The `status.updated` webhook payload already contains the full decision. You only need to call this endpoint if you didn't subscribe to webhooks, or you want to fetch the decision on demand later.
```bash theme={null}
curl https://verification.didit.me/v3/session/bs_01H.../decision/ \
-H "x-api-key: YOUR_API_KEY"
```
The decision includes registry data, key people (UBOs, shareholders, directors and representatives), AML hits, documents, and per-feature results. See [response schema](/business-verification/response-schema) for the full payload reference.
Based on `status`:
* `APPROVED` — onboard the business.
* `DECLINED` — reject and log the reason code.
* `IN_REVIEW` — wait for the analyst decision; you'll receive another webhook when it resolves.
## Node.js example
```typescript theme={null}
import fetch from 'node-fetch';
const API_KEY = process.env.DIDIT_API_KEY!;
const WORKFLOW_ID = process.env.DIDIT_KYB_WORKFLOW_ID!;
const BASE_URL = 'https://verification.didit.me';
async function startKybVerification(vendorData: string) {
const res = await fetch(`${BASE_URL}/v3/session/`, {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ workflow_id: WORKFLOW_ID, vendor_data: vendorData }),
});
if (!res.ok) throw new Error(`Session create failed: ${res.status}`);
return res.json();
}
async function getDecision(sessionId: string) {
const res = await fetch(`${BASE_URL}/v3/session/${sessionId}/decision/`, {
headers: { 'x-api-key': API_KEY },
});
return res.json();
}
// Kick off the flow
const { session_id, url } = await startKybVerification('biz-acme-001');
console.log('Deliver this URL to your contact:', url);
// ... later, after the webhook confirms completion:
const decision = await getDecision(session_id);
console.log('KYB status:', decision.status);
```
## What happens automatically
* Company registry lookup against 190+ jurisdictions.
* UBO and officer extraction from registry data.
* Company-level AML screening (sanctions, PEP, adverse media).
* Person-level AML for each identified party.
* Document OCR and cross-referencing.
* [Business profile](/business-verification/business-profiles) creation and aggregation keyed by `vendor_data`.
## Next steps
End-to-end architecture, polling vs webhooks, retry logic.
Every KYB event and payload shape.
Session, feature, and registry status reference.
The full KYB decision payload.
How profiles aggregate across sessions.
# KYB Response Schema
Source: https://docs.didit.me/business-verification/response-schema
Annotated KYB decision payload: registry checks, key people, UBO KYC summaries, documents, AML screenings, and shared fields. From $2.00 per check, priced per country and tier.
This page documents the full payload returned by [`GET /v3/session/{id}/decision/`](/sessions-api/retrieve-session) for a Business Verification (KYB) session. The endpoint is unified — when you pass a business `session_id`, the response carries `session_kind: "business"` and the business-specific feature arrays below.
## Top-level structure
```json theme={null}
{
"session_id": "bs_01H...",
"session_kind": "business",
"session_number": 89,
"session_url": "https://verify.didit.me/...",
"status": "APPROVED",
"workflow_id": "wf_kyb_standard",
"features": ["KYB_REGISTRY", "KYB_COMPANY_AML", "KYB_DOCUMENTS", "KYB_KEY_PEOPLE"],
"vendor_data": "biz-acme-001",
"metadata": { },
"callback": null,
"registry_checks": [ ... ],
"aml_screenings": [ ... ],
"document_verifications": [ ... ],
"key_people_checks": [ ... ],
"phone_verifications": null,
"email_verifications": null,
"questionnaire_responses": null,
"ip_analyses": null,
"reviews": [ ],
"contact_details": { ... },
"expected_details": null,
"created_at": "...",
"expires_at": "..."
}
```
All feature arrays are multi-instance — each item represents one node in a graph workflow. Null means the feature was not present in this session's workflow.
## Shared top-level fields
| Field | Type | Description |
| -------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `session_id` | UUID | Unique session identifier |
| `session_kind` | enum | Always `"business"` for KYB sessions |
| `session_number` | int | Sequential number within the application |
| `session_url` | URL | Hosted verification link |
| `status` | enum | Overall session status (see [statuses](/business-verification/statuses)) |
| `workflow_id` | UUID | Workflow that drove this session |
| `features` | array of strings | Feature identifiers the workflow ran (e.g. `KYB_REGISTRY`, `AML`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`, `PHONE`, `EMAIL`) |
| `vendor_data` | string | Your identifier for the business |
| `metadata` | object | Free-form JSON you attached at session creation |
| `callback` | URL or null | Optional post-verification redirect URL |
| `contact_details` | object or null | `{ email, email_lang, send_notification_emails, phone }` — set when the flow includes email/phone notifications |
| `expected_details` | object or null | Business expected-data payload you passed on creation: `company_name`, `registry_country` (`XX` or `XX-YY` with the state for state-level registries, e.g. `US-CA`), `registration_number` |
| `reviews` | array | Latest reviewer notes on the session |
| `created_at`, `expires_at` | ISO timestamps | — |
## `registry_checks[]`
Each item is one company registry check (typically one per session; multiple in graph workflows with recursive corporate UBO KYB).
```json theme={null}
{
"status": "Approved",
"node_id": "feature_kyb_registry",
"data_resolved": true,
"company": { ...see below... },
"ownership_structure": { },
"warnings": []
}
```
| Field | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `status` | Feature lifecycle status (`Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`) |
| `node_id` | Graph node identifier for this check |
| `data_resolved` | `true` when registry data has finished loading or the user provided the data manually |
| `company` | Full registry payload — see **`company` block** below |
| `ownership_structure` | Raw ownership-chain JSON as returned by the registry provider |
| `warnings` | Structured warnings / log entries scoped to this registry check. See [Business Verification warnings](/business-verification/warnings). |
### `company` block
The nested company object is the full view of the legal entity as derived from the registry (and optionally user-edited):
```json theme={null}
{
"uuid": "...",
"node_id": "feature_kyb_registry",
"status": "Approved",
"registry_status": "active",
"data_resolved": true,
"company_name": "Acme Corporation Limited",
"registration_number": "12345678",
"country_code": "GBR",
"company_type": "Private Limited",
"incorporation_date": "2010-05-14",
"registered_address": "1 Main Street, London EC1A 1AA, United Kingdom",
"tax_number": "SAMPLE-TAX-12345",
"risk_level": "LOW",
"verification_status": "verified",
"is_from_registry": true,
"fetch_status": "resolved",
"alternative_names": "Acme Ltd",
"nature_of_business": "Software publishing",
"registered_capital": "100000 GBP",
"website": "https://acme.example",
"email": "alex.sample@example.com",
"phone": "+15550101000",
"legal_entity_identifier": "529900XXXXXXXXXXXXX",
"location_of_registration": "London",
"vat_number": "GB123456789",
"vat_validation_status": "not_applicable",
"vat_validated_name": null,
"vat_validated_address": null,
"vat_checked_at": null,
"financial_summary": { },
"officers": [ ... ],
"beneficial_owners": [ ... ],
"addresses": [ ... ],
"industries": [ ... ],
"accounts": { ... },
"registry_data": { },
"user_provided_data": { },
"confirmed_by_user_at": "2026-04-16T10:10:00Z",
"last_console_edit_at": null,
"is_editable": false
}
```
| Field | Description |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `registry_status` | Provider-reported status (`active`, `dissolved`, `struck_off`, `inactive`, etc.). See [statuses](/business-verification/statuses#registry-status-from-the-provider) |
| `is_from_registry` | `true` when data came from an official registry; `false` for manual entry |
| `fetch_status` | `pending`, `resolved`, or `failed` — registry lookup state |
| `verification_status` | `verified`, `failed`, or `unknown` |
| `is_editable` | `true` while registry-sourced data can still be edited by the end user |
| `registry_data` / `user_provided_data` | Raw JSON for registry-returned vs user-entered fields (useful for audit diffing) |
| `confirmed_by_user_at` | Timestamp when the end user confirmed / edited the registry data |
| `vat_number` | VAT number as submitted, when collected |
| `vat_validation_status` | Result of the EU VIES check: `valid`, `invalid`, `could_not_validate`, or `not_applicable` (non-EU companies, or no VAT number provided). See [VAT validation](/business-verification/company-data#vat-validation-vies) |
| `vat_validated_name` / `vat_validated_address` | Trader name and address registered with VIES, when the member state discloses them |
| `vat_checked_at` | Timestamp of the VIES check; `null` when no check ran |
| `officers[]` | Officers with embedded KYC enrichment (see below) |
| `beneficial_owners[]` | UBOs with embedded KYC enrichment (see below) |
| `addresses`, `industries`, `accounts` | Extracted from `registry_data` when available |
When reported, `industries` includes classification entries with `code` and `description`, and `addresses` includes entries with `address`, `type`, and `description`.
`accounts` can include `last_annual_return_date` and `last_agm_date`; these describe corporate filings and meetings, not financial statements.
Industry descriptions or a registry business scope populate `nature_of_business`.
Contact details and alternative names are populated when available, while explicit user or reviewer edits take precedence.
### Officer item shape (inside `company.officers`)
```json theme={null}
{
"uuid": "...",
"name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"date_of_birth": null,
"date_of_birth_year": 1983,
"date_of_birth_month": 2,
"address": "1 Example Street, London",
"appointment_date": "2024-07-01",
"termination_date": null,
"designation": "Director",
"role": "director",
"nationality": "USA",
"is_active": true,
"kyc_status": "Approved",
"kyc_session_url": "https://verify.didit.me/..."
}
```
`date_of_birth` is populated only when the registry supplies a complete date.
If the registry reports only a year or month and year, use `date_of_birth_year` and `date_of_birth_month`; the missing day is never inferred.
Names, birth details, address, and appointment dates remain available in both the company officer list and the Key People registry list.
### Beneficial owner item shape (inside `company.beneficial_owners`)
```json theme={null}
{
"uuid": "...",
"name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"entity_type": "person",
"date_of_birth": null,
"country_of_birth": null,
"email": null,
"company_name": null,
"registration_number": null,
"nationality": null,
"designation": null,
"appointment_date": null,
"voting_min_shares": null,
"voting_max_shares": null,
"roles": ["ubo"],
"ownership_min_shares": "40",
"ownership_max_shares": "40",
"is_active": true,
"kyc_status": "Pending",
"kyc_session_url": "https://verify.didit.me/...",
"effective_ownership_percent": 40.0
}
```
`kyc_status` uses a B2C-safe projection: `Approved`, `Declined`, or `Pending` (full session states collapse to `Pending` until the KYC finishes).
## `key_people_checks[]`
This is the **aggregate check** over all officers and UBOs for the session. It exposes two buckets so the console can render "extracted from the registry" side-by-side with "submitted by the business", plus a high-level UBO KYC summary.
```json theme={null}
{
"status": "Approved",
"node_id": "feature_kyb_key_people",
"registry": {
"officers": [ ...registry-derived officers... ],
"beneficial_owners": [ ...registry-derived UBOs... ]
},
"submitted": {
"parties": [ ...user-submitted key-people records... ]
},
"ubo_kyc_summary": {
"total": 2,
"approved": 1,
"flagged": 0,
"pending": 1
},
"warnings": []
}
```
* **`registry.officers` / `registry.beneficial_owners`** — parties extracted from the registry check.
* **`submitted.parties`** — parties the business admin explicitly added during the Key People flow (records with `source = "USER"`).
### `registry.officers[]` / `registry.beneficial_owners[]`
The registry facts match `company.officers` / `company.beneficial_owners` above.
Key People additionally includes AML status and detailed verification linkage, such as `kyc_session_id` and verification workflow fields.
The company lists retain their existing simplified KYC status and verification URL.
### `submitted.parties[]`
User-submitted parties carry their KYC or child-KYB linkage:
```json theme={null}
{
"uuid": "...",
"entity_type": "person",
"name": "Alice Chen",
"first_name": "John",
"last_name": "Doe",
"email": "alex.sample@example.com",
"role": "ubo",
"ownership_percent": 35.0,
"nationality": "USA",
"position": "CEO",
"date_of_birth": "1990-01-01",
"phone_number": "+34612345678",
"source": "USER",
"requires_verification": true,
"is_skipped": false,
"kyc_session_id": "sess-xyz...",
"kyc_session_status": "Not Started",
"kyc_session_url": "https://verify.didit.me/...",
"kyc_session_deleted": false,
"custom_fields": { }
}
```
Corporate parties (`entity_type: "company"`) additionally carry `company_name`, `registration_number`, and `kyb_sub_session_id` / `kyb_sub_session_status` / `kyb_sub_session_deleted` when nested KYB is enabled for corporate UBOs.
`kyc_session_deleted` (and its corporate counterpart `kyb_sub_session_deleted`) is `true` when the party's child session was deleted. A deleted child keeps whatever status it had at deletion time and can never settle, so read this flag rather than the status: the matching `*_session_url` is returned as `null` because the link no longer resolves, and the Key People check drops to `In Review` instead of waiting forever. Deleting a child session never counts as a pass. Exempt the party with [`PATCH /v3/session/{id}/kyb/parties/{party_uuid}/`](/sessions-api/update-data#skip-a-key-people-party) to let the KYB settle.
### `ubo_kyc_summary`
Aggregate UBO KYC progress — useful for a single dashboard card:
| Field | Meaning |
| ---------- | ---------------------------------------------------- |
| `total` | Number of active UBOs that have a linked KYC session |
| `approved` | UBOs whose KYC session is `APPROVED` |
| `flagged` | UBOs whose KYC session is `DECLINED` or `IN_REVIEW` |
| `pending` | Everyone else (not yet finished) |
Returns `null` when no UBOs have linked KYC sessions.
## `document_verifications[]`
Documents grouped by node:
```json theme={null}
{
"status": "Approved",
"node_id": "feature_kyb_documents",
"items": [
{
"uuid": "...",
"document_type": "certificate_of_incorporation",
"status": "Approved",
"file_url": "https://didit-storage.s3...",
"document_metadata": {
"file_size": 246810,
"content_type": "application/pdf",
"creation_date": "2024-05-15",
"modified_date": "2024-05-15",
"overlay_manipulation": null
},
"ocr_data": {
"company_name": "Acme Corporation Limited",
"registration_number": "12345678",
"incorporation_date": "2010-05-14"
}
}
],
"groups": {
"legal_presence": { "approved": 1, "pending": 0, "missing": 0 },
"ownership_structure": { "approved": 1, "pending": 0, "missing": 0 }
},
"required_groups": ["legal_presence", "ownership_structure"],
"warnings": []
}
```
| Field | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `items[]` | Individual document rows — each has its own `status`, `file_url` (signed), `document_metadata`, and OCR extract |
| `groups` | Per-group progress counters — matches the [four KYB document groups](/business-verification/documents#document-groups) |
| `required_groups` | Groups your workflow marked as required; gate session approval on all being satisfied |
### Corporate document metadata
For PDF corporate documents, `items[].document_metadata.overlay_manipulation` can include forensic evidence for suspected overlay-text editing. When present, it includes:
| Field | Description |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `detected` | Whether overlay-text manipulation evidence was detected |
| `analyzed` | Whether the PDF could be analyzed |
| `signals` | Forensic signals that fired, such as `duplicate_font_subset` or `glyph_fragmentation` |
| `manipulated_regions` | PDF page-coordinate rectangles around suspected edited text: `page`, `x`, `y`, `width`, `height`, `page_width`, `page_height` |
The field is `null` when no overlay evidence was found, when the document is not a PDF, or when the PDF could not be analyzed.
## `aml_screenings[]`
Company-level and (when the workflow wires them) person-level AML screenings.
```json theme={null}
{
"node_id": "feature_kyb_company_aml",
"status": "Approved",
"total_hits": 0,
"score": 0,
"entity_type": "COMPANY",
"screened_data": {
"company_name": "Acme Corporation Limited",
"country_code": "GBR"
},
"is_ongoing_monitoring_enabled": true,
"next_ongoing_monitoring_bill_date": "2027-04-16",
"hits": [],
"warnings": []
}
```
When `total_hits > 0`, each hit includes match score, risk score, watchlist sources, and a per-hit `review_status` you can update via the update-aml-hit-status endpoint. See [AML Screening report](/core-technology/aml-screening/report-aml-screening) for the hit structure.
## Shared feature arrays
`phone_verifications`, `email_verifications`, `questionnaire_responses`, `ip_analyses` use the same per-item shapes as user (KYC) sessions. They're `null` when the workflow doesn't include them. See the [KYC response](/sessions-api/retrieve-session) for item schemas.
## Warning feature groups
Business Verification warnings are grouped by the feature that produced them:
* `KYB_REGISTRY` for registry availability, ownership, company activity, and country-restriction issues.
* `KYB_DOCUMENTS` for corporate document extraction, cross-check, metadata, manipulation, subtype, age, and attempt-limit issues.
* `AML` for company AML screening matches or missing screening data.
* `KYB_KEY_PEOPLE` for person-level issues tied to directors, officers, UBOs, or representatives.
* `PHONE` for phone-number risk, duplication, blocklist, and verification-code attempt issues.
* `EMAIL` for email risk, deliverability, duplication, blocklist, and verification-code attempt issues.
* `QUESTIONNAIRE` for custom status rules driven by questionnaire answers.
* `LOCATION` for Device & IP Analysis, private-network, IP blocklist, device blocklist, and duplicate device/IP issues.
See [Business Verification warnings](/business-verification/warnings) for the full list of warning codes.
## Workflow-driven field filtering
Workflows can limit which nested fields appear on each feature item via `response_attributes`. Fields always included regardless: `status`, `warnings`, `node_id`. All other fields are nullified when not in the allow-list — the key stays in the response for schema stability.
## Next steps
What each status value means.
How `risk_level` is computed.
Full AML hit payload reference.
How parties flow from registry vs user submission into `registry` / `submitted` buckets.
Document groups, OCR, and cross-reference.
Full endpoint reference including example responses.
# Risk Assessment
Source: https://docs.didit.me/business-verification/risk-assessment
Composite KYB risk level (LOW/MEDIUM/HIGH) and ownership complexity scoring across registry, AML, key people, documents, and country risk. From $2.00 per check, priced per country and tier.
Every KYB decision includes two composite risk signals on the registry check: `risk_level` (`LOW`, `MEDIUM`, `HIGH`) and `ownership_complexity` (`SIMPLE`, `COMPLEX`). They give your compliance team a quick directional read of the business without having to re-score each sub-check on your side.
## Inputs to `risk_level`
`risk_level` is a composite Didit computes from the checks that actually ran on the session. The main signals:
| Signal | Contribution |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Registry status** | An `ACTIVE` / `AUTHORISED` company sits at the low end. `DISSOLVED`, `STRUCK_OFF`, `CLOSED`, `UNAUTHORISED` push toward high. `INACTIVE` or statuses that require manual inspection sit in the middle. |
| **Company AML screening** | The company's AML score and hits count. Clean screens sit low; confirmed sanction or adverse-media matches push high. |
| **Person AML across key people** | The worst AML result across UBOs, directors, and other key people. Any confirmed sanction / PEP match pushes high. |
| **Document completeness** | Whether every required document group is approved, vs missing or failed documents. |
| **Country of incorporation** | Country risk tier, based on FATF and OFAC guidance and your dangerous-country policy. |
When `risk_level` is `HIGH`, the workflow typically routes the session to `IN_REVIEW` or `DECLINED` depending on your configuration.
Exact weighting evolves over time as we tune the model. Treat `risk_level` as a directional signal and combine it with your own policy before making onboarding decisions.
## Inputs to `ownership_complexity`
`ownership_complexity` reflects how hard the ownership structure is to unpack. Simple structures have direct natural-person ownership with few UBOs and no opaque intermediaries; complex structures run through multiple corporate layers, cross-border holdings, or trust / foundation / nominee entities.
| Value | Typical shape |
| --------- | ------------------------------------------------------------------------------------------------ |
| `SIMPLE` | Direct natural-person ownership, few UBOs above threshold, no opaque corporate intermediaries |
| `COMPLEX` | Multi-layer corporate chains, cross-border ownership, or trust / foundation / nominee structures |
`COMPLEX` doesn't automatically imply high risk — but it usually warrants enhanced due diligence.
## Configuration
Risk thresholds and related defaults are configurable per workflow in the Business Console:
* *Workflows → \[KYB workflow] → Risk → AML thresholds* — set the match-score and risk-score thresholds for approve/review/decline. See [AML risk score](/core-technology/aml-screening/aml-risk-score) and [AML match score](/core-technology/aml-screening/aml-match-score).
* *Workflows → \[KYB workflow] → Risk → UBO threshold* — ownership percentage at which someone is classified as a UBO (typically 10%, 25% or 50%).
* *Workflows → \[KYB workflow] → Risk → Document policy* — which documents are required vs optional.
* *Settings → Blocklist → Countries* — the dangerous-country list used in the country input.
## Illustrative examples
The examples below are illustrative of how different signals land — not an exact formula.
### Simple UK Ltd, clean AML
* Registry `ACTIVE`
* Company AML clean
* All key people AML clean
* Documents complete
* Country `GBR`
→ `risk_level = LOW`, `ownership_complexity = SIMPLE`. Approved.
### Multi-layer holding with a PEP UBO
* Registry `ACTIVE`
* Company AML clean
* One UBO has an `IN_REVIEW` PEP hit
* Documents complete
* Country `LUX`
→ `risk_level = MEDIUM`, `ownership_complexity = COMPLEX` (multi-layer chain). Session routes to `IN_REVIEW`.
### Sanctioned subsidiary detected
* Registry `ACTIVE`
* Company AML: confirmed sanctions match
* One director on a sanctions list
→ `risk_level = HIGH`. Session auto-declined with reason `aml_confirmed_sanction`.
## Risk color coding in the console
* 🟢 `LOW` — green badge.
* 🟡 `MEDIUM` — yellow badge.
* 🔴 `HIGH` — red badge.
`COMPLEX` ownership is shown alongside the risk badge with a small chain icon.
## Using the risk fields on your side
```typescript theme={null}
if (decision.status === 'APPROVED' && decision.risk_level === 'HIGH') {
// Approved by the engine but still worth a manual reviewer glance
await routeToEnhancedDueDiligence(decision);
}
if (decision.ownership_complexity === 'COMPLEX') {
await requireExtraDocumentation(decision);
}
```
## Next steps
Status taxonomy.
Two-score system used throughout Didit.
Where risk fields appear.
# KYB Statuses
Source: https://docs.didit.me/business-verification/statuses
Full reference for KYB session, feature, registry lookup, AML statuses, and decision reason codes. From $2.00 per check, priced per country and tier.
Business Verification tracks status at multiple levels: the **session**, individual **features**, the **registry lookup**, and the **AML result**. This page is the reference for every state.
## Session status
The overall status of a Business Verification (KYB) session — the single field you'll most often switch on.
| Status | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NOT_STARTED` | Session created but business has not opened the hosted link. |
| `IN_PROGRESS` | Hosted flow is active — registry lookups, document uploads, or AML screening in progress. |
| `AWAITING_USER` | Waiting for the end user to take an action (typically submit Key People, or respond to a resubmission request). Transitions automatically once the user completes the pending action. |
| `APPROVED` | All required features passed. Business is verified. |
| `DECLINED` | A critical check failed (dissolved company, sanctioned entity, blocklisted) or workflow rule rejected. |
| `IN_REVIEW` | One or more features require manual analyst review. |
| `RESUBMITTED` | Previously declined/resub-requested session has been resubmitted with updated data. |
| `ABANDONED` | Business never completed the flow (timeout). |
| `EXPIRED` | Session expired before the business completed it. |
`AWAITING_USER` is transient — you don't need to manually move sessions out of it. As soon as the end user completes the pending action (for example, submits the Key People list or re-uploads a requested document), Didit re-evaluates the session and transitions it automatically to `IN_REVIEW`, `APPROVED`, or `DECLINED` based on the outcome.
## Feature status
Each KYB feature has its own status, which rolls up into the session status.
| Status | Meaning |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NOT_FINISHED` | Feature has not yet been started or is mid-run. |
| `APPROVED` | Feature passed. |
| `DECLINED` | Feature failed (critical). |
| `IN_REVIEW` | Feature requires analyst attention. |
| `RESUB_REQUESTED` | Business must resubmit for this feature (e.g. re-upload a document). |
| `AWAITING_USER` | Feature is waiting for an end-user action. Applies to the Key People feature while the user must submit parties. Automatically transitions on submission. |
### KYB feature names
* `kyb_registry` — company registry lookup.
* `kyb_company_aml` — company-level AML screening.
* `kyb_documents` — document collection and OCR.
* `kyb_key_people` — key people: UBOs, shareholders, directors, representatives and other officers.
## Registry status (from the provider)
The official status of the company as obtained from authoritative registry sources. Reflects the legal or regulatory standing of the entity as reported by the jurisdiction-specific registry, so exact values vary by source. Surfaced inside the KYB decision under `company.registry_status`.
| Status | Meaning |
| -------------------------- | ---------------------------------------------------- |
| `ACTIVE` | Company is active and in good standing. |
| `DISSOLVED` | Company has been dissolved. |
| `DEREGISTERED` | Company has been deregistered. |
| `STRUCK_OFF` | Company has been struck from the registry. |
| `CLOSED` | Company has been closed. |
| `INACTIVE` | Company is inactive (non-trading). |
| `AUTHORISED` | Authorised (typically for regulated entities). |
| `APPOINTED_REPRESENTATIVE` | Appointed representative of another authorised firm. |
| `UNAUTHORISED` | Previously authorised, now unauthorised. |
| `NO_LONGER_AUTHORISED` | Authorisation revoked. |
| `SEE_FULL_DETAILS` | Status requires manual inspection. |
Because `registry_status` reflects raw registry values, use `verification_status` below when you need a consistent, cross-jurisdiction indicator to drive product logic.
## Company verification status
A standardized, business-facing status per company result. Normalizes the varying registry and company statuses across different countries and sources into a single consistent indicator that can be interpreted directly within your product. Surfaced inside the KYB decision under `company.verification_status`.
| Status | Meaning |
| ---------- | ------------------------------------------------------------------ |
| `verified` | The company appears active and valid. |
| `failed` | The company appears inactive, dissolved, or has issues identified. |
| `unknown` | There is insufficient information or the status is ambiguous. |
## Company fetch status
Indicates the data retrieval progress for a specific company record from external or official registries. Use it to determine how complete the fetched information is for a given company. Surfaced inside the KYB decision under `company.fetch_status`.
| Status | Meaning |
| ---------- | ------------------------------------------------------------------------------------- |
| `pending` | Only partial or basic company information has been retrieved at this stage. |
| `resolved` | Full and complete company data has been successfully retrieved from official sources. |
## AML verification status
Returned per screening (company or person).
| Status | Meaning |
| -------------- | ---------------------------------------------------- |
| `APPROVED` | No hits above threshold. |
| `DECLINED` | High-severity hit (confirmed sanction or PEP match). |
| `IN_REVIEW` | Hit found that requires analyst review. |
| `NOT_FINISHED` | Screening has not completed. |
## Decision reason codes
When a session is `DECLINED`, the decision includes a machine-readable `decision_reason_code`. Common codes:
| Code | Meaning |
| ---------------------------- | -------------------------------------------------------------- |
| `blocked_country` | Country of incorporation is in the dangerous countries list. |
| `blocked_business` | The `vendor_data` matches a Business blocklist entry. |
| `registry_company_not_found` | Company could not be located in any supported registry. |
| `registry_company_dissolved` | Registry reports company is dissolved. |
| `registry_mismatch` | Registry data contradicts the customer-provided data. |
| `aml_confirmed_sanction` | Confirmed sanctions match on the company or a key person. |
| `aml_confirmed_pep` | Confirmed PEP match on a key person. |
| `documents_failed_ocr` | Uploaded documents could not be OCR'd or fail cross-reference. |
| `documents_incomplete` | Required documents missing. |
| `key_people_incomplete` | UBOs or officers below threshold could not be identified. |
| `analyst_rejected` | An analyst manually declined after review. |
| `timeout_abandoned` | Business did not complete the flow within the SLA. |
Additional workflow-specific reason codes may appear — see your workflow configuration for the full list.
## Ownership and complexity signals
Returned in the decision alongside status, for informational use:
| Field | Description |
| ---------------------- | ------------------------------------------------------------------------------- |
| `risk_level` | `LOW`, `MEDIUM`, `HIGH` — composite of registry, AML, and ownership complexity. |
| `ownership_complexity` | `SIMPLE`, `COMPLEX` — based on number of ownership layers and entity types. |
See [risk assessment](/business-verification/risk-assessment) for how these fields are computed.
## Next steps
Where each status appears in the payload.
How statuses combine into a risk level.
Events that fire on status changes.
# Supported Countries
Source: https://docs.didit.me/business-verification/supported-countries
KYB registry coverage across 220+ countries, with the Lite, Shareholders and UBO data tiers available per jurisdiction. From $2.00 per check, priced per country and tier.
Didit's Business Verification (KYB) provides broad global coverage for company registry lookups, sold in three data tiers — see [KYB registry pricing](/getting-started/kyb-registry-pricing) for what each tier returns and what it costs. Tier availability varies by country — the table below shows which tiers are available for each jurisdiction.
Customize which countries your workflows collect, and the tier per country, via the console [workflows](/console/workflows) — align coverage to your compliance footprint without paying for a tier you don't need.
### Tier columns
| Column | What it means |
| ---------------- | ----------------------------------------------------------------------------------------------------- |
| **Lite** | Company profile, identifiers, status, registered address, activity, and officers |
| **Shareholders** | Lite plus the direct shareholders (natural persons and corporate entities) with their ownership bands |
| **UBOs** | Shareholders plus the ultimate beneficial owners resolved through the ownership chain |
Cells marked ✅ indicate the tier is available for that jurisdiction; — means the registry holds no data at that tier there. A Shareholders or UBO request in a country whose registry holds no ownership record is delivered and billed at the highest tier the country's registries can serve — usually Lite. A country with no ✅ at any tier is not offered for registry checks: the applicant enters the company manually, billed at the flat manual-entry price.
***
## Supported countries and subdivisions
The table below shows all supported countries and subdivisions. The **Code** column contains the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code used across Didit KYB APIs (e.g. in `country_code` on the registry check, session creation, and business entity fields). US states use the extended `US_` format (e.g. `US_CA`, `US_NY`, `US_DE`) to select the correct Secretary of State registry.
***
## Manual entry
A country with no tier available above, or a company the registry cannot find, is not blocked — the applicant enters the company by hand and the step continues with `is_from_registry: false`, billed at the flat manual-entry price (see [KYB registry pricing](/getting-started/kyb-registry-pricing)). Some jurisdictions route these sessions to `IN_REVIEW` automatically with reason `registry_manual_required`, depending on your workflow configuration.
## US state coverage
US registries are organized per state. To run a KYB check on a US company, pass the `US_` code (e.g. `US_DE` for Delaware, `US_CA` for California, `US_NY` for New York) — every US state and DC is supported. Using just `US` falls back to a federal-only search (company name + status). Per-state coverage of officers, shareholders, and UBOs varies — Delaware, California, Texas, and New York expose the richest officer and filing data, while smaller states often return only company name, status, and registered agent.
## Dangerous / sanctioned countries
By default, companies incorporated in any country on your application's [dangerous countries](/entities/businesses/blocklist#dangerous-countries) list are auto-declined at session creation regardless of whether the registry is supported. Default list follows FATF and OFAC guidance:
* `AF` Afghanistan
* `IR` Iran
* `KP` Korea, Dem. Republic of
* `SY` Syria
* `RU` Russian Federation — sectoral sanctions
* `MM` Burma (Myanmar)
Configure the list from *Settings → Blocklist → Countries*.
## Ongoing monitoring
Company AML screening can be configured for **ongoing monitoring** — periodic rescans against sanctions and adverse media lists. See [continuous AML monitoring](/core-technology/aml-screening/continuous-monitoring-aml-screening).
## Continuous registry monitoring
Registry monitoring is **coming soon**. No country in the staging retail catalog checked on September 7, 2026 has monitoring enabled. The configured rate is USD 2.00 per company per year; a price does not mean the feature is available. The behavior below describes the planned service after rollout.
Companies verified at the **Shareholders** or **UBO** tier can be kept under continuous registry monitoring for **\$2.00 per company per year**: the registry pushes changes of status, officers, ownership and address, the session goes back to review and a `kyb_registry_change` webhook fires. It is only available where the country allows it: the countries below. See [monitoring shareholders and UBOs](/business-verification/ownership#monitoring-shareholders-and-ubos) and the [per-country prices](/getting-started/kyb-registry-pricing).
## Registry data sources
Didit routes each query to the appropriate official government registry or an authoritative aggregator based on the jurisdiction and subdivision you specify. Data is refreshed on every session — results reflect the latest registry state at query time.
Need a country or subdivision that isn't listed here? [Contact us](mailto:support@didit.me) — registry coverage expands regularly based on customer demand.
## Next steps
How KYB uses registry data.
What registry fields get extracted.
# Business Verification Warnings
Source: https://docs.didit.me/business-verification/warnings
Business Verification warning codes grouped by feature: KYB registry, KYB documents, KYB key people, AML, phone, email, questionnaire, and Device & IP Analysis.
Business Verification surfaces warnings on the feature item that produced them. Each warning carries the standard structured shape used across the platform — `feature`, `risk`, `log_type`, `short_description`, `long_description`, optional `additional_data`, and `node_id` — so you can route review work to the right operational team.
## Overview
KYB sessions emit warnings under three core feature tags that map to dedicated feature arrays in the V3 decision response:
| Feature tag | Decision response key | What it covers |
| ---------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `KYB_REGISTRY` | `registry_checks[].warnings[]` | Official company-registry lookups. Active status, country restrictions, derivable ownership, missing officer/UBO records. |
| `KYB_DOCUMENTS` | `document_verifications[].warnings[]` | Incorporation, ownership, address, and shareholder documents the user uploads. Cross-document consistency, OCR, metadata, manipulation, age, and subtype rules. |
| `KYB_KEY_PEOPLE` | `key_people_checks[].warnings[]` | Person-level signals on directors, officers, UBOs, or user-submitted parties — including PEP/sanctions findings that propagate from AML and prior-decline reuse. |
The same KYB workflow can also include the shared features below — they emit warnings on their own arrays:
| Feature tag | Decision response key |
| --------------- | ------------------------------------------------------------ |
| `AML` | `aml_screenings[].warnings[]` |
| `PHONE` | `phone_verifications[].warnings[]` |
| `EMAIL` | `email_verifications[].warnings[]` |
| `QUESTIONNAIRE` | `questionnaire_responses` (warnings live on the session log) |
| `LOCATION` | `ip_analyses[].warnings[]` |
## KYB Registry warnings
Registry warnings use `feature: "KYB_REGISTRY"` and appear on `registry_checks[].warnings[]`.
| Tag | Description |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `KYB_COUNTRY_RESTRICTED` | The company's country is outside the allowed countries configured for the workflow. |
| `KYB_OFFICERS_DATA_NOT_FOUND` | Officer data is not available from the registry. |
| `KYB_OWNERSHIP_DATA_NOT_FOUND` | Ownership data is not available from the registry. |
| `KYB_BENEFICIAL_OWNERSHIP_NOT_DERIVABLE` | Ultimate beneficial ownership could not be established from the registry data. |
| `KYB_COMPANY_NOT_ACTIVE` | The company is not active according to the registry. |
| `KYB_COMPANY_NOT_FROM_REGISTRY` | The company was entered manually and could not be verified against a corporate registry. |
| `KYB_COMPANY_VAT_INVALID` | The company's VAT number was checked against the EU VIES service and is not valid. The configured `kyb_vat_invalid_action` is applied (default: In Review). |
| `KYB_COMPANY_VAT_COULD_NOT_VALIDATE` | The VAT number could not be validated because the EU VIES service (or the member state) was temporarily unavailable. The configured `kyb_vat_unverified_action` is applied (default: no effect). |
| `KYB_COMPANY_VAT_NAME_MISMATCH` | The trader name registered with VIES for this VAT number differs from the submitted company name. Informational — includes `vies_name` and `submitted_name` in `additional_data`. |
VAT warnings only apply to companies in the EU VAT area (EU-27 plus Northern Ireland) — see [VAT validation](/business-verification/company-data#vat-validation-vies).
## KYB Document warnings
Document warnings use `feature: "KYB_DOCUMENTS"` and appear on `document_verifications[].warnings[]`.
| Tag | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `KYB_DOCUMENT_MISSING_REQUIRED_FIELD` | A required company field — such as company name or registration number — was not found in the document. |
| `KYB_DOCUMENT_NAME_MISMATCH` | The company name in the document does not match the registry. |
| `KYB_DOCUMENT_REG_NUMBER_MISMATCH` | The registration number in the document does not match the registry. |
| `KYB_DOCUMENT_INCORPORATION_DATE_MISMATCH` | The incorporation date in the document does not match the registry. |
| `KYB_DOCUMENT_ADDRESS_MISMATCH` | The registered address in the document does not match the registry. |
| `KYB_CROSS_DOCUMENT_MISMATCH` | Company data differs across uploaded corporate documents. |
| `KYB_DOCUMENT_METADATA_MISMATCH` | The document signature, metadata, or file structure indicates a possible issue. |
| `KYB_ASSOCIATED_PARTY_MISMATCH` | Directors or shareholders in the document do not match the registry. |
| `KYB_DOCUMENT_GRAPHIC_EDITOR` | The document appears to have been processed by graphic editing software. |
| `KYB_DOCUMENT_SUSPECTED_MANIPULATION` | The system detected signs of possible corporate-document manipulation. |
| `KYB_ADDITIONAL_DATA_NOT_FOUND` | A document field could not be verified because the corresponding registry data is unavailable. |
| `KYB_DOCUMENT_EXPIRED` | The corporate document exceeds the configured maximum age. |
| `KYB_DOCUMENT_UNSUPPORTED_SUBTYPE` | The uploaded document subtype is disabled for the workflow. |
| `KYB_DOCUMENT_MAX_ATTEMPTS_EXCEEDED` | The maximum number of corporate-document upload attempts was exceeded. |
For PDF overlay-text manipulation, `KYB_DOCUMENT_SUSPECTED_MANIPULATION` can include `additional_data.manipulated_regions`. These are page-coordinate rectangles that identify the suspected edited areas and match the rectangles returned in `document_verifications[].items[].document_metadata.overlay_manipulation.manipulated_regions`.
## KYB Key People warnings
Key People warnings use `feature: "KYB_KEY_PEOPLE"` and appear on `key_people_checks[].warnings[]`. They surface person-level issues on directors, officers, UBOs, and user-submitted parties.
| Tag | Description |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `KYB_PREVIOUSLY_DECLINED_INDIVIDUAL` | A person associated with the company was previously declined in another business verification for the same application. |
**PEP / sanctions hits on key people** are reported as standard AML hits on the matching `aml_screenings[]` item, not as a KYB-key-people warning. The Key People check propagates the resulting `Declined` or `In Review` status onto each affected party so the overall key-people verdict reflects the AML outcome.
## Shared feature warnings
The features below can run inside a KYB workflow. They use the same warning catalog as User Verification — full reference on each feature's own warnings page.
### AML
`feature: "AML"` on `aml_screenings[].warnings[]`.
| Tag | Description |
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `POSSIBLE_MATCH_FOUND` | The AML screening found a possible match requiring review. |
| `COULD_NOT_PERFORM_AML_SCREENING` | Didit could not perform the AML screening because required screening data was missing or unavailable. |
### Phone
`feature: "PHONE"` on `phone_verifications[].warnings[]`.
| Tag | Description |
| ------------------------------------- | -------------------------------------------------------------- |
| `DISPOSABLE_NUMBER_DETECTED` | The phone number is disposable. |
| `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` | The maximum number of verification-code attempts was exceeded. |
| `VOIP_NUMBER_DETECTED` | The phone number is a VoIP number. |
| `DUPLICATED_PHONE_NUMBER` | The phone number was used in another verification process. |
| `HIGH_RISK_PHONE_NUMBER` | The phone number is high risk. |
| `PHONE_NUMBER_IN_BLOCKLIST` | The phone number matches an entry in your blocklist. |
### Email
`feature: "EMAIL"` on `email_verifications[].warnings[]`.
| Tag | Description |
| ------------------------------ | -------------------------------------------------------------------- |
| `BREACHED_EMAIL_DETECTED` | The email address appears in known data breaches. |
| `DISPOSABLE_EMAIL_DETECTED` | The email address is disposable. |
| `UNDELIVERABLE_EMAIL_DETECTED` | The email address is undeliverable. |
| `DUPLICATED_EMAIL` | The email address was used in another verification process. |
| `EMAIL_IN_BLOCKLIST` | The email address matches an entry in your blocklist. |
| `EMAIL_CODE_ATTEMPTS_EXCEEDED` | The maximum number of email verification-code attempts was exceeded. |
### Questionnaire
`feature: "QUESTIONNAIRE"`.
| Tag | Description |
| ------------------------------ | --------------------------------------------------------------------------------------- |
| `CUSTOM_STATUS_RULE_TRIGGERED` | A workflow status rule evaluated a questionnaire answer and changed the feature status. |
### Device & IP Analysis
`feature: "LOCATION"` on `ip_analyses[].warnings[]`.
| Tag | Description |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `COUNTRY_FROM_DOCUMENT_DOES_NOT_MATCH_COUNTRY_FROM_IP` | The document country does not match the IP country. |
| `PRIVATE_NETWORK_DETECTED` | A private network, VPN, proxy, or Tor signal was detected. |
| `EXPECTED_IP_ADDRESS_MISMATCH` | The IP address does not match the expected IP address provided for the session. |
| `IP_ADDRESS_IN_BLOCKLIST` | The IP address matches an entry in your blocklist. |
| `DEVICE_FINGERPRINT_IN_BLOCKLIST` | The device fingerprint matches an entry in your blocklist. |
| `DUPLICATED_IP_ADDRESS` | The same IP address was used in another verification with different `vendor_data`. |
| `DUPLICATED_DEVICE_FINGERPRINT` | The same device fingerprint was used in another verification with different `vendor_data`. |
## Examples
### Registry — company not active
```json theme={null}
{
"registry_checks": [
{
"node_id": "feature_kyb_registry",
"status": "Declined",
"warnings": [
{
"feature": "KYB_REGISTRY",
"risk": "KYB_COMPANY_NOT_ACTIVE",
"log_type": "error",
"short_description": "Company is not active",
"long_description": "The company is not active according to the registry.",
"additional_data": null,
"node_id": "feature_kyb_registry"
}
]
}
]
}
```
### Documents — suspected overlay manipulation
```json theme={null}
{
"document_verifications": [
{
"node_id": "feature_kyb_documents",
"status": "In Review",
"warnings": [
{
"feature": "KYB_DOCUMENTS",
"risk": "KYB_DOCUMENT_SUSPECTED_MANIPULATION",
"log_type": "warning",
"short_description": "Possible document manipulation",
"long_description": "The system detected signs of possible corporate-document manipulation.",
"additional_data": {
"manipulated_regions": [
{ "page": 1, "x": 120, "y": 240, "width": 180, "height": 22, "page_width": 612, "page_height": 792 }
]
},
"node_id": "feature_kyb_documents"
}
]
}
]
}
```
### Key people — previously declined individual
```json theme={null}
{
"key_people_checks": [
{
"node_id": "feature_kyb_key_people",
"status": "In Review",
"warnings": [
{
"feature": "KYB_KEY_PEOPLE",
"risk": "KYB_PREVIOUSLY_DECLINED_INDIVIDUAL",
"log_type": "warning",
"short_description": "Previously declined individual",
"long_description": "A person associated with the company was previously declined in another business verification for the same application.",
"additional_data": null,
"node_id": "feature_kyb_key_people"
}
]
}
]
}
```
## Related
See where warnings appear in each KYB feature array.
Understand how warning severity maps to verification status.
Review document groups, OCR, and cross-check behavior.
Officer, UBO, and submitted-party verification flow.
Hit-level structure for company and person AML screenings.
Canonical shapes for warnings and feature arrays.
# KYB Webhooks
Source: https://docs.didit.me/business-verification/webhooks
Webhook events for KYB sessions and Business entities — triggers, payload schemas, and HMAC signature verification. From $2.00 per check, priced per country and tier, no contracts.
Business Verification and Business entities emit webhook events on every meaningful state change. Subscribe to the events you care about via the [webhook destinations API](/management-api/webhook/list-destinations) or the Business Console.
## Event catalog
Webhook events are shared between User and Business sessions — the `session_kind` field on the payload tells you which kind the event refers to. Entity-level events are separate from session-level events.
### Session-level events
| Event | When it fires |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status.updated` | A session's status changed. For Business Verification (KYB) sessions the payload carries `session_kind: "business"`. |
| `data.updated` | A session's data was updated (registry refresh, key-people submission, document upload, AML rescan). Payload includes `session_kind: "business"` for KYB sessions. |
### Business entity events
| Event | When it fires |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `business.status.updated` | The linked [Business entity](/entities/businesses/overview)'s status changed (`ACTIVE` / `FLAGGED` / `BLOCKED`). |
| `business.data.updated` | The Business entity's profile fields or aggregate counters changed. |
Subscribe to any combination on your webhook destination. See [entity webhooks](/entities/webhooks) for the full Business / User entity event reference.
## Envelope
Every webhook uses the same top-level envelope. The `session_kind` field inside `data` discriminates between User Verification (KYC) and Business Verification (KYB) for session-level events.
```json theme={null}
{
"event": "status.updated",
"event_id": "evt_01H8X...",
"application_id": "app_abc123",
"timestamp": "2026-04-18T12:30:00Z",
"data": { ... }
}
```
## Payload shapes
### `status.updated` — Business Verification (KYB) session
Fires when a KYB session's status transitions.
```json theme={null}
{
"event": "status.updated",
"data": {
"session_id": "bs_01H...",
"session_kind": "business",
"previous_status": "IN_PROGRESS",
"status": "APPROVED",
"vendor_data": "biz-acme-001",
"workflow_id": "wf_kyb_standard",
"features": ["KYB_REGISTRY", "KYB_COMPANY_AML", "KYB_DOCUMENTS", "KYB_KEY_PEOPLE"],
"decision": { ...abbreviated KYB decision payload... },
"changed_at": "2026-04-18T12:30:00Z"
}
}
```
The `decision` block contains the same shape as [`GET /v3/session/{id}/decision/`](/sessions-api/retrieve-session) — `registry_checks[]`, `key_people_checks[]`, `document_verifications[]`, `aml_screenings[]`, etc.
### `data.updated` — Business Verification (KYB) session
Fires when session data (registry, key people, documents, AML) changed without a status transition.
```json theme={null}
{
"event": "data.updated",
"data": {
"session_id": "bs_01H...",
"session_kind": "business",
"vendor_data": "biz-acme-001",
"changed_fields": ["registry_checks", "key_people_checks"],
"updated_at": "2026-04-18T14:00:00Z"
}
}
```
Use `changed_fields` to efficiently decide what to re-fetch.
### `business.status.updated`
Fires when the [Business entity](/entities/businesses/overview) moves between `ACTIVE`, `FLAGGED`, or `BLOCKED`.
```json theme={null}
{
"event": "business.status.updated",
"data": {
"vendor_data": "biz-acme-001",
"uuid": "f7a9c1b2-4e6d-4f8a-9c5d-2a1b3c4d5e6f",
"status": "FLAGGED",
"previous_status": "ACTIVE",
"reason": "manual",
"actor": "compliance@yourcorp.com"
}
}
```
### `business.data.updated`
Fires when the Business entity's profile fields or aggregate counters change.
```json theme={null}
{
"event": "business.data.updated",
"data": {
"vendor_data": "biz-acme-001",
"uuid": "...",
"legal_name": "Acme Corporation Limited",
"registration_number": "12345678",
"country_code": "GBR",
"session_count": 2,
"approved_count": 1,
"declined_count": 0,
"in_review_count": 1,
"changed_fields": ["session_count", "approved_count", "features"]
}
}
```
## Signature verification
All webhooks are signed HMAC-SHA256 with your destination's shared secret, sent in the `X-Didit-Signature` header. Verify the raw request body before acting.
Example (Node.js):
```typescript theme={null}
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const provided = Buffer.from(signature);
const computed = Buffer.from(expected);
return provided.length === computed.length && timingSafeEqual(provided, computed);
}
```
Full details in the [webhooks reference](/integration/webhooks).
## Retry policy
* Non-2xx responses trigger retries with exponential backoff.
* Up to 5 retries over \~24 hours.
* Each retry gets a new `X-Didit-Delivery` header but the same `event_id` — de-duplicate on your side.
* Repeated failures flip the destination to `disabled`.
## Subscribing
Subscribe to the events you care about on a [webhook destination](/management-api/webhook/create-destination):
```bash theme={null}
curl -X POST https://verification.didit.me/v3/webhook/destinations/ \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "KYB events",
"url": "https://yourapp.com/webhooks/didit-kyb",
"subscribed_events": [
"status.updated",
"data.updated",
"business.status.updated",
"business.data.updated",
"activity.created"
]
}'
```
Filter for business sessions on your side by checking `data.session_kind === "business"` inside `status.updated` and `data.updated` events.
## Next steps
Destinations, signing, retries, common patterns.
Session and feature status reference.
Decoding the KYB decision payload.
# April 2026
Source: https://docs.didit.me/changelog/april-2026
April 2026 release: KYB business verification revamp, transaction monitoring upgrades, streaming exports, new documents, and native SDK updates.
### KYB Business Verification — Major Revamp
Business verification received its biggest update yet. The end-to-end flow — from creating a session to reviewing the final report — was rebuilt around the same patterns you already know from KYC.
* **New Business List View** — Filters, column visibility, tags, bulk actions, and create-session shortcuts make managing business onboarding far easier
* **Redesigned Business Detail Page** — Scrollable feature tabs (registry, key people, documents, AML, questionnaires), PDF download, file preview, resubmit, and delete — matching the session detail experience
* **Key People Graph** — Split view between registry-provided and submitted parties, clickable detail modal, and a workflow-style hierarchy graph (Cmd/Ctrl + scroll to zoom)
* **Expanded Registry Check** — More fields, richer inline editing, country flags with localized names, and translated labels for addresses, financials, and industries
* **AML for KYB** — Configure AML screening for businesses and their key people with the same controls as KYC
* **Required Document Validation** — Workflows can now require at least one verified business document (incorporation, POA, etc.) before approval
* **Tags, Cases, and Decision Endpoints** — Tag businesses, attach cases, and apply manual decisions through the same APIs used for individuals
### Customer-Facing KYB Flow
The end-user side of KYB also saw heavy work for a smoother onboarding experience.
* **Smart Prefill** — Phone numbers and key people are prefilled when the data is already known, so applicants only confirm
* **POA Improvements** — Better proof-of-address screen, document subtype detection, and a new action when the address can't be parsed
* **KYB Document Compression** — Uploaded business documents are now compressed automatically for faster submission and smaller payloads
* **Region Picker Polish** — Redesigned country and state pickers shared across every step for a more consistent feel
### Transaction Monitoring (KYT)
Transaction monitoring grew from a beta feature into a full surface area inside the Console.
* **Transaction PDF Reports** — Download a complete PDF for any transaction, matching the format already available for verification sessions
* **Lists & Blocklists Expanded** — System lists now cover IPs, devices, and duplicated identifiers, with translations and richer match details
* **Rules Summary** — Each transaction shows only the rules that actually matched, with a count summary at the top
* **Cases on User Details** — User profiles now surface their associated cases, transactions, and screening history in one place
* **Improved Screening Performance** — AML screening responses are cached, transaction screening is faster, and rule responses are more compact
* **Subject Country Fix** — Cross-border transactions resolve the correct subject country
* **Webhooks for Transactions** — Get notified when transaction state changes via the same webhook system as sessions
* **Payment Methods** — Payment-method data is now captured and surfaced on the transaction record
### Database Validation
* **New Service Format** — Database validation services moved to a richer format that returns more structured data for downstream use
* **Outcome Codes** — Standardized outcome codes make it easier to map vendor responses across countries
### Streaming CSV Exports
Large exports no longer time out. Sessions, transactions, analytics, and usage exports stream results as they're generated.
* **Sessions & Businesses Export** — Streamed CSV with no row cap and far lower memory usage
* **Analytics Export** — Download analytics data as a CSV directly from the dashboard
* **Usage Export** — Per-feature usage breakdown is now exportable
* **Faster Generation** — Exports complete several times faster on large organizations
### Multi-Account Abuse Prevention
New protection against organization farming — when a single actor creates multiple sub-accounts to bypass limits.
* **Multi-Account Detection** — Suspicious patterns of organization creation are now flagged automatically
* **New Roles & Permissions** — Additional granular permissions, plus updates to SAML for enterprise customers
### Workflow & API Improvements
* **More Flexible Workflow API** — Create and patch workflows with a wider range of configurations, including KYB settings and node deduplication
* **Workflow Editor Polish** — Template fixes, blur/scroll improvements in the node editor, redesigned modals, and clearer node-based UI
* **Custom Verification Email** — Send the verification link from a dedicated email channel
* **Update-Data Endpoints for API Clients** — The endpoints used to update session data are now first-class, available to all API clients
* **Workflow Templates** — More templates and better preview screens
### Console Performance & UX
* **Faster Console** — Reduced initial load time and smoother navigation across the dashboard
* **Phone Lifecycle** — Cleaner state transitions for phone-verification features
* **User Details Pages** — All tabs (sessions, businesses, transactions, cases) now visible on a single user-detail surface
* **Permissions Fixes** — Several Console buttons that were incorrectly disabled (e.g., the "+" create button) now reflect the user's real permissions
### New Document & Country Support
* **Kyrgyzstan** — Added support for the national ID
* **India** — PAN ID card now correctly classified, separate from passports
* **Mexico** — New ID document added; Switzerland and Czech Republic recognition improved
* **Algeria** — CSCA chip validation now works correctly
* **Income Tax Cards** — New document type for tax-residency verification across multiple regions
### Kazakh Language
The verification flow, Console, and SDKs now support Kazakh (kk), bringing total language coverage to 53.
### Native SDKs
A wave of releases across all native SDKs this month, focused on stability and reliability.
* **Android SDK 3.4.4 – 3.5.3** — Improved document and face overflow detection, better photo quality, refined face-intro styling, fixed close buttons and verification screen styling, and updated languages including Kazakh
* **iOS SDK 3.2.10 – 3.3.3** — Multiple stability releases with active liveness improvements, language updates, and miscellaneous fixes
* **React Native SDK 3.2.8 – 3.2.9** — Resolved Swift 6 main-actor isolation error on Xcode 26.0.x, surfaced the new `RetryBlocked` error variant, added an Expo example app, and improved the CI pod-install fallback
* **Flutter SDK 3.4.4 – 3.4.5** — Fixed Swift 6 exhaustive switch, updated native iOS/Android SDKs underneath, and surfaced `RetryBlocked` from the native Android layer
### MCP Server
The Didit MCP server (used by AI agents) was upgraded.
* **Lists API** — Legacy blocklist tools were replaced with the unified Lists API, giving agents the same blocklist/allowlist capabilities the Console exposes
* **Version 3.0.3** — Latest release published
### Improvements
* IP enrichment now handles private IP addresses gracefully
* KYB sub-sessions (KYC for UBOs, officers, etc.) correctly inherit the whitelabel branding of their parent
* WebSocket responses include the user's locale for client-side display
* Active liveness uses the latest verification SDK with faster capture validation on iOS
* Camera permission handling distinguishes between permission timeouts and scan timeouts for clearer error messages
* Arabic transliteration improved
* Country detection from IP fixed for several edge-case ranges
* Fraud reports include richer detail and a clearer summary
# April and May 2025
Source: https://docs.didit.me/changelog/april-may-2025
Didit V2 launch with modular architecture, age estimation, biometric authentication, proof of address, prepaid credits, and open APIs.
### Didit V2 Is Here
We've rebuilt Didit from the ground up. This is not just an update — it's a reimagining of how identity verification should work.
**Modular Architecture** — Choose only the tools you need. Use our web widget or go deep with our open APIs.
**New Features:**
* **Age Estimation** — Facial analysis to verify user age, with optional document check
* **Biometric Authentication** — Use biometrics to unlock sensitive flows like payments or restricted access
* **Proof of Address** — Accept bills, bank statements, or cross-reference data to verify residency
* **Face Search 1:N** — Match a user's face against your database to detect duplicates, enforce blocklists, or manage biometric access
* **Phone Verification** — OTP-based phone number checks built-in
**More Control** — Fully customize your verification flow based on your risk and product needs.
**Prepaid Credit Model** — Buy only what you use. Save more on premium features like AML, Biometrics, and Age Estimation.
**Open & Modular APIs** — Core features exposed via API so you can integrate directly without the hosted verification flow.
# August 2025
Source: https://docs.didit.me/changelog/august-2025
Database validation API launch, phone verification, proof of address by country, and RTL language support across Didit's identity verification platform.
* **Database Validation** — New API for verifying identity against government databases. Launched coverage for Brazil and Dominican Republic
* **Phone Verification** — New phone verification API integrated into biometric authentication flows
* **Proof of Address by Country** — POA documents can now be configured per country for flexible integrations
* **Unparsed Address Warning** — Notifications when document addresses cannot be parsed or geolocated
* **Enhanced Document Handling** — Additional personal number fields, improved non-standard MRZ handling (Peru, Bolivia, Romania, Iraq, Kazakhstan, Kuwait)
* **Language & RTL Support** — Full right-to-left interface improvements for Arabic, Hebrew, and Persian
* **Improved iOS Capture Quality** — Optimized image and video quality on iOS while keeping file sizes lightweight
* **Shorter Session URLs** — Smaller QR codes and more readable URLs
* **Automatic Language Routing** — Language auto-detection when scanning QR codes and during redirections
* **Bug Fixes** — Fixed face match edge cases, improved video compression, fixed Samsung Internet compatibility issues
# August 2026
Source: https://docs.didit.me/changelog/august-2026
Biometric-template retention with a breaking session-deletion change, new fraud signals, ISO/IEC 27566-1 age assurance, and standalone risk APIs.
### Stronger fraud detection
Fraudsters increasingly submit documents and selfies that never came from a real capture. Five new signals catch those submissions before they reach a reviewer.
* **Catch recycled document images** — every upload is scored against a corpus of ID document images already circulating publicly, and a match raises its own warning
* **Liveness video forensics** — frame count, duplicate-frame ratio, and effective frame rate are analyzed server side, so replayed or synthetic video is flagged rather than trusted
* **Covered documents are detected** — a partially obscured document is caught at pixel level and returns `DOCUMENT_OCCLUSION_DETECTED`, translated into every supported language
* **Live capture confirmed** — Face Match confirms the person in front of the camera, distinguishing a live capture from the portrait printed on the document
* **Capture quality gate** — every upload is scored for quality before extraction runs, so a poor capture is caught while the user is still in the flow
### Age assurance built to ISO/IEC 27566-1
Age-assurance workflows can now return the age decision and nothing else, in line with the international age-assurance standard.
* **Returned data is locked** — a workflow that declares the age-assurance standard cannot return identity attributes alongside the age decision, and only an age-capable workflow may declare it
* **Branch on a borderline age** — the Age Estimation borderline band is visible to the workflow graph, so an uncertain result can route to a document check instead of a flat pass or fail
* **Ready-made templates** — start from an age-assurance template in the workflow builder with returned data already restricted
### New standalone APIs
Four capabilities that previously required a full verification session are now callable on their own, each priced per call on the [pricing page](https://didit.me/pricing).
* **Document AI** — `POST /v3/document-ai/` extracts structured data from any uploaded document, not just identity documents. See [Document AI](/standalone-apis/document-ai)
* **Contact and network risk** — `POST /v3/email/risk/`, `POST /v3/phone/risk/`, and `POST /v3/ip/risk/` return risk signals for an email address, phone number, or IP address. See [Email risk](/standalone-apis/email-risk), [Phone risk](/standalone-apis/phone-risk), and [IP risk](/standalone-apis/ip-risk)
* **Run them as a single check** — each is also available from the single-check dialog in the **Business Console**, without building a workflow
### Didit Copilot
The in-console assistant became a floating copilot that cites its sources and can build a working workflow from a description.
* **Available on every page** — a resizable floating panel replaces the docked one, with starting prompts for the page you are on and a fullscreen layout on mobile
* **Answers cite their sources** — inline citations open the underlying documentation without leaving the panel
* **Build a workflow by describing it** — the copilot creates the draft, adds and configures steps, adds branches, sets final-status rules, and validates before publishing
* **Configure Database Validation by conversation** — the copilot creates a configured **Database Validation** step for any supported country
* **Build a questionnaire from a spreadsheet** — attach a CSV or Excel file and the copilot turns it into a questionnaire, including very long answer lists
* **Works in your language** — the console language travels with every turn, voice and typed input are both supported, and you can download the transcript
### Support in the Business Console
Support moved into the product, so you can raise and follow an issue without leaving the **Business Console**.
* **A Support section** — organization-scoped tickets with live status and a standard tickets table
* **Report an issue in place** — a dialog with category, severity, and session ID, attachments on any message, and the ticket number shown everywhere
* **Escalate to a human in the same thread** — the assistant hands off to a support agent without losing context, and you get an email when a human replies
* **Follow a ticket by link** — every ticket has a permalink
### Marketplace and per-check engines
The Marketplace is now available in the **Business Console**, and you can choose which engine runs each individual check.
* **Browse and enable partner modules** from the Marketplace section. See [Marketplace](/console/marketplace)
* **Pick an engine per feature** — a full-width engine selector sits in the first tab of a feature, in both the graph and simple workflow editors
* **Your choice persists** on simple workflows as well as graph workflows
### Transaction Monitoring
Monitoring rules are now fully programmable, from the API and from an AI agent.
* **Manage rules through the API** — create, update, delete, and backtest transaction-monitoring rules with the `/v3/transactions/rules/` Management API endpoints, in addition to the **Business Console**. Browse, install, and remove preset rules from the library with the matching `library` and `install` endpoints
* **Manage rules from the Didit MCP** — nine `didit_transaction_rule_*` MCP tools bring the same list, create, update, delete, backtest, and library workflows to any MCP-connected AI agent
* **AI-assisted rule migration** — ask the Didit assistant (or any MCP-connected agent) to map a rule set exported from another provider (CSV or Excel) onto Didit's rule schema, backtest it against your own transaction history, and create the rules in test mode for you to review before activating
* **Every manual status change is attributed** — a transaction moved by hand records the console user who moved it
### Biometric templates and data retention
Applications can keep one image-free face template after a session is deleted, so duplicate detection keeps working once session data is purged.
* **Biometric-template retention after session deletion (opt-in)** — keep one image-free face biometric template anchored to the User when a session is deleted, so duplicate detection, Face Search, and biometric authentication keep working after you purge session data. Enable it in **App Settings → Data** (**Retain biometric template**, with a required finite retention period) or with `face_retention_policy` and `face_retention_days` on `PATCH /v3/webhook/`. Override it per call with `retain_face_embeddings` on `DELETE /v3/session/{sessionId}/delete/` and `POST /v3/sessions/delete/`. Retained templates never contain images, session data, or identity fields, carry a finite `expires_at`, and are purged on User deletion, privacy erasure, expiry, or explicit purge. See [Biometric templates](/management-api/biometric-templates/overview)
* **Privacy-erasure instructions** — send `deletion_instruction: "privacy_erasure"` (and your own `instruction_id`) on session deletion to purge every retained template for that person regardless of the application policy
* **New Biometric Templates API** — `GET /v3/biometric-templates/`, `GET /v3/biometric-templates/count/`, `GET`/`DELETE /v3/biometric-templates/{template_uuid}/`, and `POST /v3/biometric-templates/delete/` list, count, inspect, and purge retained templates. Every retain and purge is audited
* **Face Search matches** now report `source: "retained_template"` with `vendor_user_id` and `biometric_template_id` for hits on retained templates, and `vendor_user_id` on session and imported matches
* **Breaking: session deletion responses** — `DELETE /v3/session/{sessionId}/delete/` and `POST /v3/sessions/delete/` now return `200 OK` with a JSON body (`face_retention_outcome`, `biometric_template_uuid`; per-session `results[]` for batch) instead of `204 No Content`. Update clients that assert on `204`. Default deletion behavior is unchanged: existing applications stay on `delete_with_session` until you explicitly enable retention
* **`POST /v3/users/delete/`** purges the user's retained biometric templates before deleting the user, and returns `503` (retryable, nothing deleted) if a template cannot be purged
* **A retention policy you can see** — set the policy in settings and review stored templates in **Lists → Biometric templates**
### Session API
Retrieve-session responses carry more of the signals your integration needs, so you can decide without extra calls.
* **Barcode data in responses** — retrieve-session responses include `id_verifications[].barcodes`. Each barcode entry preserves `type`, `position`, `data`, `data_raw`, and `side`; documents without barcode output return an empty array
* **Explicit liveness method** — face-step payloads always include `face_liveness_method`. When a workflow does not set it explicitly, the API returns `PASSIVE`
* **Marital status in rules** — use `kyc.marital_status` in workflow branches and custom status rules
* **Order by last update** — list sessions by `updated_at`, and see at a glance which sessions were imported
### New documents and countries
New document types across ten countries, and firearm licenses as a new document category.
* **New identity documents** — Moldova ID card (2025), Pakistan ID card (2025), Uruguay ID card (2026), Greece asylum seeker card (2025), and a third Belgium driving license variant
* **Digital driving licenses** — Argentina (Mi Argentina and Jujuy) and Australia (New South Wales including provisional, South Australia including learner, and Victoria including heavy vehicle)
* **Firearm licenses are a new category** — Victoria firearms license in Australia, and Illinois, Massachusetts, and two New Jersey permits in the United States, selectable as their own document type
* **Mexico City driving license (2025)**, including the permanent-license catalog, and **Paraguay driving license (2026)**. Separately, **Malaysia MyKad** is now selectable as its own subtype rather than a generic ID card
* **Extraction upgraded for ten more countries** — Belgium, Germany, India, Israel, Madagascar, New Zealand, Norway, Sierra Leone, South Africa, and the United States
* **French 2D-Doc validation** — French 2021 ID cards read and validate the ANTS 2D-Doc Data Matrix on the back of the document
* **See exactly which fields a barcode signs** — the barcode on North American driving licenses reports its signed fields, and each signature is validated against its certificate's validity window
### Chip reading
The ePassport trust store was refreshed, so more chips authenticate cleanly and an unreadable chip explains itself.
* **136 issuing countries** in the ePassport trust store, with new signing-certificate generations imported for 32 of them
* **A missing trust anchor is named** — when the trust anchor for a chip is missing, the result says so directly
* **Branch on why a chip was skipped** — workflows can route on the specific NFC skip reason
* **Wider chip compatibility** — passports whose signatures use a non-standard encoding now read successfully, and chip portraits in every color mode convert correctly
### Database Validation
A new authoritative source, and Database Validation now runs on its own inputs.
* **Nigeria Bank Verification Number** — a direct BVN lookup, gated on a selfie face match so the number is only resolved for the person actually present. Names are optional inputs
* **No document step required** — each field can draw from its own input source, so a workflow can run Database Validation from a selfie and a national ID number with no document capture at all
* **Explicit approval semantics** — an approval means the source positively confirmed the data, and billing follows a successful source call
* **Clearer outcomes** — when Database Validation does not run, the response says why
### Business verification
Declared owners and officers are checked against the documents you upload, and a registry outage keeps the flow moving.
* **Key people are confirmed against corporate documents** — a declared party is matched on document evidence, and a shareholder named in a document must carry an owner role
* **Registry roles are preserved** — UBO roles come from registry ownership data, and officer roles from the full registry designation
* **Registry outages return a clear code** — registry-search outages return `502` with `code: "kyb_registry_provider_unavailable"`, so your integration can react instead of guessing
* **Manual company entry** — web, iOS, and Android verification flows let the applicant enter company details by hand when the registry provider is unavailable
* **French overseas departments** are now searchable in the French company registry
* **Registry coverage analytics** — see registry search coverage and success by country and workflow version in **Reports**
* **Per-group document progress** on the documents step, with documents awaiting replacement counted separately
### AML screening and Wallet Screening
Reviewers get the reasoning behind a score, and wallet screening gains a shareable report.
* **The risk score is explained** — the result shows the factors behind the score, with readable hit tooltips and spelled-out dataset names
* **Signed PDF report for wallet screening** — a standalone report endpoint whose results are signed, so the PDF cannot be altered after the fact
* **Idempotency keys** on the AML API, so a retried screening request is never charged or screened twice
* **Sharper matching** — nationality is normalized to exact country codes before search, and re-screening survives a manual country correction
### Verification flow
Capture is where users most often fail, so most of this month went into finishing the first attempt.
* **The alignment frame stays on screen through a retake**, and sits correctly above the camera preview on the newest mobile browsers
* **Camera recovery** — the flow detects a camera that has stopped producing images and restarts it, so capture continues
* **Photo fallback for capture** — web and Android capture flows continue with a photo when video recording is unavailable, and Android retries a failed document-detection model download
* **Honest liveness messaging** — timeout-specific wording, stronger guidance after a second consecutive timeout, and a device with no camera is told so rather than asked for permission
* **Larger uploads** — documents up to 50 MB on Proof of Address, business documents, and Document AI, with automatic compression
* **Vatican City and Saint Vincent and the Grenadines** added to the country list
* **Phone provider outages return a clear code** — `502` with `code: "phone_provider_unavailable"`
* **Retry and rescan stay available** after a document or face upload error on Android (SDK 4.7.3)
* **A clear message when credits run out**, so the user knows exactly what happened
### Branding and copy
You can now rewrite the words your users read and match the flow to your palette.
* **A Texts tab in Customization** — override the wording of each step and the completion screen, with a preview showing the real default text
* **Per-application copy overrides** — the same override can differ between two applications in one organization, and applies per step rather than only to the first
* **The "Secured by" footer** takes your configured support-text color
### Billing and usage
Spend is easier to read, and a failed payment explains itself.
* **Prepaid volume discounts** — prepaid credits use charge discounts instead of bonus credits: save 4% at $250, 7% at $1,000, and 10% at \$2,000
* **Pay once per document step, not once per upload** — a step with ten business documents costs the same as one with two
* **Monthly invoices for contract billing** — contract-billed organizations receive a monthly usage invoice settled in the payer's currency
* **Auto-recharge explains itself** — a failed attempt records and shows the decline reason, and auto-recharge resumes automatically once the payment method is replaced
* **A rebuilt Usage page** — sortable tables, search inside the breakdown dialog, a promoted total-cost figure, and a free-tier counter
* **A machine-readable code on insufficient credits**, so your integration can react to it programmatically
### Native SDKs
Native SDKs shipped steadily all month, landing every platform on 4.7.x with materially smaller binaries.
* **React Native 4.5.4 to 4.7.5**, **Android 4.5.4 to 4.7.4**, **iOS 4.5.4 to 4.7.2**, **Flutter 4.6.0 to 4.7.2**
* **Smaller apps** — iOS variants are materially smaller, and a lighter on-device engine now powers auto-capture on both platforms
* **Wallet support is optional on Android** — wallet connection ships as a separate dependency, so your app carries it only when you use it
* **Per-step welcome copy** — welcome text resolves against its own workflow step, and title overrides apply on fully white-labeled apps
* **Framework support** — the React Native TurboModule specification supports React Native 0.77 code generation, Kotlin 2.2 is supported, and Flutter passes the device locale into the session so the flow opens in the user's language
* **Android 16** — Active Liveness camera previews render correctly on Android 16 devices; React Native 4.7.5 carries the Android 4.7.4 build that fixes it
* **iOS still-photo capture** — the iOS SDK falls back to still-photo capture on iOS 15 when the live video frame is unavailable
* **Crash symbolication** — the React Native SDK publishes iOS dSYM artifacts from 4.5.4, and Expo Android dependency collisions are resolved across all variants
### Didit Academy
Ten video lessons take you from your first workflow to reading a verification result, each with a full timestamped transcript in these docs.
* **Ten lessons on YouTube** — [platform tour](https://www.youtube.com/watch?v=skV9iUR2UhQ), [what your users see](https://www.youtube.com/watch?v=LzGe-dMWjUw), [build a KYC workflow with no code](https://www.youtube.com/watch?v=7-nJ3JE20i4), [connect Didit to Claude with MCP](https://www.youtube.com/watch?v=I3UkFIhW548), [read a verification result](https://www.youtube.com/watch?v=7htTWCWsl60), [KYB from registry to UBO](https://www.youtube.com/watch?v=JEnW_sEe35Y), [real-time transaction monitoring rules](https://www.youtube.com/watch?v=Yhm63_n7HuU), [integrate the API, SDKs, and webhooks](https://www.youtube.com/watch?v=fX03-WEu_EI), [analytics, teams, and roles](https://www.youtube.com/watch?v=sRcOBxjRYag), and [pricing explained](https://www.youtube.com/watch?v=aTWy1rfChhI)
* **Watch the full course** in the [Didit Academy playlist](https://www.youtube.com/playlist?list=PLIikxNViJoCY)
* **Searchable transcripts** — every lesson has a timestamped transcript in the docs, and each paragraph deep-links to that exact moment in the video. Start at [Didit Academy](/academy/overview)
### Trust and compliance
Where the age-assurance work above sits in Didit's credentials.
* **Age verification is certified in Germany** — FSM, Germany's youth-protection self-regulator, certified that Didit's age-verification system reliably establishes a closed user group under Section 4(2) JMStV. See [Certifications](/getting-started/certifications)
* **Biometric anti-spoofing** — iBeta Level 1 presentation-attack detection under ISO/IEC 30107-3, tested by a NIST-accredited laboratory
* **FIDO Alliance** — Didit joined the FIDO Alliance as an Associate Member on August 13, 2026
### Improvements
Smaller changes across the Business Console, the API, and the verification flow.
* Download a user's PDF report directly from user details, and get one report covering every session a returning user has completed
* Start a workflow with a branch, and route branches on the session metadata you set at session creation
* Workflows keep running unchanged as new document subtypes are added or renamed
* A resubmission replays the full workflow graph, including webhooks
* Proof of Address keeps the address exactly as printed on the document, and reads a utility bill's payment deadline as a payment date
* Compliance country questions group countries by continent, with select-or-clear for a whole continent
* Merchants using the Shopify app can see their verification results in the Shopify admin
* Pending invitations are listed before accepted members, and your organization ID is shown in Organization Settings
# December 2025
Source: https://docs.didit.me/changelog/december-2025
Advanced questionnaires, real-time collaboration, AML for businesses, database validation expansion, and platform performance gains across Didit.
* **Advanced Questionnaires** — Dynamic questionnaires that adapt in real time: show/hide questions based on previous answers, split flows into sections, and preview full branching logic before publishing
* **Questionnaires Analytics** — Visual summaries and graphs for questionnaire results (including source-of-funds charts) for easier pattern spotting
* **Encrypted PDF Support** — Now supporting encrypted PDFs with password handling and improved browser compatibility
* **New Users Section** — The Console now includes a Users area with user detail pages (documents, verifications, metadata) so teams can investigate across sessions
* **Real-time Session Collaboration** — Session chat, tags, notifications, and reviews are now real-time, improving coordination for ops/compliance teams
* **AML: Business Entities** — Added support for company/business AML checks, expanding coverage beyond individuals
* **Database Validation Expanded** — Extended to Chile, Panama, Paraguay with UI improvements and lower pricing in certain cases
* **Precise Billing** — Clearer cost reporting with total cost + cost breakdown in session details
* **Whitelabel & Domain Safety** — Safer whitelabel setup with subdomain-only rules and updated app configuration
* **Extra Fields** — Added middle name, improved address handling (parsed + raw), and more country-aware name rules (e.g., Brazil edge cases)
* **Platform Performance** — Multiple backend optimizations for faster queries and fewer incidents
# February 2025
Source: https://docs.didit.me/changelog/february-2025
AML ongoing monitoring, streamlined verification, age and gender estimation, and CSV exports across Didit's identity verification platform.
* **AML Ongoing Monitoring** — Daily automated checks against 230+ databases for PEP, sanctions, warnings, and adverse media (Pro Plan)
* **18 Supported Languages** — Added new languages to the verification process
* **Streamlined Verification Flow** — Auto-skip single-option screens; auto-preselect country based on user IP
* **Demo Repos** — New demo repositories for integrating verification in Expo (React Native) and Flutter via WebView
* **Age and Gender Estimation** — Now included in the KYC flow for more comprehensive verification
* **Export Sessions as CSV** — Export verification sessions from the Business Console (Dashboard → Verifications)
* **Delete Sessions** — One-click session deletion from the Verifications section
# February 2026
Source: https://docs.didit.me/changelog/february-2026
Spanish financial sandbox results, e-commerce integrations, native SDK updates, AI agent integration, and social login on the Didit platform.
### Spanish Financial Sandbox Conclusions
After more than a year of testing inside Spain's financial sandbox, the official conclusions report from the Spanish Treasury, CNMV, and SEPBLAC has been published. The results confirm that Didit's NFC + active biometrics technology blocks the most advanced fraud scenarios — including deepfakes and document manipulation — delivering security equivalent to or superior to in-person verification.
* **Fraud reduced to residual levels** — Even sophisticated deepfake and impersonation attacks were blocked
* **EBA-compliant** — Full alignment with European Banking Authority guidelines on digital onboarding
* **Positive regulatory evaluation** — Both CNMV and SEPBLAC validated Didit's approach for financial services
* **ICAO-standard NFC verification** — Chip-based document reading valid in 150+ countries
[Read the full success story →](https://didit.me/blog/success-story-spanish-financial-sandbox-confirms-didit-new-standard-digital-onboarding)
### E-Commerce Integrations
New plugins to add identity verification directly into your Shopify or WordPress store. Merchants can configure verification preferences, pass custom data, and verify customers without leaving the platform.
* **Shopify Plugin** — Install the Didit app from the Shopify App Store and start verifying customers in minutes
* **WordPress Plugin** — Drop-in plugin for WordPress and WooCommerce sites with flexible configuration options
### Native SDKs
We shipped major SDK updates across all platforms this month.
* **Native Android SDK** — Full-featured Android SDK with document detection, camera capture, video recording, NFC chip reading, phone and email verification, resubmit support, and privacy mode
* **Native iOS SDK** — Expanded iOS 13+ support, Swift 6 compatibility, and NFC settings navigation when NFC is disabled
* **Flutter SDK** — New Flutter SDK for cross-platform mobile integration
* **React Native SDK** — New React Native SDK for cross-platform mobile integration
* **SDK v3.2.0** — Translation fixes across 47 languages and improved UI components on all platforms
### AI Agent Integration
New MCP server and programmatic APIs that let AI agents run identity verification workflows end to end — create sessions, trigger checks, and read results without a UI. Perfect for building automated onboarding pipelines or integrating verification into your own AI-powered tools.
### Social Login & Passkeys
Sign in to the Console with GitHub, Google, Apple, or Microsoft. We also added passkey support for passwordless login and SSO enforcement for organizations that require it.
### Face Search Performance
Major improvements to the face search algorithm — significantly faster and more accurate duplicate detection and blocklist matching, even at scale.
### Resubmission Flow
Users who fail verification can now resubmit their documents and selfies directly. The Console shows the resubmission status and lets your team review corrected submissions without starting over.
### Age Verification by Country
Set different age rules per country or state. Configure minimum and maximum age thresholds, and choose what happens when a user falls outside the range — decline automatically or send to manual review.
### New Document Support
* **Mongolia ID** — National identity card
* **Ontario Photo Card** — Canadian provincial photo ID
* **Mexico Driver's Licenses** — Multiple states supported
* **Germany Driver's License** — Added to supported documents
* **Date of Issue** — Now available as a workflow branching condition, so you can route sessions based on how recently a document was issued
### Improvements
* AML reports now include richer metadata for deeper compliance insights
* Image quality checks run before processing to catch blurry or dark photos early
* Low-balance email notifications so you never run out of credits unexpectedly
* Alphanumeric email verification codes for better security
* Public API endpoints for managing blocklists
* Better image compression for faster uploads on slow connections
* Improved country selector in the Console
* Proof of Address now supports months-since-issue validation
### Fixes
* Fixed document side mismatch between front and back captures
* Fixed OCR accuracy for Brazilian documents
* Fixed translation issues across all SDKs
* Fixed various authentication and SSO edge cases
# January 2025
Source: https://docs.didit.me/changelog/january-2025
New frontend design, step-by-step video, QR and barcode detection, OCR improvements, and white-label across Didit's identity verification platform.
* **New Frontend Design** — Revamped design for a more intuitive verification process with optimized AI models and reduced loading times
* **Step-by-Step Video** — Added video guide walking users through the KYC process, accessible via `kyc.front_video` / `kyc.back_video` in the session model
* **QR & Barcode Detection** — Enhanced accuracy for barcodes and QR codes
* **OCR Improvement** — Faster and more accurate document processing, with significant improvements for Arabic and other languages
* **Generate PDF API** — New API to generate PDF reports for verifications
* **White-Label** — Fully customize verification flows with your branding from the Business Console
# January 2026
Source: https://docs.didit.me/changelog/january-2026
Node-based workflows, native iOS SDK, advanced AML, webhook testing, and platform improvements across the Didit identity verification platform.
### Node-Based Workflows & Decision Engine
We have completely overhauled how verification flows are built.
* **Node-Based Workflows** — Create custom workflows with complex decision trees and specific nodes (e.g., "Determine Final Status")
* **Custom Rules** — Added support for granular custom rules within the workflow graph
* **Visual Editor** — The Console now features a new visual graph editor to help you design and visualize complex user journeys
### Native iOS SDK
We are excited to launch our fully native iOS SDK, bringing a smoother experience to Apple devices.
* **Compatibility** — Full support for devices running iOS 13+
* **NFC Capabilities** — Native NFC chip reading enabled for devices running iOS 15+
### Advanced AML & Fraud Detection
We've deepened the data available for compliance teams.
* **Risk & Match Scores** — The AML report now includes detailed `risk_score`, `match_score`, and configurable thresholds/weights
* **AML Summary Page** — A dedicated summary page in the Console for a quick overview of hits
* **Phone Fraud Signals** — Added "Shared Device Mode" and new phone verification signals to detect high-risk numbers or reused devices
### Webhook Testing
Developers can now test webhooks directly from the dashboard to ensure their listeners are configured correctly before going live.
### Improvements
* **Face Verification Benchmarks** — Updated thresholds for face matching and liveness to improve pass rates for genuine users
* **Health Insurance Cards** — Added as a valid document type
* **New Document Support** — USA California ID/DL, Costa Rica ID, Malaysia/Netherlands/Australia DL improvements, Mexico Voter Card
* **Performance** — Lower memory usage and faster processing on older devices
* **Database Optimization** — Significantly faster API response times for session lookups
* **CSV Exports** — Now includes more columns for POA and Phone verifications
### Fixes
* Fixed Android WebView `Java object is gone` error affecting post-message communication
* Fixed questionnaire migration, locale/translation loading, and header rendering bugs
* Fixed blurry photos when switching cameras on multi-lens devices
# July 2025
Source: https://docs.didit.me/changelog/july-2025
Shared sessions API, passive liveness API, face search enhancements, and low-bandwidth support across Didit's identity verification platform.
* **Shared Sessions API** — New Share Session and Import Shared Session APIs for B2B verification data sharing between trusted partners
* **Passive Liveness API** — New standalone API for performing passive liveness checks
* **Face Search Enhancements** — Refined algorithm with significantly improved accuracy for duplicate detection and blocklist matching
* **Data Retention Controls** — Set custom data retention policies from 1 month to 60 months, or indefinitely
* **PDF Multilingual Support** — PDF reports now support RTL languages (Arabic, Hebrew) with improved compatibility
* **Improved Photo & Video Capture** — Better quality during ID and face liveness checks for improved OCR accuracy
* **Faster Verification** — Reduced number of steps in the flow; quicker screen transitions
* **Language Picker** — Users can select their preferred language directly during verification
* **Low Bandwidth Support** — Verification flows run smoothly on 3G networks and older devices with dynamic feature adjustment
* **User Logout Button** — Secure logout from the verification process when authenticated
* **Real-Time Web Notifications** — Desktop users receive real-time updates when continuing verification on mobile
* **New Territories** — Western Sahara, Saint Martin, Luhansk, Falkland Islands, Christmas Island, Cocos Islands
# July 2026
Source: https://docs.didit.me/changelog/july-2026
July 2026 release: Travel Rule compliance, Console Copilot, applicant data review, Test mode, device integrity, SOC 2 Type 2 and FSM certifications, and native SDK updates.
### Travel Rule for Crypto Transfers
When your users send or receive crypto, regulators increasingly require you to exchange sender and recipient details with the counterparty institution before the transfer settles. Didit now handles that end to end.
* **Encrypted information exchange** — sender and recipient details are exchanged automatically using IVMS 101, the industry data standard, with encrypted payloads
* **Broad interoperability** — Didit routes each transfer over the counterparty's preferred network and includes a native implementation of the open TRP messaging standard
* **Secure email fallback** — when a counterparty isn't reachable on any network, Didit sends a protected pickup link so the transfer still gets compliant treatment
* **Counterparty directory and address book** — find virtual asset service providers (VASPs) in a directory seeded from the EU's official register of authorized crypto-asset providers, then save and reuse tagged wallet addresses
* **Self-custody ownership proof** — a white-labelled widget lets users prove control of their own wallet by signature (or screenshot proof where you allow it) across Ethereum-compatible networks, Solana, Bitcoin, and Tron — connecting any major wallet, including by QR code
* **Ready-made EU rules** — start from a preset bundle for the EU Transfer of Funds Regulation, and set your jurisdiction: EU, UK, Switzerland, Singapore, US, or UAE
* **Due diligence and timeouts** — every counterparty gets a due-diligence score that can gate a transfer, and you choose what happens when a confirmation deadline passes
* **Built-in testing and events** — create sample transfers in the **Console**, track usage, and receive a webhook for each status change
### Didit Copilot in the Console
Didit Copilot helps you operate the Business Console from every page while keeping you in control of consequential actions.
* **Ask from anywhere** — open the resizable Copilot panel from the **Ask** pill in the **Console** header and get starting prompts tailored to the page you are viewing
* **Build and navigate for you** — create workflows, apply list filters, and open the right dialogs through natural-language requests
* **You approve every change** — anything that writes or deletes waits for your explicit confirmation
* **Watch the work happen** — see workflow blocks appear node by node, and stop the Copilot whenever you need to
* **Talk, type, or attach** — use voice input, add a file, and return to searchable conversation history that resumes after a dropped connection
### ID Verification Data Review
A new applicant review step lets users confirm or correct extracted identity-document fields before they submit.
* **Review in every language** — applicants can check standard and document-specific fields in every supported language, including flows that also use chip reading
* **Actions by mismatch severity** — configure separate outcomes for critical and minor corrections while protecting fields validated by the document's machine-readable zone (MRZ) from edits
* **Complete correction history** — session details show each original value beside the applicant-confirmed value
* **Capture quality check** — optionally let applicants inspect and retake the cropped document image before submitting; this screen appears only for camera scans
### Test Mode
Sandbox applications now provide a complete Test mode for exercising integrations without contacting external providers.
* **Choose the result first** — pick approved, declined, or in review, including review scenarios for individual feature groups
* **Test more paths quickly** — run business-verification registry scenarios, upload sample documents with one tap, and enter any one-time password (OTP) code
* **Clear in every surface** — persistent banners identify Test mode in both the verification flow and the Console, while API keys carry environment badges
* **Visible simulated usage** — zero-price activity remains in usage tables, and mocked sections carry simulated-data chips
* **Real captures, mocked results** — captured media stays available for realistic review while extracted data remains synthetic
* **Integration-ready scenarios** — no external provider is ever called, your automated tests can assert the armed scenario (returned as `sandbox_scenario` in the v3 decision payload), and native SDKs support Test mode from version 4.2.0
### Device & Capture Integrity
New device and capture signals help you respond to tampered apps, emulators, and manipulated camera input without adding friction for legitimate users.
* **Operating-system attestation** — verify app and device integrity with Apple App Attest and Google Play Integrity
* **Capture integrity signals** — detect frame injection and show the attack subtype on face-attack warnings
* **Virtual-camera flag** — mark sessions that present a virtual or external capture device as a risk signal
* **Actions per workflow** — decline, review, or record device and capture risks according to each workflow's policy
* **Safe outage behavior** — configuration outages on Didit's side do not decline legitimate applicants, and risk reasons are translated
* **Escape from in-app browsers** — users in chat apps' built-in browsers get a clear screen for reopening camera steps in their main browser
### Workflow Builder Upgrades
The workflow builder now supports richer branching, clearer controls, and safer version management.
* **Merge and regional branches** — bring branches back to one merge point, route by US state, and apply IP geofencing rules
* **More configurable actions** — decide what happens when a phone number is flagged as high risk, when the same face appears under a different name, or when a database check does not apply
* **Recorded chip-read outcomes** — see the specific reason when near-field communication (NFC) is skipped
* **More KYB control** — configure registry fields and VAT actions, toggle beneficial owner (UBO) and shareholder collection, and accept business licences or tax registration certificates
* **True version recovery** — delete drafts and restore earlier versions for both workflows and questionnaires
* **Clearer build decisions** — see each step's price and a warning when file upload reduces forgery protection compared with camera capture
* **Safer standalone checks** — document liveness now starts enabled for the standalone ID Verification API
### Business Verification (KYB)
Business verification now collects the right registry and document information with less applicant effort across more markets.
* **EU VAT validation** — validate VAT numbers through the EU's VAT Information Exchange System (VIES)
* **Configurable company search** — choose registry fields and prefill and lock both company name and registration number for applicant confirmation
* **Local documents in 103 countries** — show familiar local document names and recognize business licences and tax registration certificates
* **Fewer false declines** — accept notarial deeds more reliably and treat user-entered information as the source of truth
* **Document AI results in KYB** — receive newly available extracted custom-document results in KYB payloads and the PDF report
* **Only enabled sections appear** — keep disabled UBO, shareholder, registration-number, and contact sections hidden from applicants
* **Larger uploads** — accept business, proof-of-address, and custom-document files up to 30 MB
### Ongoing AML Monitoring for Users and Businesses
You can now manage ongoing anti-money laundering (AML) monitoring directly for each user or business.
* **Per-record controls and status** — enable or disable monitoring from list rows and follow it in a dedicated column on the **Users** and **Businesses** tables, with screening categories explained inline
* **Know the impact first** — see a cost estimate before enabling monitoring and automatically screen identities that have not been checked before
* **One record per identity** — reuse a single monitored record instead of creating duplicates and duplicate charges
* **Transparent bulk jobs** — process many records together and see explicit skip reasons for every record that could not be monitored
### Transaction Monitoring: SDK Submission and Step-Up Remediation
Transaction monitoring now connects native submission, device intelligence, and biometric step-up remediation in one flow.
* **Submit from native SDKs** — send transactions with device intelligence attached, and receive an `action_required` response whenever the user has to do something
* **One-selfie step-up** — hold a flagged transaction while awaiting the user, then reuse the stored face for biometric remediation with one selfie and no document
* **Choose the remediation path** — select a workflow or workflow group for user-action rules
* **Richer rule inputs** — compare sender fields with receiver fields and use your custom lists in text rules
* **Faster list review** — use verification-style filters with currency and asset icons across transaction lists
### Verification Flow Experience
The verification flow is more reliable on real devices and clearer about what applicants need to do next.
* **Reliable auto-capture** — removed capture deadlocks and false “too dark” messages while improving distance guidance
* **Better camera control** — added pinch-to-zoom, localized rear-camera selection, iOS black-screen recovery, and clean camera release between document and face steps
* **Visible capture recovery** — camera failures show a retry action, microphone access never blocks the camera, and the frame border provides live feedback
* **Clearer waiting screens** — redesigned processing screens explain what is happening instead of leaving applicants guessing
* **Progress from start to finish** — the **Welcome** screen shows estimated time, while the progress bar, completion screens, error screens, one-time-code entry, and phone input use refreshed designs
* **Clearer proof of address** — show country and accepted languages, use canonical subtypes, and order document types by country, with Australia added to the per-country ordering
### Native SDK Updates
Versions 4.0.9 through 4.5.3 brought the latest Didit capabilities to iOS, Android, React Native, and Flutter.
* **Transactions and wallet ownership** — submit and retrieve transactions with automatic user actions, and run wallet-ownership proof natively
* **Test mode from 4.2.0** — use the scenario picker and sample documents directly in native integrations
* **Swift Package Manager support** — integrate the iOS SDK through Swift Package Manager in React Native and Flutter while keeping CocoaPods support
* **Smaller Flutter variants** — choose `didit_sdk_core`, `didit_sdk_autodetection`, or `didit_sdk_nfc` with the same Dart API and a smaller native footprint
* **Right-to-left and language coverage** — use Arabic right-to-left layouts and Hebrew localization
* **Capture polish and stability** — get image review by default, SMS OTP autofill, per-country document ordering, an unmirrored front camera, and more stable face capture on high-resolution Android cameras
### Document Coverage & AI Extraction
We continued June's extraction expansion with new documents, regions, and more precise field mapping.
* **New supported documents** — added Australia's Larrakia Nation ID Card and Northern Territory Proof of Identification Card, plus another Israeli ePassport variant
* **AI-powered extraction expanded again** — added coverage for Israeli and Kyrgyzstani documents, German health insurance cards, New Jersey IDs, Pennsylvania commercial driving licences, and the British Columbia ID family
* **Canonical issuing regions** — return a document's state or province as ISO 3166-2 so you can distinguish regional documents without parsing text
* **More precise document controls** — exclude the legacy Chilean carnet subtype and receive distinct Australian licence-number and card-number fields
### Database Validation & Analytics
New analytics and validation behavior make it easier to evaluate coverage, speed, and real-world outcomes.
* **Coverage and latency widgets** — compare match coverage and median and 95th-percentile response times by country and database
* **Flexible analytics views** — expand coverage to a full-screen world map and let charts choose the right time grouping for the selected range
* **Cleaner catalog** — low-coverage databases have been removed instead of returning consistently empty results
* **Regional validation updates** — derive Spain DNI/NIE addresses from proof-of-address data, while India Aadhaar no longer requires organization onboarding
* **Clearer outcomes** — incomplete requests return a clear validation error, and empty validation responses resolve to In Review instead of passing silently
### Right-to-Left Languages & Translation Quality
Arabic, Hebrew, and document translations now feel consistent across both the Console and the verification flow.
* **Arabic and Hebrew coverage** — use right-to-left Arabic layouts across the Console, Copilot, and verification flow, with Hebrew coverage throughout the flow
* **Reviewed labels and events** — get improved proof-of-address and business-document names in Estonian, Slovak, Korean, Japanese, Chinese, Catalan, Portuguese, and other supported languages, plus translated event labels
* **Locale links stay respected** — explicit language URLs keep the selected locale as you navigate
### New Certifications
Two independent credentials to announce, both verifiable from our public [Security & Compliance center](https://didit.me/security-compliance/).
* **SOC 2 Type 2** — an independent audit against the AICPA Trust Services Criteria confirmed that Didit's security, availability, and confidentiality controls **operated effectively** over a five-month observation period (March–July 2026) — the operational follow-up to April's Type 1. The report is available to customers under NDA
* **Certified for youth protection in Germany** — Germany's youth-protection self-regulator (FSM) certified that Didit's Age Verification System reliably establishes a closed user group under Section 4(2) of the Interstate Treaty on the Protection of Minors (JMStV), so only verified adults reach age-restricted content
* **Every credential in one place** — the docs gained a dedicated [Certifications page](/getting-started/certifications) listing every audit and attestation we hold, with dates and how to get each report — and we're adding more all the time
### Improvements
* **Reusable verification imports** — imported individual and business sessions return the complete decision, with `session_kind` and `shared_from_session` fields so you can trace where a reused verification came from; existing v2 integrations are untouched
* **Use Didit inside your identity provider** — identity providers can now delegate verification to Didit over OpenID Connect (with Pushed Authorization Requests and PKCE) and receive standards-based verified claims back
* **Reliable final webhooks** — the webhook that closes out a session can no longer be lost, and signature validation now works consistently for events carrying dates and amounts
* **Sessions never get stuck** — verifications stalled on a background step resume on their own, and session-list requests are answered once instead of twice
* **Balance safeguards** — balances update in real time, and new billable work stops when an account has a negative balance
* **Questionnaire drafts** — delete unpublished questionnaire versions cleanly
* **Accurate date and cost views** — custom date ranges follow your local calendar and every cost-breakdown row has a complete label
* **True-to-device customization preview** — preview every screen with the same proportions and presentation applicants see
* **Calmer Console updates** — real-time changes patch lists in place, and the sidebar stays open while you navigate
* **Findable session warnings** — the warnings filter adds search, collapsible groups, and per-group counts
* **Workflow-aware resubmission** — the resubmit dialog is built from your actual workflow graph, including branches, so you re-request only the steps you need
* **Honest check states** — checks that never ran show as "not evaluated", disabled validations read "Not applicable", and a skipped chip read states its reason
* **Sessions for existing users** — search your users when creating a session and reuse their stored portrait
* **Account security** — require a two-factor code to delete applications, recover accounts through identity-verified support, reset passkeys, protect sandbox applications from deletion, and keep application mode immutable
* **Longer, clearer invitations** — invitations remain valid for 30 days, show when expired, and correctly match email addresses from providers that treat dots as optional
* **Organization-wide administration** — owners and admins span every application, and the members page stays fast as teams grow
* **MCP server updated** — supports the latest Model Context Protocol revision (2026-07-28) while remaining compatible with existing clients
* **Deploy-safe flow reloads** — recover from a stale page after a deployment and continue the current session
* **Fewer false liveness rejections** — borderline passive-liveness results are automatically double-checked before a decision is made
* **Smarter proof-of-address review** — handle abbreviated or reordered names and respect printed expiration dates
* **More proof-of-address coverage** — accept India driving licences as proof of address
* **Safer manual checks** — run document liveness by default for individual checks started from the Console
# June 2025
Source: https://docs.didit.me/changelog/june-2025
PDF and POA enhancements, white-label improvements, Stripe invoice generation, and OCR optimization across Didit's identity verification platform.
* **PDF & POA Enhancements** — Improved text extraction, resolved missing pages, faster and more reliable POA verifications
* **White-Label Improvements** — Expanded customization including favicon support
* **Invoice Generation** — Stripe invoices integrated directly into the Business Console
* **Face Search** — Enhanced matching logic for faster and more accurate liveness checks
* **Barcode Optimizations** — Improved detection accuracy across multiple document types
* **OCR & Document Analysis** — Optimized for greater speed and accuracy
* **Adverse Media & AML** — Significantly expanded coverage and improved media quality
* **ID Verification** — Added first name, last name, expected details, and contact information to webhooks and session endpoints
* **Session Handling & API** — New endpoints for easier listing and management of sessions
* **Frontend** — Better loading indicators, improved camera support for older devices, auto-selection of document type, adapted video sizing, improved POA camera preview
# June 2026
Source: https://docs.didit.me/changelog/june-2026
June 2026 release: Document AI custom document verification, transaction detail redesign, native SDK 4.0.x updates, white-label brand import, and MCP Server v5.
### Document AI Verification
A brand-new verification feature. Define any custom document your business needs — contracts, licenses, certificates, statements — and Didit's AI reads it and extracts exactly the fields you configure.
* **Define any document** — create your own document types with the fields you want extracted, no template required
* **Auto-suggest fields from a sample** — upload a sample document and Didit proposes the extraction fields for you, with a per-organization daily limit
* **Built-in upload flow** — your users get a new "Documents" step in the verification flow, with clear retry prompts and an attempts-left counter when a file can't be read
* **Results everywhere** — extracted documents are returned in the decision and events endpoints and delivered through webhooks
* **Workflow builder integration** — auto-detect extraction fields from a sample right inside the editor, and map a full-name field so extracted names can be matched against the verified identity
* **Console review** — session details get a dedicated Document AI section, in the same style as KYB documents, showing every extracted field
* **Every language, automatically** — document definitions are translated into all supported verification languages the moment you save them
* **Native SDK support** — available on iOS, Android, React Native, and Flutter on SDK versions 4.0.7 and above
### Transaction Detail Redesign
The transaction detail page was redesigned to match the KYC session experience, so reviewing a flagged transaction feels the same as reviewing a verification.
* **Counterparty flow cards** — sender and recipient are shown as horizontal flow cards, so you see who sent what to whom at a glance
* **State-aware rule rows** — triggered rules collapse into compact rows that surface each rule's outcome and description without opening a dialog
* **Related transactions table** — related activity is now a paginated table instead of an endless list
* **Threshold-colored score** — the transaction score pill takes its color from your organization's own review and decline thresholds
* **Dark mode polish** — badges, tags, and section animations were tuned for full dark-mode legibility
### Native SDK Updates
A steady stream of 4.0.x releases across iOS, Android, React Native, and Flutter this month.
* **Document AI support** — the new custom-document verification step works natively in every SDK
* **KYB upgrades** — document requirements guide users to upload the right business document, and ownership percentage is now optional to match industry practice
* **PACE NFC on Android** — passports and IDs protected with PACE can now be read over NFC on Android
* **Social Security Card** — new document type supported across all four SDKs
* **iPhone 17 liveness fix** — liveness video recording issues on the newest iPhones were resolved
* **App Store submission fix** — resolved the iOS privacy-manifest warning so your submissions pass review cleanly
* **Algeria NFC** — chip reading now works for Algerian documents
* **Active liveness polish** — removed the brief screen flash when the active-liveness intro starts
### White-Label Upgrades & Brand Import
Making the verification flow look like *your* product now takes seconds instead of an afternoon.
* **Import your brand from your website** — enter your URL and Didit detects your logo, colors, and fonts and maps them onto the verification flow automatically (rate-limited per organization)
* **Applied from day one** — new organizations can have their branding imported and applied automatically during onboarding
* **Logos rendered faithfully** — customer logos are never clipped, cropped, or force-rounded, whatever their aspect ratio
* **Theme-aware everywhere** — every screen, including KYB steps, radio groups, and loaders, follows your theme
* **Softer default look** — the default white-label corner radius moved to 24 for a more modern feel
### Notification Preferences
You now control exactly which notifications you receive, per event and per channel.
* **Preferences matrix** — a new Notifications tab in Account Settings lists every event with a toggle per channel, per user
* **Conservative defaults** — new accounts start with only the notifications that matter, not everything
* **Slack disconnect** — unlink your Slack notification channel in one click
* **Send a test** — fire a test notification to confirm a channel works before you rely on it
* **Throttled failure alerts** — webhook-failure notifications are limited to one per 30 minutes, so an outage doesn't flood your inbox
* **Redesigned emails** — notification emails were rebuilt to match the clean design of the rest of Didit's emails
### KYB Registry Improvements
Business verification keeps getting more automatic — less typing for applicants, more data for reviewers.
* **Company field prefill** — registration number, address, share capital, and location are prefilled from the registry, so applicants only confirm
* **Previous and parent names** — a company's former names and parent-company names now appear on the business detail page
* **Registry section** — registry check results render in their own dedicated section on business details
* **Optional UBO email** — email is no longer required for beneficial owners who don't need to complete KYC
* **Automatic UBO emails** — beneficial owners who do need KYC receive their verification email automatically, with one-click resend
* **No duplicate parties** — associated parties confirmed once can be reused instead of being added twice
### Smarter Transaction Rules
The transaction-monitoring rule engine understands more of your data.
* **New operators** — rule conditions now support date comparisons, regex matching, fuzzy matching, and `in` (match against a list of values), in the API and the Console rule editor alike
* **Better crypto screening** — wallet screening detects the correct chain for each transaction and surfaces screening failures instead of hiding them
* **Richer network graph** — the counterparty network graph shows a more complete picture of the accounts around a transaction
* **Live updates** — transaction lists and detail views update in real time, no refresh needed
### MCP Server v5.0.0
The Didit MCP server — the way AI agents run verifications, screen wallets, and manage your workflows — had its biggest release yet.
* **Public marketplace release** — v5.0.0 ships with a registry manifest and a Cursor plugin, and its tool metadata is aligned with OpenAI's app review guidelines, so you can install Didit wherever your agents live
* **Scoped access tokens** — agents exchange credentials for narrowly-scoped tokens using the standard token-exchange flow, so each agent only gets the permissions it needs
* **Org-scoped user tokens** — the session API now accepts organization-scoped user tokens, so agents can act on behalf of a signed-in user within the right organization
### Expanded AI Document Extraction
AI-powered data extraction was enabled for roughly 300 additional document types this month, focused on documents from Arabic-speaking countries and document types that historically had lower extraction accuracy. Expect noticeably better results on these documents with no integration changes on your side.
### Improvements
* **Faster verification flow** — the verification web flow loads significantly less code up front, making it noticeably faster on slow connections
* **Responsive Console redesign** — a sweep across the Business Console brought a responsive layout, restyled inputs, and consistent tables to every page
* **Column visibility** — Users and Businesses tables gained a column-visibility toolbar, matching the sessions list
* **Organization-level lists** — list sessions, businesses, transactions, and cases across all applications in your organization through the API, with each row attributed to its application
* **Guided onboarding** — a new post-signup create-organization wizard plus a self-driving guided tour that performs each step for you
* **Demo organization** — the demo environment now seeds 1,000+ realistic sessions so you can explore the Console with meaningful data
* **Sandbox template gallery** — pick a ready-made test persona and the sandbox sets the country and document type for you
* **Field blurring everywhere** — document field blurring for data minimization is now available for all countries, not just the EU
* **Better data imports** — CSV imports report specific row-level errors before committing, and imported records carry an "Imported" badge across lists
* **Stay signed in** — Console sessions now stay alive for about 30 days through silent renewal instead of logging you out
* **Console in Italian** — the Business Console is now available in Italian
* **Rename questionnaires** — published questionnaires can now be renamed
* **Questionnaire input validation** — questionnaire fields in the verification flow validate their format as the user types, with inline error messages
* **Password security** — changing your password now signs out your other active sessions
### Fixes
* Fixed the verification flow showing front-side instructions after navigating back from the back-side photo
* Fixed completed sessions occasionally missing their redirect when the final status update was dropped — the flow now recovers and redirects reliably
* Fixed KYB sub-sessions (beneficial owners and key people) starting on an outdated workflow version instead of the latest published one
* Fixed custom status rules not taking other status rules into account
* Fixed company AML screening applying cross-script name matching meant for people, which produced false-positive matches on business names
* Fixed names screened through database validation not appearing in session search results
* Fixed white-label logo and application name on verification emails sent through the API
* Fixed two-factor accounts with both a passkey and an authenticator app being forced into the passkey popup — you now choose the method
* Fixed adding faces to the blocklist failing in certain cases
* Fixed Chilean ePassport number validation and gender extraction on Mexican documents
* Fixed permission edge cases in organization member invitations
# March 2025
Source: https://docs.didit.me/changelog/march-2025
Enhanced AI models, liveness detection upgrades, 47-language coverage, and improved barcode detection across Didit's identity verification platform.
* **ID Verification** — Enhanced AI model accuracy with support for numerous new documents issued in the last quarter
* **Liveness Detection** — Flash and 3D Action models upgraded with 30% better accuracy and improved UX
* **White-Label** — Now billed per completed verification rather than per initiated one
* **AML Sources** — When a hit appears during AML Screening or Continuous Monitoring, you can now access the source directly from the Console
* **47 Languages** — Expanded to support 47 different languages. See [Supported Languages](/integration/supported-languages)
* **Barcode Detection** — Enhanced barcode detection on identity documents
* **IP Geolocation** — Much more accurate VPN and private network detection
* **Document Liveness** — Improved models to better identify photocopies or inconsistencies in real-time document capture
# March 2026
Source: https://docs.didit.me/changelog/march-2026
Revamped analytics, document liveness, database validation expansion, workflow improvements, and native SDK updates across Didit's identity platform.
### Revamped Analytics Dashboard
The analytics dashboard received a major overhaul with richer data and better performance across the board.
* **More Metrics** — New widgets covering warning breakdowns, country distribution, and feature-level stats
* **Faster Loading** — Optimized queries for significantly faster dashboard rendering
* **Improved Filters** — More reliable date-range, workflow, and status filtering
### Node-Based Workflow Improvements
The visual workflow editor shipped in January received a wave of usability and reliability improvements this month.
* **Better UX** — Redesigned node layout, improved preview screen, and smoother drag-and-drop interactions
* **Validation on Publish** — Workflows are now validated before publishing, catching configuration errors early
* **Questionnaire Nodes** — Improved questionnaire integration within graph workflows, including better draft and propagation logic
* **Custom Status Rules** — You can now define custom status rules for simple (non-graph) workflows too — not just node-based ones
### Console Security & Authentication
* **Redesigned 2FA** — Choose between passkeys and authenticator apps with a cleaner two-factor authentication screen
* **Passkey Management** — Add and manage passkeys from your account settings
* **Connected Devices** — See which devices are signed into your account
### Document Liveness
* **Three Configurable Actions** — Choose what happens when document liveness fails: approve, decline, or send to manual review
* **Workflow Integration** — Configure document liveness directly in the workflow editor alongside other features
### Database Validation Expansion
* **Argentina (RENAPER)** — Real-time identity verification against Argentina's national civil registry, joining Brazil, Dominican Republic, Chile, and Paraguay
* **Panama** — Improved integration for more reliable results
### Custom Email Branding
White-label customers can now set a custom sender email address for verification emails, so end users see your brand — not Didit's — in their inbox.
### User Profile Editing
You can now edit a user's date of birth and full name directly from their profile page in the Console — useful for correcting OCR errors or updating records after manual review.
### New Languages
The verification flow, Console, and native SDKs now support three additional languages:
* **Albanian**
* **Bosnian**
* **Kyrgyz**
### Native SDK Updates
Major updates across all native SDKs this month.
* **Android SDK v3.4.1 – v3.4.3** — Fixed document back upload loop, step transition freeze on repeated liveness, questionnaire translations, Samsung file upload picker, and responsive layout issues
* **iOS SDK v3.2.2** — Removed microphone requirement from passive liveness, stripped OpenSSL bitcode for smaller binary size, and resolved symbol collisions with other SDKs
* **Portrait Lock** — The verification process now forces portrait orientation on mobile to prevent layout issues
* **Close & Exit Configuration** — Unified close/exit behavior across iOS, Android, Flutter, and React Native SDKs
### New Document Support
* **Korea** — Improved Korean ID document recognition
* **Ireland** — Enhanced driver's license accuracy
* **Brazil** — Updated support for the 2022 driver's license format
* **India & Panama** — Fixed ID recognition issues
* **Guatemala** — Added support for alien residence cards
* **Mexico** — Fixed surname parsing for names with only two words
### Improvements
* Session creation is faster with fewer database queries per request
* Full-name search is now normalized for more accurate results across different name formats
* Selfie capture added when creating database validation sessions for some countries for stronger identity matching
* NFC certificates expanded for broader document chip reading coverage
* Questionnaire draft and propagation logic improved for more reliable form building
* Blur detection added for document images — flagging low-quality captures before processing
# May 2026
Source: https://docs.didit.me/changelog/may-2026
May 2026 release: native SDK 4.0 with modular variants, configurable document liveness, South Africa database validation, and Mexico INE checks.
### Native SDK 4.0 — Modular Variants
The biggest native SDK release of the year. iOS, Android, React Native, and Flutter all shipped a 4.0 line that splits the SDK into independent variants so you only ship the binary you actually need.
* **Four variants** — `Core` (no NFC, no auto-capture), `AutoDetection` (auto-capture, no NFC), `NFC` (NFC reading, no auto-capture), and `All` (everything)
* **84% smaller binary on iOS Core** — about 6 MB on disk and 3 MB compressed in the IPA, down from \~40 MB in 3.x for apps that don't need MediaPipe-backed auto capture
* **Backward compatible** — existing `pod 'DiditSDK'` and Gradle integrations keep working unchanged; `All` is the default
* **Per-platform pickers** — CocoaPods subspecs and SPM library products on iOS, separate artifacts on Android, Expo config plugin flags on React Native, and `DIDIT_SDK_IOS_VARIANT` / `$DiditSdkIosVariant` on Flutter
* **Migration guide** — new tables in every SDK README explain the install matrix and how to move from 3.x
### Configurable Document Liveness
Document liveness moved from a single approve/decline switch to a richer per-fraud-type configuration, exposed both in the API and in a brand-new Forgery tab inside the workflow editor.
* **Per-fraud thresholds** — set independent **Decline** and **Review** scores for Screen Replay, Printed Copy, and Portrait Replacement
* **New Forgery tab in the Console** — three sliders replace the older approve/review/decline chips, with full localization across all supported languages
* **Evidence on every warning** — session tooltips now show the actual liveness score, the active thresholds, the decision bucket, and which side of the document produced the result
* **Smarter on uploads** — false-positive "printed copy" warnings on legitimate scanned or photographed IDs were removed; screen-replay and portrait-manipulation still trigger on uploads
### Front and Back Consistency Check
A new check catches users who upload the front of one ID and the back of a different one — caught for the first time on a Bolivian cedula in production.
* **New `DATA_INCONSISTENT` risk** — fires when OCR data on the front doesn't match the back
* **Workflow action** — route inconsistent submissions to Decline, Review, or Approve through the existing `inconsistent_data_action` setting
* **Persisted correctly** — the saved front image always reflects the front step, even when OCR initially swaps front and back labels
### South Africa Database Validation Expansion
South Africa coverage grew from a single national-ID check into a full data-source suite, all callable through the same database validation API.
* **Department of Home Affairs** — fingerprint match and DHA photo lookup
* **Vehicle data** — ownership and lookup by VIN
* **Business data** — company registry, company directors, and person directorships
* **Banking and consumer signals** — bank account holder verification and contactability
* **Fraud and AML** — fraud prevention check, refugee verification, and criminal screening by face
* **Cheaper national ID** — the South African national-ID validation moved to a new integration with significantly lower per-call cost
### Mexico INE Voter-Card Validity
Mexican INE/IFE voter cards can now be validated against the official registry in real time.
* **Document validity** — confirms the credential model (`modelo`), expiration (`vigencia`), and whether it's valid as identification
* **Voting rights** — confirms the holder's right-to-vote status
### AML Screening Optimization
AML screenings are now deduplicated on resubmits when the identity hasn't changed, so you stop getting billed for repeated screenings of the same person.
* **Skip on identical resubmits** — name, date of birth, nationality, and document number all unchanged from the prior screening means no new screening (and no new charge)
* **Unchanged behavior** when the identity actually changes, on the first screening, in sandbox sessions, and on the standalone AML endpoint
* **Applies to KYB too** — company AML screenings dedupe the same way for unchanged business identity on resubmit
### Upload User Face
Faces imported through the **Upload User Face** endpoint now behave like any face captured in a verification session.
* **Visible on the user profile** — the portrait shows up on the Console user-detail page like an organic capture
* **Included in duplicate detection** — imported faces are now part of the 1:N search index, so re-registrations under a different vendor data identifier are correctly flagged
* **Backfill-friendly** — useful when migrating an existing identity database into Didit
### Whitelabel Custom Domain Errors
Custom verification domains no longer silently flip to "Verified" when your DNS provider's CAA records actually block the certificate from being issued.
* **Real error surfaced** — the Console now shows the exact reason (typically a CAA record blocking AWS from issuing the SSL cert)
* **5-step recovery instructions** — the exact DNS records you need to add are listed in the Customization page
* **No more invisible failures** — domains that look verified but don't load are caught before they reach end users
### Redesigned Transactional Emails
All 11 authentication and organization emails were rebuilt to match the Console design system — clean white layout, full-width pill CTA, and a text-forward footer with `hello@didit.me`. White-label customers still get their own logo and theme on the verification email.
### Compound Name Matching
Database validation lookups now correctly handle compound first names across consumer-records services.
* **US** — "Jorge Luis", "Miguel Angel Jr", and similar Spanish double-given-name patterns now match correctly
* **UK** — compound first names like "Lukasz Marcin" are parsed as first + middle instead of being treated as a single token
### New Document Support
* **Tax Card document type** — a new document subtype added across iOS, Android, Flutter, and React Native SDKs for tax-residency verification
* **Netherlands 2025 driver's license** — the new design is supported end-to-end
* **Brazil Digital Driving License** — two-sided captures with "Side C" / "Page B" markers now save the correct front photo and back image
* **Spain Refugee Convention Travel Document** — and similar stateless travel documents using D / DV / TI MRZ prefixes and `XXA` nationality now parse correctly
* **11 newly-supported documents** synced into the production registry from real-world submissions
### Mongolian Language
The verification flow and all four native SDKs now support Mongolian (mn), bringing total language coverage to 54.
### Native SDK Releases
Beyond the 4.0 modular variants, every native SDK shipped a steady stream of stability fixes through May.
* **Android SDK 3.5.3 – 4.0.2** — Document photo quality and face-intro styling, recovery after ambiguous network failures, white-label theme flash fix on completion, camera switching with state reset for both document and liveness, upload guards when video segments are missing, terms and translation updates, front-camera selfie capture fix, back-button and questionnaire polish, Mongolian language, and the 4.0 line with configurable default cameras and switcher visibility
* **iOS SDK 3.3.3 – 4.0.2** — Document capture quality and playable MP4 / H.264 uploads, step recovery after interrupted backend responses, optional no-NFC build, inline podspec license, 3.5 / 3.6 routine releases, a critical fix for an `EXC_BAD_ACCESS` crash on iOS 13–17 introduced by a Swift 6 runtime symbol, Mongolian language, and 4.0 with the modular variant split and Tax Card support
* **React Native SDK 3.2.11 – 4.0.1** — Bumped to the latest native iOS and Android releases each cycle, added `iosNfcEnabled` / `androidNfcEnabled` flags to the Expo config plugin so consumers can pick the no-NFC variant, and shipped 4.0 with the full variant matrix
* **Flutter SDK 3.5.0 – 4.0.2** — Pulled in the latest native iOS and Android releases, added an Android no-NFC build option, exposed the full native configuration surface (`showCloseButton`, `showExitConfirmation`, `closeOnComplete`), and shipped 4.0 with native variant selection and Tax Card support
### Device Intelligence
Server-side device fingerprinting got more accurate and harder to fool.
* **Deterministic anchor required** — fuzzy fingerprint matches now require a hardware-rooted identifier (Android SSAID or Widevine, iOS `identifierForVendor`) before two sessions can be linked, eliminating false-positives
* **TLS / JA4 corroboration** — web fingerprints are now corroborated against TLS fingerprint signals for stronger deterministic match classification
### Improvements
* Stale "image too blurry" warnings no longer attach to approved sessions when the user successfully retook the document on the final allowed attempt
* The face-capture algorithm now uses real bounding-box geometry instead of normalized ranges, with new "move away" and "move closer" instructions and device-specific stability tuning
* Camera permission errors are classified consistently across the verification flow, with clearer messages distinguishing permission timeouts from camera-access failures
* Unverified accounts can no longer permanently squat an email address — re-registration takes over the unverified row instead of being blocked
* Workflow graph and questionnaire-editor fullscreen toggles now work on Safari
* Australian ImmiCard and travel-document fields, New Zealand passport expiration, and structured-address resolution were fixed across roughly 35 address-aware database validation services
### Fixes
* Fixed corrupted `front_image` crop returned by the ID Verification API when front and back were swapped during OCR (Argentina 2019 and 2020 IDs)
* Fixed missing `FILE_UPLOADED` events on resubmitted sessions, which had caused resubmitted documents to show up with no file in the Console
* Fixed translations and white-label customization issues across the verification web flow
# November 2025
Source: https://docs.didit.me/changelog/november-2025
Front-camera desktop, face privacy mode, unilinks, SSO, and data retention up to 10 years across Didit's identity verification platform.
* **Front-Camera on Desktop** — Laptops/desktops now use the front camera by default with better device handling (including iPhone/iOS and Huawei setups)
* **Face Privacy Mode** — For passive liveness, admins can enable a privacy mode with a blurry effect during the check
* **Sharper Video** — Higher quality iPhone recordings, reduced black-screen edge cases, and more reliable WebView redirects
* **Quicker 2FA Steps** — Enter key submits SMS/email codes; phone inputs auto-format (even in countries with multiple calling codes)
* **Unilinks** — Reusable, no-code verification links for any workflow. Share a single permanent link instead of creating sessions via API
* **Age Rules: Min & Max** — Enforce both minimum and maximum age with configurable actions (decline or set to In Review)
* **Bengali Language** — Product now available in Bengali
* **SSO & Authentication** — New sign-in methods, device management, and SSO setup under organization settings
* **Questionnaires Simplified** — Cleaner, faster editor for creating/editing forms (sections, translations, uploads)
* **Manual Checks Upgraded** — Clearer views for Face Match/POA/AML/ID, new risk indicators, better PDF exports
* **Database Validation** — More options, streamlined UI, continued 1×1 and 2×2 match support
* **Data Retention up to 10 Years** — Set longer data-retention windows directly in settings
* **Similarity Tuning** — Optional ethnicity-based thresholds for duplicate face detection
* **Workflow UX Polish** — Session approval controls, clearer status displays, reliable reusable links, accurate whitelabel preview
# October 2025
Source: https://docs.didit.me/changelog/october-2025
Desktop verification, manual checks in the Console, OCR improvements, and admin quality-of-life updates across Didit's identity verification platform.
* **Desktop Verification** — Complete ID + liveness verification on desktop with a polished, responsive UI
* **Smoother Liveness & Face Capture** — Fewer false errors, better handling on older/quirky devices (incl. Huawei models)
* **Faster Uploads on Slow Networks** — Images/PDFs compressed automatically; previews attach when possible
* **Clearer Toasts** — Replaced disruptive alerts with consistent, dismissible messages
* **Better Address Forms** — Smarter suggestions, per-field validation, support for extra address info (apartment, street 2)
* **More Reliable Document Capture** — Fixed image rotation issues and improved front/back capture flows
* **Questionnaires Easier** — Cleaner phone/email inputs, multi-file/image fields, improved multilingual support
* **New Event Tracking** — Improved status updates, reduced "stuck step" situations
* **Admin Console Quality-of-Life** — Richer document previews (incl. PDFs), persistent filters, events timeline grouped by day
* **Manual Checks** — Perform AML/ID/Liveness/POA checks in the Console without user interaction
* **OCR Optimization** — Major accuracy and speed improvements for Japan, Colombia, and Bolivia
# September 2025
Source: https://docs.didit.me/changelog/september-2025
Redesigned Console, email verification, questionnaires, multi-channel phone flow, and session lists at scale across Didit's identity platform.
* **New Console Design** — Redesigned Console with better navigation and more intuitive UX
* **Email & Phone Blocklist** — Backend support for email/phone blocklisting with per-session info and pagination
* **Email Verification** — Now available in workflows and APIs with corresponding frontend screens
* **Questionnaires** — Launched end-to-end for collecting structured attestations and supporting documents via customizable forms
* **White-Label Configuration** — New WL config across flows with more customization flexibility
* **ID/POA Enhancements** — ID flow supports PDF uploads; POA returns document metadata
* **Compliance Links & Policies** — Added Privacy Policy and Terms of Service URLs with data-retention settings
* **Session Lists at Scale** — Heavily optimized queries with normalized search
* **Phone Flow** — Multiple channels (SMS, WhatsApp, Telegram, Discord, Viber); clearer status semantics; better OTP UX
* **Frontend Polish** — New WL theming, improved KYC step list, progress bars, camera overlays, country selection
* **Events & Telemetry** — More precise events, richer device context, targeted debug logs
* **Docs & Media Handling** — PDF to JPG conversion stabilized; better frame-rate handling; more video formats
* **Internationalization** — More translations (incl. Montenegrin and Emirates ID), robust country handling
# September 2026
Source: https://docs.didit.me/changelog/september-2026
ID Verification gets three methods: photograph a document, check a national ID number against an authoritative source, or sign in with a digital ID wallet.
### Three ways to verify an identity
ID Verification is still one configurable feature, with billing determined by the method used, but a document photograph is no longer the only way through it. You now choose, country by country, which methods a person may use.
* **Document capture, unchanged** — every document type, capture rule and threshold you already configure keeps working exactly as before, and a workflow that says nothing about methods stays document capture only
* **Non-document lookup, available now** — the user types their national ID number plus a few personal details and we check them against the authoritative source for that country. No photograph, no upload. That source is a government register in most countries and a credit-bureau, financial-services, utility or residential-records file in some, so [the coverage table](/core-technology/id-verification/non-doc-lookup) names the exact source per country
* **Digital ID wallets, not live yet** — the roadmap is published and configurable in the console as **Coming soon**, but no wallet can be enabled or offered to an end user in production yet. MitID, BankID, itsme, Vipps, iDIN, UAE PASS, gov.br and the EUDI Wallet are on [the wallet page](/core-technology/id-verification/digital-id-wallets); talk to your account team about being in the first cohort
* **Choose per country** — the **Countries** tab on the ID Verification step turns each method on or off for each country, and the same settings are available through the API
### Sources that return a photograph verify the person too
Where a government register returns the person's portrait — South Africa, Nigeria, Argentina and Panama — the lookup also takes a selfie, runs passive liveness on it and matches it to that portrait. All of it is included in the lookup price, and the workflow can skip its own Liveness and Face Match steps afterwards because they would repeat work already done.
### You decide what happens when a method does not land
Every non-document method has a switch for each way it can fail: a partial match, no match, no response from the source, or a wallet sign-in that was cancelled or timed out. Each one either sends the user to document capture or declines the session.
* **Retries are yours to set** — one to five attempts, two by default, and only lookups the source actually answered are counted
* **A typo costs nothing** — a number that fails the format check never reaches the source, so it is neither counted nor billed
* **The console shows the bill before you save** — each switch spells out what that configuration costs
### Every result says how it was verified
Sessions, the decision API and the `status.updated` webhook now carry the method that produced each ID Verification and the assurance tier that goes with it — **Documentary** for a document, **Data match** for a lookup, **Cryptographic** for a wallet. The tiers describe different evidence; they are not a ranking.
* **Registry comparison** — every compared field with what the user provided, what the source held, and whether it matched, plus an error row when a source did not answer
* **Credential details** — the wallet, its issuing authority, the level of assurance it asserted, and whether the signature checked out
* **Existing integrations are untouched** — a document session reports `document` and `documentary` and leaves the new fields empty
### Pricing
Document capture is unchanged: \$0.15 per check with the first 500 checks each month free. Lookup prices are published in the [country and identifier table](/core-technology/id-verification/non-doc-lookup#coverage-and-pricing), in USD per answered attempt. Multi-source attempts sum answered service rates; retries can add charges. Wallet prices remain pending publication and no placeholder rate is quoted.
* A source that answered bills the lookup, whether it matched or not
* A source that never answered is not billed
* An abandoned or failed wallet sign-in is not billed
* A user who falls back to document capture pays for that capture on top
Read more in [ID Verification methods](/core-technology/id-verification/verification-methods), [Non-document lookup](/core-technology/id-verification/non-doc-lookup) and [Digital ID wallets](/core-technology/id-verification/digital-id-wallets).
### Web SDK 0.3.1
`@didit-protocol/sdk-web` 0.3.1 is on npm. Upgrade with `npm install @didit-protocol/sdk-web@latest`; nothing in the API surface changed.
* **The modal no longer clips** — on short desktop and landscape viewports the iframe tracks the visible viewport instead of a fixed 700px, so the last step of a verification is always reachable
* **Embedded mode fills your container** — it uses your host element's content box instead of inheriting the modal's height cap
* **`SDK_VERSION` tells the truth** — it is generated from the package version in every build format and in the TypeScript declaration. Earlier releases reported `0.2.1` no matter what you installed
Read more in the [Web SDK guide](/integration/web-sdks/javascript-sdk).
### Mobile SDK capture reliability
[iOS SDK 4.7.6](/integration/native-sdks/ios-sdk), [Android SDK 4.7.6](/integration/native-sdks/android-sdk), [React Native SDK 4.7.7](/integration/native-sdks/react-native-sdk), and [Flutter SDK 4.7.6](/integration/native-sdks/flutter-sdk) are available.
Both wrappers pin the native SDKs to 4.7.6, including every Flutter variant package.
* **More reliable iOS recording** - completed recordings are accepted when the camera reports success, and face capture waits for every recording segment to finish.
* **Document capture stays locked while saving** - the shutter stays disabled until the captured media has finished processing.
* **Sandbox catalog access** - iOS requests include the session credentials needed to load the catalog.
* **Aligned Android packages** - all six Android artifacts use 4.7.6, with no behavior changes from 4.7.5.
No integration API changes are required.
# Didit V3 Launch
Source: https://docs.didit.me/changelog/v3-launch
Didit V3 launches: unified identity verification platform with pay-per-call pricing, graph workflows, KYB, transaction monitoring, and 220+ countries.
We're thrilled to announce the launch of **Didit v3** — a major leap forward in identity infrastructure!
### What's New in v3
**Workflow & Automation Power-Ups**
* **Graph-Based Workflows** — Create complex, visual workflows to map any identity flow. Centralize, automate, and optimize your operations and compliance
* **Workflow Templates** — Get started faster with pre-built templates in the Console
* **Conditional Questionnaires** — Build sophisticated logic into questionnaires for dynamic user experiences
**Enhanced Verification & Security**
* **Advanced AML** — Define match scores and weight names, DOBs, and countries to precisely determine match percentages
* **Improved UI/UX** — Continuously optimized based on global A/B testing
* **New Database Validations** — Expanded country coverage
* **Age Estimation** — Privacy-preserving age estimation for compliance
### Pricing Updates
*Effective February 15 at 00:00 UTC*
**Free Tier** — 500 free checks per month (resets monthly), each feature counted independently.
| Feature | Price after free tier |
| -------------------- | --------------------- |
| ID Verification | \$0.15 |
| Passive Liveness | \$0.10 |
| Face Match 1:1 | \$0.05 |
| Device & IP Analysis | \$0.03 |
**Price Reductions**
| Feature | New Price | Previous Price |
| ------------------ | --------------------- | -------------- |
| Phone Verification | Dynamic carrier rates | — |
| White Label | \$0.20 | \$0.30 |
| AML Screening | \$0.20 | \$0.35 |
| Proof of Address | \$0.20 | \$0.50 |
No changes to existing enterprise contracts.
# Analytics dashboard
Source: https://docs.didit.me/console/analytics
Track KYC, KYB, and transaction performance: conversion rates, workflow funnels, verification times, geography, top rules, analyst performance, and more.
This Didit Academy lesson shows where analytics live in the console, then covers date filters, the geography and channel splits, the audit log, applications and environments, and the five built-in roles.
The analytics dashboard gives you a shared view of KYC, KYB, and transaction activity directly inside the Business Console. You can keep the default widget set, or customize the home dashboard by adding, removing, and reordering widgets for the whole application.
Volume widgets compare the selected date range against the immediately previous period. On the transactions overview, the approved, rejected, and in-review trend chart also overlays the previous period so you can spot changes in decision mix at a glance.
## Key Metrics
| Category | Metrics |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **KYC** | Verification volume, workflow funnels, average active verification time, conversion by country, resubmission insights, automatic verdict breakdown (how many completed sessions the system originally classified as approved, in review, or declined before any manual override), demographics, and device distribution |
| **KYB** | KYB request volume, average processing time, business-country distribution, IP distribution, automatic verdict breakdown (how many completed business sessions originally landed in approved, in review, or declined), and device context for business sessions |
| **Transactions** | Transaction count and volume, average amount, category and action-type breakdowns, approved vs. rejected vs. flagged trends, user geography, top rules triggered, analyst performance, and score histograms |
| **Home dashboard** | A shared per-application layout that can mix KYC, KYB, and transaction widgets in a single customizable view |
## Home dashboard customization
The **Dashboard** page is now customizable per application:
* Click **Add widgets** to browse all supported KYC, KYB, and transaction analytics.
* Remove widgets you do not need from the selected list.
* Drag widgets to reorder the layout.
* Save the layout once and every teammate looking at the same application sees the same home dashboard configuration.
## Overview pages
The console overview pages use the same analytics system:
* **Dashboard**: mixed home dashboard with your saved widget layout.
* **KYB overview**: KYB-focused widgets using the same date-range filters and visual system.
* **Transactions overview**: transaction-specific analytics including rule, geography, analyst, and score widgets.
## Filtering
The dashboard supports scope-aware filtering:
* **Workflow**: Filter analytics by a specific workflow. Defaults to the latest published workflow.
* **Version**: Filter by a specific published version of the selected workflow.
* **Branch Path**: For workflows with conditional branching, filter by a specific path through the workflow. Each path is labeled with its conditions and features.
* **Time Range**: Last 7 days, 30 days, 90 days, 12 months, or a custom date range.
* **Transaction tag**: Filter the transactions overview by a single application tag.
* **Transaction type**: Filter the transactions overview by finance or travel-rule flows.
* **Action type**: Filter the transactions overview by the customer-defined action type, such as `deposit`, `withdrawal`, or channel-specific actions.
## Active Verification Time
The average verification time metric tracks only the time users are actively engaged in the verification flow. If a user starts a step, leaves, and returns later to continue, the gap is **not** counted. This gives you an accurate picture of the actual user experience duration.
# Audit logs
Source: https://docs.didit.me/console/audit-logs
Track all API activity with searchable audit logs. Filter by user, method, status, and date range for compliance audits, security review, and debugging.
Audit Logs provide a comprehensive, searchable record of all API activity within your organization. Every request made to the Didit platform — whether from the Console, your integration, or team members — is automatically logged for security, compliance, and troubleshooting.
***
## Why audit logs?
| Challenge | Solution |
| ---------------------------------- | ------------------------------------------- |
| Regulatory compliance requirements | Complete 1-year audit trail of all activity |
| Security incident investigation | Trace exactly who did what and when |
| Debugging integration issues | See the exact requests and responses |
| Team accountability | Track which team members accessed what data |
| Usage monitoring | Understand API consumption patterns |
***
## Accessing audit logs
Navigate to **Audit Logs** in your Didit Console sidebar. The interface displays a chronological list of all API requests made within your organization.
Each log entry includes:
| Field | Description |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| **Timestamp** | When the request was made |
| **User** | Email of the authenticated user (empty for API key requests, which are attributed to the application) |
| **Method** | HTTP method (GET, POST, PUT, DELETE) |
| **Path** | The API endpoint that was called |
| **Status** | HTTP response status code |
| **IP Address** | Origin IP of the request |
| **Application** | Which application the request was associated with |
***
## Filtering and search
The Audit Logs interface provides powerful filtering capabilities to help you find exactly what you're looking for.
### Smart search
The search bar automatically detects what you're looking for:
| Search type | Example | Behavior |
| ---------------- | -------------------------------- | --------------------------------------- |
| **Exact path** | `/v1/organization/.../sessions/` | Finds logs with this exact request path |
| **Exact email** | `admin@company.com` | Finds logs from this specific user |
| **Exact IP** | `192.168.1.100` | Finds logs from this IP address |
| **Wildcard** | `/v1/*/analytics/*` | Matches patterns with wildcards |
| **General text** | `sessions` | Fuzzy search across all fields |
### Available filters
| Filter | Description | Example |
| --------------- | ------------------------------ | ------------------------------ |
| **Application** | Filter by specific application | Select from dropdown |
| **Method** | Filter by HTTP method | `GET`, `POST`, `PUT`, `DELETE` |
| **Status Code** | Filter by response status | `200`, `401`, `500` |
| **Date Range** | Filter by time period | Last 7 days, custom range |
***
## Data retention
Audit logs are retained for **1 year (365 days)** to meet common compliance requirements:
| Timeframe | Availability |
| ----------------- | --------------------- |
| Last 24 hours | Available |
| Last 7 days | Available |
| Last 30 days | Available |
| Last 90 days | Available |
| Last 365 days | Available |
| Older than 1 year | Automatically deleted |
For extended retention requirements, contact our support team to discuss enterprise options.
***
## Security and privacy
### What's logged
Audit logs capture metadata about API requests:
* Request timestamp and duration
* User identity (email, user ID)
* Request path and query parameters
* Response status codes
* Client IP address and user agent
### What's NOT logged
To protect sensitive data, the following are automatically excluded:
* Request/response bodies
* Authentication tokens and credentials
* Passwords and secrets
* Personal data from verification sessions
### Access control
Audit log access is restricted to users with **Admin** or **Owner** roles in your organization. Regular team members cannot view audit logs unless explicitly granted elevated permissions.
***
## Common use cases
Demonstrate to auditors that you have complete visibility into who accessed verification data:
1. Filter by date range matching the audit period
2. Filter by specific applications or users if needed
3. Export or screenshot the results for documentation
If you suspect unauthorized access:
1. Search for the affected user's email or suspicious IP addresses
2. Filter by date range around the suspected incident
3. Look for unusual patterns: failed authentication attempts, unexpected endpoints, odd hours
When troubleshooting API integration issues:
1. Search for the specific endpoint path
2. Filter by `4xx` or `5xx` status codes to find errors
3. Note the timestamps to correlate with your application logs
Monitor how your team uses the platform:
1. Filter by specific team member emails
2. Review which sessions and features they accessed
3. Ensure team members are following proper procedures
***
## Best practices
1. **Regular reviews** — periodically review audit logs to catch anomalies early.
2. **Narrow your search** — use specific filters to reduce noise and find relevant entries faster.
3. **Date ranges** — always specify a date range for better performance on large datasets.
4. **Bookmark searches** — save common filter combinations as browser bookmarks for quick access.
# Blocklist users
Source: https://docs.didit.me/console/blocklist-users
Auto-decline fraudulent verifications by blocklisting documents, faces, phone numbers, and emails. Stop identity fraud, multi-accounting, and repeat offenders.
The blocklist feature automatically declines verification sessions that match previously identified documents, faces, phone numbers, or emails that should be rejected. This helps prevent fraud and ensures the integrity of your verification process.
***
## Overview
The blocklist operates on four entity types:
Prevents reuse of specific documents identified as fraudulent, stolen, or otherwise problematic.
Prevents users whose biometric data matches previously blocklisted faces from passing verification.
Prevents verifications using phone numbers that have been blocklisted.
Prevents verifications using email addresses that have been blocklisted.
When a blocklisted entity is detected during verification, the session is automatically declined with the corresponding warning: `ID_DOCUMENT_IN_BLOCKLIST`, `FACE_IN_BLOCKLIST`, `PHONE_NUMBER_IN_BLOCKLIST`, `EMAIL_IN_BLOCKLIST`, `IP_ADDRESS_IN_BLOCKLIST`, or `DEVICE_FINGERPRINT_IN_BLOCKLIST`.
***
## How blocklisting works
### Document blocklisting
When a document is added to the blocklist, the system stores secure fingerprints of the document's unique identifiers (document number, MRZ data, etc.). During future verification sessions, if a document matches these fingerprints, the session is automatically declined.
Useful for:
* Preventing reuse of known fraudulent documents
* Blocking documents reported as stolen
* Preventing multiple accounts using the same document
### Face blocklisting
When a face is added to the blocklist, the system stores biometric templates derived from facial features. These templates are compared against faces in new verification sessions.
Useful for:
* Preventing users who have attempted fraud from creating new accounts
* Enforcing platform bans across new registration attempts
* Implementing regulatory exclusion requirements
### Phone number blocklisting
When a phone number is added to the blocklist, the system evaluates new verification sessions against the blocklisted numbers (including normalized E.164 formats).
Useful for:
* Blocking numbers associated with repeat abuse or policy violations
* Preventing re-registration attempts using the same phone number
* Enforcing regional compliance requirements
### Email blocklisting
When an email address is added to the blocklist, the system checks new verification sessions against blocklisted addresses (case-insensitive, normalized).
Useful for:
* Preventing repeat fraud or spam attempts from the same email address
* Enforcing bans across multiple accounts tied to the same email
* Meeting compliance requirements for account creation
***
## Transaction blocklists
In addition to verification blocklists, the following entity types are checked during **transaction monitoring**. If any match is found, the transaction is **immediately declined** before rules are evaluated:
| Blocklist | Entity checked |
| -------------------------------- | --------------------------------------------------------------------- |
| **Wallet Address Blocklist** | The `account_id` of crypto wallet payment methods |
| **Bank Account Blocklist** | The `account_id` of bank transfer payment methods |
| **IP Address Blocklist** | IP address from the transaction party's device (supports CIDR ranges) |
| **Device Fingerprint Blocklist** | Device fingerprint from the transaction party |
| **User Blocklist** | The applicant's `vendor_data` identifier |
| **Business Blocklist** | The business's `vendor_data` identifier |
Transaction blocklist checks are separate from verification blocklist checks. Both use the same underlying lists, so an entity blocklisted for verification is also blocklisted for transactions.
***
## Managing the blocklist
Items can be added to the blocklist through:
* **The Didit Console** — navigate to the blocklist section and add items manually.
* **The Lists API** — programmatically manage blocklist entries. See the [Lists API reference](/management-api/lists/overview).
For API-based blocklist management, see the [Create entry](/management-api/lists/create-entry), [Delete entry](/management-api/lists/delete-entry), and [List entries](/management-api/lists/list-entries) endpoints.
# Overview & Analytics
Source: https://docs.didit.me/console/case-management/analytics
Track workload and outcomes across your case queue: an officer and manager overview dashboard, plus per-officer, per-blueprint, and per-source analytics.
Two pills give you visibility into the case queue: **Overview** for day-to-day workload, and **Analytics** for trends and outcomes over a date range.
***
## Overview dashboard
The Overview pill adapts to the viewer:
* **Officer view**: cases needing resolution, cases awaiting user, and open/resolved counts by blueprint, plus FIU report tracking for reports the officer created.
* **Manager view**: SLA breach risk (cases with the least time remaining before their due date), unassigned high-priority cases, team availability from console presence, and top officers by throughput.
## Analytics pill
Pick a date range (defaults to the last 30 days, up to 731 days) to see:
* A **created vs. reviewed** chart, cases opened and cases resolved per day across the range.
* Four tables, **Officer**, **Blueprint**, **Creation source**, and **Total**, each with the same metrics:
| Metric | Description |
| --------------------- | -------------------------------------------------- |
| `created` | Cases created in range |
| `assigned` | Assignment events in range |
| `resolved` | Cases resolved in range |
| `threat_rate` | Share of resolved cases marked Valid threat |
| `false_positive_rate` | Share of resolved cases marked False positive |
| `unresolved` | Cases created in range still Open or Awaiting user |
| `reassignment_rate` | Share of created cases assigned more than once |
| `escalation_rate` | Share of created cases escalated at least once |
| `avg_assign_hours` | Average time from creation to first assignment |
| `avg_resolve_hours` | Average time from creation to resolution |
| `avg_handling_hours` | Average time from assignment to resolution |
A live **queue** count (open cases, and overdue cases) sits alongside the range-scoped metrics so you always see today's backlog regardless of the selected date range.
Export any table as CSV from the same controls used elsewhere in the console.
Cross-check one officer's row against their case list (filter by Assignee) the first time you rely on these numbers for a review, it is the fastest way to confirm the date range and filters mean what you expect.
# Blueprints
Source: https://docs.didit.me/console/case-management/blueprints
Configure how a category of cases is assigned, escalated, transferred, and displayed, and install the four built-in presets.
A blueprint defines how one category of case is handled: who it is assigned to, how it routes, and what shows up on its case page. Every case belongs to exactly one blueprint.
***
## General settings
| Setting | Description |
| --------------------------- | -------------------------------------------------------------------------------------- |
| **Name** | Up to 24 characters |
| **Description** | Optional, free text |
| **Who handles cases** | Assignee pool, required, at least one member ("Pick at least one assignee") |
| **Assignment** | **Manually**, or **Automatically** |
| **Case transfer** | Toggle; when on, cases can move to another active blueprint of the same application |
| **Escalation** | Toggle plus a target blueprint select |
| **4-eyes review** | Pair with a checker blueprint, see [4-eyes review](/console/case-management/four-eyes) |
| **Require resolution note** | Toggle; when on, the Resolve dialog's note becomes mandatory |
### Automatic assignment
Automatic mode reveals "Assign to users with fewer than **N** cases of the current blueprint assigned to them". Only officers currently online in the console are considered. When the whole assignee pool is at or over the threshold, new cases queue unassigned and are pulled automatically as soon as an officer resolves a case or comes online.
### Escalation and transfer
Escalation moves a case to a fixed target blueprint (its queue, its content configuration) in one action. Case transfer instead lets an officer pick any active blueprint of the same application to move a case to. Both are hidden from the case page unless the current blueprint enables them, and both are blocked server-side (400) if attempted while disabled.
A blueprint cannot reference itself as its own escalation target or 4-eyes checker.
### Connected sources
A blueprint's right rail lists:
* **Case creation sources**: AML configs, transaction rules, and workflow nodes currently routing new cases into it, with shortcuts to each one.
* **Receives escalation from**: other blueprints whose escalation target points here.
## Presets
Install any of the four built-in presets from the **Blueprints** pill's presets gallery. Installing copies the preset into your application as a fully editable blueprint, you can then change any setting freely without affecting the catalogue. Installing is idempotent per preset, so re-installing an already-installed preset is a no-op.
| Preset | Case content |
| ----------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Screening hit** | AML control (financial + identity), identity (personal information, risk overview) |
| **AML investigation** | AML control (financial), identity (personal information, contact information, risk overview, PoI documents) |
| **Suspicious activity** | Identity (personal information, risk overview, PoI documents), financial (payment methods, transactions) |
| **KYC review** | Identity (all fields), verification summary |
None of the presets pre-configure assignment, escalation, 4-eyes, or transfer, those routing decisions are always yours to make per blueprint.
## Case content
The **Case content** tab controls which tabs and fields appear on the case page, as six togglable sections:
| Section | Fields |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| **Checklist** | Enable, make optional for resolution, and the checklist item list, see [Checklist](/console/case-management/checklist) |
| **AML control** | Financial AML information, identity AML information |
| **Identity** | Personal information, applicant tags, contact information, risk overview, proof of identity documents, proof of address documents |
| **Verifications** | Verification summary |
| **Reporting** | FIU reports |
| **Financial data and sources** | Payment methods, financial transactions |
A blueprint with no sections enabled still shows the case Overview tab, just without the extra content blocks.
There is deliberately no priority, severity, case type, or default-due-hours setting on the blueprint. Priority and due date are set per case (by whatever created it, or edited from the case page), keeping the blueprint focused on routing and content.
## Deleting a blueprint
Deleting a blueprint always succeeds and does not touch its cases: existing cases keep their blueprint reference, and its settings (content sections, escalation, transfer, 4-eyes pairing) continue to apply to them. A deleted blueprint no longer appears in blueprint lists and can no longer be chosen as a transfer target. If a maker blueprint is deleted while one of its resolutions is pending 4-eyes approval, a rejected resolution leaves the case on the checker blueprint instead of returning it.
To wind a blueprint down cleanly, transfer its active cases to another blueprint before deleting it.
# Cases
Source: https://docs.didit.me/console/case-management/cases
Open, work, and resolve investigation cases anchored to a single user or business, with saved views, SLA due dates, and bulk actions.
Case Management is the investigation hub of the Didit Console. Every AML hit, flagged verification, or suspicious transaction can become a **case** your compliance team works from open to resolution, with a full audit trail.
***
## Lifecycle
A case moves through exactly three statuses:
| Status | Meaning |
| --------------- | ----------------------------------------------------------------- |
| `Open` | Under active investigation |
| `Awaiting user` | Paused while more information is collected from the verified user |
| `Resolved` | Investigation complete, with a resolution recorded |
Resolving a case requires a resolution:
| Resolution | Meaning |
| ---------------- | ------------------------------------------- |
| `False positive` | The activity was legitimate |
| `Valid threat` | Confirmed suspicious or fraudulent activity |
Every case also carries a **priority** (`Low`, `Medium`, `High`, default `Medium`) and an optional **due date** that drives SLA sorting and overdue flags.
There is no `Under review`, `On hold`, `Pending approval`, `Critical` severity, or case type axis. The lifecycle is deliberately fixed to keep analytics, SLAs, and permissions unambiguous across every case, regardless of how it was created.
## One subject per case
Every case is anchored to **exactly one** user or one business. You cannot mix subjects on a single case, and you cannot attach a session, business session, or transaction that belongs to a different subject than the case, doing so returns a clear error.
This keeps every case page focused: the subject card, risk overview, documents, and financial data all describe the same person or business the case investigates. To review a second entity, open (or create) a separate case.
## Lists
Case Management has status tabs: **Open**, **Awaiting user**, **Resolved**, and **All**.
Search matches subject name or ID, tag, case number, case title, linked session ID, and linked transaction ID, so you can paste in a session or transaction UUID and land directly on its case.
Filter chips: **Blueprint**, **Created**, **Source**, **Assignee**, **Tag**, plus **Add filter** for **Priority** and **Due**.
Columns: Created (date and creator), Case details (title), Status, Blueprint, Subject (name and ID), Subject tags, Assignee, Priority, and Due date, with more available behind horizontal scroll and column configuration.
### Sort and SLA
Sort by **Created at** (default) or **Due date**. Sorting by due date surfaces overdue cases first; a case is overdue once its due date has passed while it is still Open or Awaiting user, shown with a red due chip.
### Saved views
Save the current combination of filters and columns as a named view with **Save view**, switch between views with **Select a view**, and reset with **Clear filters**. Views are personal and persist the same way other console table preferences do.
### Bulk actions
Select multiple cases to:
* **Assign to** an officer. All selected cases must share the same blueprint, the console warns before submitting if they do not.
* **Set status** to Awaiting user, Reopen, or Resolve (with a resolution and optional note). Each case is evaluated independently, so a case blocked by an incomplete checklist or a missing required note is reported individually without failing the rest of the batch.
### Exports
The list menu offers two CSV exports, delivered through the [Reports page](/console/export-pdf-csv):
* **All cases report**, one row per case with status, resolution, priority, tags, linked transactions, and the last FIU report filed.
* **SLA report**, per-case timing metrics: hours to assignment, hours to resolution, overdue, and SLA-met flags.
## Tags
Add tags to a case from its header (**Add tag**) to group related investigations, or filter and search by tag from the list. Tag changes are recorded on the case timeline.
## Related pages
Every entry point: users, businesses, sessions, transactions, and automatic connectors.
Configure assignment, escalation, transfer, and the case page content.
Gate resolution on required investigation steps.
Maker/checker approval for sensitive resolutions.
# Checklist
Source: https://docs.didit.me/console/case-management/checklist
Gate case resolution on a required list of investigation steps, configured per blueprint and tracked per case.
The checklist is a short, auto-numbered list of steps an officer must work through before a case can be resolved.
***
## Configuring a checklist
On a blueprint's **Case content** tab, enable **Checklist** and add items with **+ Add field**. Items are auto-numbered and can be reordered by drag and drop. Didit recommends 5-7 items, enough to standardize an investigation without turning it into busywork.
Toggle **Make checklist optional for case resolution** to let officers resolve a case with incomplete items. Leave it off to make every item mandatory.
Every new case created on the blueprint copies the current checklist items at creation time, later edits to the blueprint's checklist do not retroactively change checklists already running on existing cases.
## Working the checklist
The case page's persistent right rail shows the checklist as "**n / m completed**". Officers check items off as they complete each step; every completion is logged on the case timeline.
## Resolution gating
Attempting to resolve a case with incomplete, non-optional checklist items is blocked with a 400 listing the incomplete item labels, and the Resolve dialog surfaces them inline so the officer knows exactly what is left. Once the blueprint marks the checklist optional, resolution proceeds regardless of checklist state.
The optional flag lives on the blueprint, not per item. Earlier per-item mandatory flags have been replaced by this single blueprint-level toggle, keeping the gating rule unambiguous.
# Creating Cases
Source: https://docs.didit.me/console/case-management/creating-cases
Open a case from a user, business, verification session, or transaction, manually or automatically through AML screening, transaction rules, and the workflow builder.
A case can be opened from any of the entities you already work with, or created automatically as your rules and workflows run.
***
## Manual creation
The same **Create case** dialog appears from every entry point:
* **Case title**, prefilled as `Manual - `
* **Case blueprint** (required), with a **Manage blueprints** link if you need to set one up first
* **Assign to** (optional), with the current user shown first as ` (Assign to me)`
Entry points:
* A user's detail page (overflow menu, and the user's **Cases** tab with **+ Create case**)
* A business's detail page (overflow menu, and the business's **Cases** tab)
* A KYC session's detail page (overflow menu)
* A KYB session's detail page (overflow menu)
Creating from any of these anchors the new case to that user or business automatically, satisfying the [one-subject-per-case](/console/case-management/cases#one-subject-per-case) rule without any extra steps.
## From a transaction
From a transaction's row menu, **Add to case** either:
* Attaches the transaction to one of the subject's existing open cases, or
* Creates a new case anchored to the transaction's subject and attaches the transaction to it.
Attaching a transaction that belongs to a different subject than the case is rejected, the console shows "belongs to a different profile" and the API returns a 400.
## Automatic creation
Three connectors open cases without an officer in the loop, each recorded with a distinct **source**:
| Source | Trigger | Configured in |
| ------------------ | --------------------------------------------------------------------------- | --------------------------------------- |
| `AML screening` | An ongoing-monitoring AML hit, when the toggle is enabled on the AML config | AML settings, per verification workflow |
| `Transaction rule` | A transaction rule match with the **Create case** effect | The rule's effect configuration |
| `Workflow builder` | A session completing a **Create case** node | The workflow graph |
### AML screening
Enable **Create cases from ongoing monitoring hits** on a workflow's AML configuration and pick a blueprint. A second hit against a subject that already has an open case attaches to that case instead of opening a duplicate.
### Transaction rules
Add the **Create case** effect to a [transaction rule](/console/transaction-monitoring) with:
* **Blueprint** to route the case to
* **Grouping**: **By applicant** (reuses the subject's open case across every rule) or **By rule and applicant** (opens one case per rule per applicant)
* **Attach matched transaction**, to link the triggering transaction automatically
### Workflow builder
Add a **Create case** action node to a workflow graph, configured with a blueprint, priority, and due date. When a session reaches that node, a case opens with source `Workflow builder` and the session's user as its subject.
### Where connectors are visible
A blueprint's detail view lists every connector currently routing cases into it (**Connected sources**) and any blueprints that escalate into it (**Receives escalation from**), with shortcuts to the AML config, rule editor, or workflow builder that owns each connection.
## Manual vs. automatic sources
Manually created cases always carry source `Manual`. This distinction feeds the [analytics](/console/case-management/analytics) Creation source table, so you can see at a glance how much of your caseload originates from each channel.
# FIU Reports & Templates
Source: https://docs.didit.me/console/case-management/fiu-reports
Generate SAR, STR, CTR, and other FIU regulatory filings from a case or from the standalone Reports page, using reusable per-jurisdiction templates.
When an investigation concludes that activity must be reported to your Financial Intelligence Unit, generate the filing directly from case data, or from the standalone Reports page.
***
## Report types
| Code | Report |
| ----- | ----------------------------------- |
| `SAR` | Suspicious Activity Report |
| `STR` | Suspicious Transaction Report |
| `CTR` | Cash Transaction Report |
| `TTR` | Threshold Transaction Report |
| `UTR` | Unusual Transaction Report |
| `IFT` | International Funds Transfer Report |
| `CBR` | Cross-Border Report |
| `TFR` | Terrorism Financing Report |
Every report renders in two formats: **goAML XML**, the UN-standard schema your FIU portal accepts, and a **PDF** for internal records and 4-eyes review.
## Report templates
A report template captures the parts of a goAML header that stay constant across your filings for a given jurisdiction and report type, so you are not re-entering them on every report:
* **Reporting entity ID** (`rentity_id`) and branch, the identifier your organization received from the FIU
* **Submission code** (electronic or manual)
* **Local currency code**
* Which optional blocks to include (reporting person, location, reason, action) via `enabled_fields`
Build templates from the Reports pill's **Templates** tab, one per jurisdiction and report type. A template is only usable once it passes validation, missing obligatory goAML fields are listed and block both saving an invalid template and creating a report from it.
## Creating a report
### From a case
1. Open the case and go to its **FIU reports** tab, shown when the blueprint's Reporting section is enabled.
2. Click **Create report**, pick a template (or leave it blank and fill fields manually), and optionally choose which of the case's linked transactions to include.
3. Didit auto-populates the draft: reporting entity details from the template, the case's subject, linked transactions, and a structured narrative.
4. Review the draft, then **Validate** to check obligatory goAML fields before finalizing.
5. **Finalize**. The goAML XML and PDF are generated, uploaded, and appear on the case's report list with download links for both files.
### From the standalone Reports page
The **Reports** pill also has a **Create report** button that works app-wide: pick a **case** and a **template**, both required, then follow the same validate and finalize flow. Use this when you are working from a queue of pending filings rather than from inside a specific case.
## Report lifecycle
| Status | Meaning |
| ----------- | ------------------------------------------------------------------------------- |
| `Draft` | Editable, not yet validated |
| `Validated` | Passed obligatory-field validation |
| `Finalized` | XML and PDF generated and stored; the report can no longer be edited or deleted |
### SAR narrative
SAR, STR, and TFR reports include a narrative structured around the five Ws: **who** is conducting the activity, **what** instruments are involved, **when** and **where** it took place, **why** it is suspicious, and **how** it occurred. Didit drafts each section from case data; edit any section before finalizing.
### Alert lifecycle
Finalizing a SAR, STR, or TFR automatically moves the case's transaction alerts from **Pending SAR** to **SAR filed**, keeping your alert queue in sync with your filings.
## Print case (separate from FIU reports)
**Print case**, in the case page's overflow menu, produces a full-case PDF independent of any FIU filing: case summary, subject, checklist state, notes, attached sessions and transactions, the event timeline, and the list of FIU reports on the case. Use it for internal audits or handoffs that need the whole investigation in one document, not a regulator-facing filing.
## Case exports
From the Cases list, queue two CSV reports (delivered through the [Reports page](/console/export-pdf-csv)):
* **All cases report**, status, resolution, priority, linked transactions, and the last FIU report filed per case.
* **SLA report**, per-case timing metrics.
## Related pages
Lifecycle, lists, and bulk actions.
Overview dashboard and case analytics.
# 4-Eyes Review
Source: https://docs.didit.me/console/case-management/four-eyes
Require a second officer to approve or reject every resolution on a blueprint, with the proposed outcome staged until a different reviewer signs off.
4-eyes review adds a second, independent sign-off before a resolution takes effect, a standard segregation-of-duties control for higher-risk investigations.
***
## Setting it up
On the maker blueprint, set **4-eyes review** to a **checker blueprint**. Setting this field is what turns 4-eyes on for the maker blueprint, there is no separate toggle.
## How it works
1. An officer on the maker blueprint resolves a case as usual (false positive or valid threat, plus a note if required).
2. Instead of closing, the case status stays **Open**, the proposed resolution is staged, and the case moves into the **checker blueprint's** queue. The case timeline logs `Submitted for approval`.
3. A checker reviews the case's Overview tab, where a dual-control banner shows **Approve** and **Reject** actions.
4. **Approve** applies the staged resolution: the case becomes **Resolved** with the original resolution and note, credited to the officer who submitted it. The timeline logs `Approval granted` then `Resolved`.
5. **Reject** clears the staged resolution and returns the case to the **maker blueprint**. The timeline logs `Approval rejected`, and the maker is notified.
The officer who submitted the resolution cannot approve or reject it themselves, enforced server-side. Attempting to do so returns a 403. (Approving or rejecting a case with no resolution pending approval returns a 409.)
## Notes
* The checker blueprint's own assignment, escalation, and content settings apply while a case is in its queue, it is a real blueprint, not a special mode.
* A case can only have one resolution staged at a time; requesting more information (**Awaiting user**) is blocked while a resolution is pending approval.
* 4-eyes pairs with [required resolution notes](/console/case-management/blueprints#general-settings): turn both on for your highest-risk blueprints to require both a documented rationale and a second reviewer.
# Custom domain
Source: https://docs.didit.me/console/custom-domain
Serve the verification flow from your own subdomain instead of verify.didit.me. Add the domain, create two DNS records, and verify ownership.
Serve the verification flow from your own subdomain — `verify.yourbrand.com` instead of `verify.didit.me`. Users stay on your domain for the whole journey, which removes the last visible reference to Didit in a [white-label](/console/white-label) flow.
Custom domain is part of White Label and must be enabled on your account. You also need **write access to Customization** — members with read-only access see the section disabled.
***
## Requirements
| Requirement | Detail |
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
| **Subdomain only** | Use `verify.yourbrand.com`. Root domains such as `yourbrand.com` are rejected, and so is a `www.` prefix. |
| **Not already in use** | The subdomain cannot point to an existing website or application of yours. |
| **DNS access** | You need to create two records at your DNS provider. |
***
## How it works
You enter your subdomain in the Business Console. Didit then generates two DNS records for you to add at your provider:
| Record | Purpose |
| ---------------------- | ------------------------------------------------------------ |
| **Verification CNAME** | Proves you own the domain, which issues the SSL certificate. |
| **CloudFront CNAME** | Points your subdomain at the verification UI. |
Both records are required. Once they resolve, you verify ownership from the console and the flow starts serving from your domain.
***
## Set up your domain
Go to **Business Console → White Label → Domain**, enter your subdomain (for example `verify.yourbrand.com`), and click **Add Domain**.
The console generates your DNS records. This takes a few seconds.
The console shows a card for each record — **Verification CNAME** and **CloudFront CNAME**. Each card gives you two values:
| Card field | Where it goes at your DNS provider |
| ------------ | ---------------------------------- |
| **Hostname** | The record **name** (host) |
| **Data/URL** | The record **value** (target) |
Don't swap **Hostname** and **Data/URL** when you paste them into your DNS provider, and use the copy button for each value. The verification values are long and easy to mistype.
Create both records as `CNAME`. Adding only the CloudFront record is the most common reason verification fails later.
Set the TTL to `300` so any correction you make afterwards propagates in minutes instead of hours.
Back in the console, click **Verify domain**. On success the panel changes to **Domain verified** and the button disappears.
If it fails, the console shows a **Verification failed** box with the specific reason. Read it before you change anything — it names the record that is missing or incorrect.
DNS usually propagates within minutes, but it can take up to **24–48 hours**. If the console reports **DNS changes detected**, your records are correct and propagation is still finishing — no action needed.
***
## Change or remove your domain
To move the flow to a different subdomain, enter the new one and click **Update Domain**. Didit issues a new pair of records, so you have to add those at your DNS provider and verify again.
To go back to `verify.didit.me`, clear the field and click **Remove Domain**.
While a custom domain is configured, you cannot re-enable the Didit login screen. Remove the domain first.
***
## Troubleshooting
Your subdomain has an `A` record conflicting with the CNAME — a host cannot have both. Delete the `A` record and keep only the CNAME.
If there is no `A` record, the CloudFront CNAME points at the wrong target. Match it character for character against the **Data/URL** value in the console.
Check that **both** records exist at your provider. Adding only the CloudFront record is the most common cause, because the flow appears to load while the certificate never issues.
The **Verification failed** box in the console names the record that is missing or wrong.
Set both records to **DNS only** — the grey cloud, proxy off. The orange-cloud proxy intercepts the request and breaks certificate issuance.
Providers without a proxy toggle, such as cPanel, are not affected.
Some providers append your zone automatically, turning `verify.yourbrand.com` into `verify.yourbrand.com.yourbrand.com`.
On those providers, enter the **relative** name rather than the full one the console shows — `verify` instead of `verify.yourbrand.com`, and `_abc123.verify` instead of `_abc123.verify.yourbrand.com`. Then re-open the saved record and check that the full name it stored matches the console **Hostname** exactly.
Your records are cached for the length of their TTL. Lower the TTL to `300` and wait for the previous value to expire before testing again.
***
## Next steps
* **Match the rest of the UI to your brand** — colors, logo, and typography live in the [White Label](/console/white-label) style editor.
* **Enable your style per workflow** — branding only applies to workflows with **Include custom style** turned on. See [White Label](/console/white-label#activate-custom-style-in-your-workflow).
* **Review your disclosure obligations** — a custom domain hides Didit's branding but not its role. See [compliance responsibilities](/console/white-label#compliance-responsibilities-in-white-label-flows).
# Data retention
Source: https://docs.didit.me/console/data-retention
Configure how long Didit stores verification data, delete sessions on demand, and implement privacy-first KYC with EU residency, GDPR, and DPA support.
## Role and processing location
| Aspect | Detail |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Processor role** | Didit acts as a **data processor**; you remain the data controller. |
| **Processing region** | EU by default. Enterprise accounts can enable **in-country processing** (local data residency) subject to availability and contract. |
| **Regulatory posture** | Designed to support **GDPR** and local data-protection regimes. Configure retention below to meet your obligations. |
Need a DPA, TOMs, or other compliance attestations? Contact your Didit representative.
***
## Retention controls
Configure retention in **Business Console → App Settings → Data**.
Go to **Business Console → App Settings → Data**.
Select a window from **1 month** to **10 years**, or leave as **unlimited** (default).
Click **Save** to apply the policy to all future and existing sessions.
The policy applies to verification inputs/outputs, derived results, and operational metadata stored by Didit. When a session reaches the end of the window it is deleted exactly as by [Delete Session](/sessions-api/delete-session), including its face embedding unless you have enabled [biometric-template retention](#biometric-template-retention).
***
## Manual deletion
Delete individual sessions from the Console when you need one-off removals.
1. Navigate to **Dashboard → Verifications**.
2. Search or filter for the target session.
3. Click the **Delete** button (top-right) and confirm.
***
## Programmatic deletion
Delete a session via the API at any time by calling the [Delete Session](/sessions-api/delete-session) endpoint.
```bash theme={null}
curl --request DELETE \
--url https://verification.didit.me/v3/session/{session_id}/delete/ \
--header 'x-api-key: YOUR_API_KEY'
```
| Response | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `200 OK` | Session deleted. The body reports what happened to the face biometric data (`face_retention_outcome`). |
| `404 Not Found` | Session already deleted or unknown `session_id`. |
For a data-subject erasure request, send `{"deletion_instruction": "privacy_erasure"}` in the body so that any retained biometric template for that person is purged as well.
***
## Biometric template retention
Every KYC session with a liveness selfie carries a face embedding that powers duplicate detection and [Face Search 1:N](/core-technology/face-search/overview). By default that embedding is deleted with the session, so a person whose session you deleted can verify again without being flagged as a duplicate.
If you delete sessions soon after approval but still need to catch repeat sign-ups, enable **biometric-template retention**. Didit then deletes the session and all of its data as usual and keeps one separately managed, image-free face biometric template anchored to the User.
A retained biometric template is biometric data. It is not anonymous, it is not transient, and it is not deleted with the session. It stays until its scheduled expiry, an earlier applicable-law deadline, User deletion, a privacy-erasure request, or an explicit purge. Enable retention only when your controller instruction and privacy notice cover it, and choose a retention period that satisfies the laws that apply to your users.
### Enable it
Go to **Business Console → App Settings → Data**.
Select **Retain until scheduled expiry or earlier deletion**. The setting reads: *Keeps an image-free face biometric template for tenant-scoped duplicate detection after an operational session deletion. It remains biometric data and is deleted on its configured schedule, applicable-law deadline, User deletion, privacy-erasure request, or explicit purge.*
Enter the number of days (1 to 3650) a template may be kept. The setting cannot be saved without it. Didit applies the shorter of this period, your general retention window, and any deadline you send on a deletion call.
The policy applies to deletions from now on. No existing template is created or purged by changing the setting.
Programmatically, set the same policy with `PATCH /v3/webhook/`:
```bash theme={null}
curl -X PATCH https://verification.didit.me/v3/webhook/ \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"face_retention_policy": "retain_until_user_deleted", "face_retention_days": 365}'
```
| Field | Values | Description |
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `face_retention_policy` | `delete_with_session` (default), `retain_until_user_deleted` | What happens to the face embedding when a session is deleted. |
| `face_retention_days` | integer 1-3650 | Finite retention period for retained templates. Required when the policy is `retain_until_user_deleted`; the request is rejected with `400` without it. |
Existing applications stay on `delete_with_session` until you change the policy. Nothing enables retention on your behalf.
### What is retained and what is erased
| Deleted with the session | Retained (opt-in only) |
| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Decision, extracted data, and every feature record | The numeric face template used for matching |
| Document images, portraits, selfies, liveness video, POA documents, extra uploads | A new random template id and an opaque provenance reference |
| The session id, number, and its link to any media | The owning User, the retention dates, the instruction class and id, and the acting principal id |
The retained template is tenant-scoped and used only on your instruction for duplicate-face and multi-account detection, biometric authentication, and your own face lists. It is never used for model training, analytics, cross-organization matching, or Didit's own fraud processing.
### Per-deletion control
Every deletion reports its outcome, and every delete call can override the policy:
* **Console**: the delete confirmation shows whether each session's template will be retained or deleted. The confirmation reads: *This deletes the session and starts deletion of its session data. If biometric-template retention is enabled for this operational deletion, an image-free biometric template remains separately until its scheduled expiry or earlier purge. Privacy-erasure requests also purge the template.* Sessions without a User cannot retain a template and are marked as such.
* **API**: send `retain_face_embeddings`, `face_retention_days`, `face_retention_deadline`, `deletion_instruction`, and `instruction_id` on [Delete Session](/sessions-api/delete-session) or [Batch Delete Sessions](/management-api/sessions/batch-delete).
### Two kinds of deletion instruction
| Instruction | What it does |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Operational session deletion (`operational_session_delete`, default) | Deletes the session and its data. A template is retained only when the policy or the request says so, and the response states the outcome. |
| Privacy erasure (`privacy_erasure`) | Deletes the session and purges every retained template for that person. The application policy cannot override it. Use it for data-subject requests. |
### When a template is purged
* You purge it in **Lists → Biometric templates** or through the [Biometric Templates API](/management-api/biometric-templates/overview).
* You delete the User with [Batch Delete Users](/management-api/users/delete).
* You delete any session of that User with `deletion_instruction: "privacy_erasure"`.
* Its retention period ends. Didit purges it automatically.
Switching the policy back to **Delete with session** stops new templates from being created but does not purge existing ones; purge them explicitly.
### Where to see them
* **Lists → Biometric templates** lists every template with its User, source, retained and expiry dates, and status, with single and bulk purge.
* **Users → \[user] → Biometric templates** shows the templates anchored to one User.
* The [Biometric Templates API](/management-api/biometric-templates/overview) exposes the same data and actions, and every retain and purge is recorded on the audit trail.
Retained templates are covered by the biometric-data terms of your DPA. Nothing in this page is legal advice: you remain responsible for the legal basis, notices, consent, and the retention period that applies to your users.
***
## Process-and-purge pattern
For maximum data minimization, process verification data through Didit and purge it immediately after receiving results via webhooks.
Your backend calls the [Create Session](/sessions-api/create-session) API.
Identity, liveness, AML, and any other configured checks execute automatically.
Didit sends a [webhook](/integration/webhooks) with `status`, `session_id`, `vendor_data`, and full verification data.
Store the minimum fields required for your records (e.g., `status`, `vendor_data`).
Call the [Delete Session](/sessions-api/delete-session) API for that `session_id` to delete the session and its data from Didit. If you rely on duplicate detection across future sign-ups, enable [biometric-template retention](#biometric-template-retention) first; otherwise the face embedding is deleted too.
***
## Security and assurance
ISMS in place. Certificate and excerpts available on request.
Periodic third-party penetration tests with tracked remediation.
No security breaches reported to date.
Dedicated cybersecurity team with least-privilege access and strict environment separation.
All API activity is recorded in **Audit Logs** for security, compliance, and troubleshooting. Logs are retained for **365 days** and then auto-deleted.
***
## Privacy-minimized storage
We are adding features that let you **retain only selected data fields** — for example, keep `status` and `vendor_data` while auto-purging heavier artifacts like images and documents. This gives stricter control for teams operating under data-minimization principles.
Want early access to artifact-level retention rules? Contact your Didit representative.
***
## FAQ
Yes. You can configure different retention policies for each application you have in Didit.
Yes. Export session data via the Console or API, then call the Delete Session endpoint.
Only if you enabled biometric-template retention before deleting their session. By default the face embedding is deleted with the session and the person can verify again without a duplicate flag. See [Biometric template retention](#biometric-template-retention).
Use **Audit Logs** in the Console. Filter by user, endpoint, or date range. Logs are retained for **365 days**.
***
## Implementation checklist
Set your retention policy in **Console → App Settings → Data**.
Set up [webhooks](/integration/webhooks) and verify signatures.
Store only the fields your business requires.
Call the [Delete Session](/sessions-api/delete-session) API if you use the process-and-purge pattern.
Verify your processing region (EU by default, or in-country for enterprise).
Use separate Sandbox and Live API keys with independent retention policies. Rotate keys regularly.
# Export to PDF & CSV
Source: https://docs.didit.me/console/export-pdf-csv
Export KYC verification results to PDF reports or CSV files for compliance audits, regulatory reporting, and data analysis. Available in the console or via API.
Didit lets you export verification data in two formats — **PDF** for individual session reports and **CSV** for bulk data exports. Both options are available from the Didit Console and through the API.
***
## Export PDF
Generate a PDF report containing the full verification results of an individual session. You can do this in two ways:
* **From the Console** — open any session and click the **Download PDF** button.
* **Via the API** — call the [Generate PDF](/sessions-api/generate-pdf) endpoint.
The PDF includes all verification steps, extracted data, biometric scores, AML results, and the final decision — formatted for compliance audits and regulatory filings.
### Full user history PDF
To get every session for one user in a single file, open that user's detail page and use **Actions → Download PDF**. The report bundles a cover page (profile summary, feature status, session index) with the full standard report of every completed session sharing that user's `vendor_data`, merged in chronological order.
Only sessions in a reportable status (`Approved`, `Declined`, `In Review`, `Kyc Expired`) are included, capped at the 20 most recent. The cover page states when older sessions were left out.
***
## Export CSV
The CSV export allows you to download verification session data in bulk for further analysis or record-keeping. Customize the exported data by selecting specific columns and filtering sessions.
Use CSV exports for periodic compliance reports, internal analytics, or importing verification data into your own systems.
# ID Verification Methods in the Console
Source: https://docs.didit.me/console/id-verification-methods
Choose which of the three ID Verification methods each country may use, set the fallback switches, and read the method a session actually used.
The [ID Verification](/core-technology/id-verification/verification-methods) step in a workflow has a **Countries** tab. That is where you decide, country by country, which of the three methods a person may use and what happens when a non-document method does not land. Everything on the tab writes the [`methods`](/management-api/workflows/feature-configs#ocr--id-verification) key on the ID Verification node — the console and the Management API are two views of the same object.
## Countries and allowed ID methods
Open a workflow, select the **ID Verification** node, then **Countries**. Each country row shows the methods it accepts. Pick a country to open its editor.
| Section | What it controls | Config it writes |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Document capture** | Whether a photographed document is accepted, plus the document types, subtypes and capture rules for that country | `document.enabled`, `documents_allowed` |
| **Non-doc lookup** | Whether the non-document lookup is offered, how many answered attempts the user gets, and whether the workflow's later Liveness and Face Match steps are skipped after a portrait-backed match | `id_lookup.enabled`, `id_lookup.max_attempts`, `id_lookup.skip_liveness_and_face_match` |
| **Wallets accepted** | Which digital ID wallets that country offers. An accept-list, not a ranking — the end user picks from what you tick | `wallet.enabled`, `wallet.providers` |
| **Fall back to document capture when…** | One switch per failure mode | `id_lookup.on_partial_match`, `id_lookup.on_no_match`, `id_lookup.on_provider_error`, `wallet.on_failure` |
A country where you turn every method off is rejected on save with **No method enabled — nobody can verify here**, because that configuration cannot verify anybody.
### Availability is server-driven
The tab reads the same capability catalog the API validates against, so it can only ever offer you what the platform actually supports:
* A method the catalog does not offer in that country is not shown as a choice.
* A source or wallet the catalog marks **Coming soon** is listed so you can see the roadmap, but it cannot be switched on.
* Wallet sign-in is switched on per environment. While it is off, every wallet reads **Coming soon** whatever the catalog says.
### Expected request and response
**See details** on a lookup source or a wallet opens the expected request and response: what the end user is asked for, in plain language, and what the source returns. Each lookup source is named exactly — a government register in most countries, a credit-bureau or residential-records file in some — because that is what determines provenance and the consent regime. The end user never sees the response fields, the source name or the price.
### Bulk switches
**Global settings** turns a method on in every country that supports it, so you do not have to walk 36 countries by hand. The per-country editor still wins: switch a method on globally, then turn it off for the one country where you do not want it.
## Prices on the tab
Each method shows what it costs. Document capture is \$0.15 with the first 500 checks each month free. Non-document methods are priced per country and per wallet, and any price the pricing catalog has not published yet is labelled a placeholder on the tab rather than presented as a quote.
The tab also spells out the billing consequence of each fallback switch, so the cost of a configuration is visible before you save it:
* A registry that answered bills the lookup — a match and a no match both count — and the document capture then bills on top when the user falls back.
* A lookup that never returned is not billed, so an outage costs you the document price only.
* Turn a fallback off and those users are declined; nothing further is billed.
* An abandoned or failed wallet sign-in is not billed.
## Reading a session
The **ID Verification** block on a session detail carries a chip for the method that produced it and a second chip for its [assurance tier](/core-technology/id-verification/verification-methods#assurance-tiers): **Document / Documentary**, **Non-doc lookup / Data match**, or the wallet's name with **Cryptographic**.
A session that fell back from a non-document method to document capture reads as a **Document / Documentary** session. The API does not yet record the method that was tried first on a successful fallback — [`fallback_from`](/reference/data-models#id-verification) is populated only when the country's switch was `decline` and the session was declined — so the console has nothing to draw a "tried this, then that" trail from. To see which non-document method was attempted, use the checks list on the session.
Below the chips, each method shows its own evidence:
* **Document** — unchanged: the front, back and selfie images, quality scores, extracted fields and warnings.
* **Non-doc lookup** — a **Registry comparison** listing every compared field with what the user provided, what the source held, and a **Match**, **Partial** or **No match** result, plus the source name, the time it was checked and the attempt count. A source that did not answer appears as its own row with an error rather than a field verdict. Where the source returned a portrait, that portrait and the user's selfie are shown side by side with the face score.
* **Wallet** — the **Credential** card: the wallet, its issuing authority, the credential type, the level of assurance it asserted, when it was verified, whether the signature checked out, and the attributes the wallet shared.
Where a source or a wallet shares no portrait, the panel says so instead of leaving an empty frame: there is nothing to face-match, and Liveness and Face Match run as their own steps if the workflow has them.
## Related
* [ID Verification methods](/core-technology/id-verification/verification-methods) — the three methods, fallback and billing
* [Non-document lookup](/core-technology/id-verification/non-doc-lookup) — per-country fields and consent
* [Digital ID wallets](/core-technology/id-verification/digital-id-wallets) — per-wallet attributes and assurance
* [Workflows](/console/workflows) — building and publishing a workflow
* [Workflow feature configs](/management-api/workflows/feature-configs#ocr--id-verification) — the same settings through the API
# Manual review
Source: https://docs.didit.me/console/manual-review
Review flagged identity verification sessions with the Didit console dashboard. Approve, decline, or request resubmission with full audit trails and evidence.
Manual review is a critical part of any robust identity verification and KYC compliance workflow. When the automated system flags a verification session with warnings or inconsistencies, it moves to **In Review** status — requiring a trained reviewer to make the final decision.
This guide walks you through the complete manual review process, including how to use the **resubmission** feature to give users a second chance.
***
## Verification dashboard overview
The verification table view lists all sessions with their current status, document type, country, and other key details at a glance. Sessions can have the following statuses:
| Status | Meaning |
| --------------- | ------------------------------------------------------------ |
| **Approved** | The user passed all identity checks |
| **Declined** | The user failed one or more verification checks |
| **In Review** | Requires your manual attention before a decision can be made |
| **Resubmitted** | The user has been asked to redo specific verification steps |
Sessions marked as **In Review** have triggered one or more warning signals during automated processing. The total count of sessions pending review is displayed at the top of the verification dashboard.
***
## How to conduct an effective manual review
Click on any session to open the detailed session view:
1. **Review all warnings** displayed in the session overview — these are the specific signals that triggered the manual review (e.g., low liveness score, AML match, document inconsistency).
2. **Review the user's previous verification attempts** — click on the user's vendor data to access the session history and see if they have prior verification sessions.
3. **Review the session events timeline** — the events section provides a chronological log of every action taken during the session.
Didit's automated system already performs comprehensive document verification — including security feature detection, field-level data consistency checks, MRZ validation, expiry date verification, and image quality analysis. Your role is to **evaluate the flagged warnings** and visually confirm the document when needed.
1. **Review document warnings** — check the specific warnings the system raised. Common warnings include data inconsistencies between fields, failed MRZ check digit validation, expired documents, or suspected tampering.
2. **Visually verify when warnings are ambiguous** — if a warning suggests potential tampering or low image quality, inspect the document images directly. Look for signs of digital editing, cropping, screen capture, or physical manipulation.
3. **Check extracted OCR data** — review the data the system extracted from the document and confirm it looks consistent. The system highlights fields where confidence is low.
4. **Review decoded QR codes and barcodes** — when Document AI detects a QR code or barcode on the document, the session view highlights its location on the image or PDF preview and shows the decoded payload. A code that was found but could not be decoded is shown as detected only, with no payload claimed. This location overlay is separate from the document-manipulation warning highlights, which keep their existing treatment.
5. **Review document matches** — if **Face Search** or duplicate detection is enabled, check whether this user has been verified before under a different identity or if the document appears in your **blocklist**.
Hover over document images in the Didit console to use the **zoom feature** for detailed pixel-level inspection. This is especially useful when verifying microprinting and holograms.
Didit automatically performs **face matching**, **liveness detection**, and — if enabled — **face search**. Your role is to review the results when they are flagged.
1. **Face match score** — the system computes a biometric similarity score between the selfie and the document portrait. When reviewing a flagged face match, focus on structural features like eye shape, nose geometry, jawline, and distinguishing marks.
2. **Liveness detection score** — the liveness system detects presentation attacks such as printed photos, screen replays, deepfakes, and 3D masks. Poor camera quality or low lighting can sometimes produce lower scores for genuine users — consider **requesting resubmission** rather than declining.
3. **Face search results** — review any matches carefully:
* **Duplicate detection** — a match with a different session may indicate the same person verifying under multiple identities.
* **Blocklist matches** — if the face matches a blocklisted user, this typically warrants a decline.
* **False positives** — facial similarity between different people does occur. Evaluate the confidence score and compare images visually.
Navigate through the **AML**, **Device & IP Analysis**, and **Database Validation** sections to assess contextual risk.
**AML Screening**
* **No matches** — low risk. No hits found against global watchlists, sanctions lists, or PEP databases.
* **Matches found** — review each matched entry. Check whether it's a true positive or false positive, review the match category (PEP, sanctions, watchlist, adverse media), and consider the source and recency of the data.
**Device & IP Analysis and device intelligence**
* **Geographic consistency** — compare the document's country of issue, the user's claimed address, and the IP geolocation.
* **VPN / Proxy detection** — check if the user is connecting through a VPN, proxy, or Tor network.
* **Device metadata** — review the device type, operating system, and browser information for anomalies.
***
## Making a decision
After completing your review, you have three options: **Approve**, **Decline**, or **Request Resubmission**.
* Document appears authentic and unaltered
* All data fields are consistent and legible
* Selfie biometric match confirms the same person
* Liveness check score is within acceptable range
* No relevant AML, sanctions, or PEP matches
* Device and location data are consistent
* Document appears tampered, forged, or altered
* Selfie does not match the document photo
* High-confidence AML or sanctions match confirmed
* Strong indicators of a presentation attack
* Fraudulent identity patterns detected
* Critical device or location inconsistencies
* Blurry or low-quality document images
* Failed liveness check due to technical issues
* Wrong document submitted
* Incomplete or expired verification steps
* Fixable issues the user can correct
Always **select a decline reason** and add detailed **review notes** when declining a session. This supports quality assurance, regulatory audits, and internal analytics.
***
## Requesting a resubmission
When a session cannot be clearly approved or declined — typically because of **fixable issues** — you can request the user to resubmit specific verification steps.
### How to request resubmission
Navigate to the session in the Didit console.
Click the actions menu and select **Request Resubmission**.
The dialog shows all non-approved features with their current status. Select or deselect individual steps. Features that were never started are automatically required.
If the user's email is available, send them a localized notification with a direct link to resume.
The session status changes to **Resubmitted** and a webhook is sent to your server.
### What happens after resubmission
1. **Feature data is reset** — only the selected features are cleared. All previous attempt data is archived and marked as `previous_attempt` in the logs.
2. **The user re-enters the verification flow** — they only need to complete the specific steps that were requested.
3. **Session stays in Resubmitted status** — distinct from **In Progress**, helping you track resubmission sessions separately.
4. **Automatic re-evaluation** — once all resubmitted features are completed, the system recalculates the final status automatically.
5. **New webhook fires** — your server receives a webhook with the updated final status.
You can request resubmission multiple times on the same session. Each cycle preserves the complete history of all previous attempts for compliance audits.
### Resubmission vs. creating a new session
| | Resubmission | New session |
| --------------------- | ------------------------------------------------------------- | -------------------------------------- |
| **Session ID** | Same session ID preserved | New session ID created |
| **History** | Previous attempts archived within the same session | Separate session, no linked history |
| **User effort** | Only redo failed/selected steps | Full verification from scratch |
| **Conversion impact** | Higher completion rates | Risk of user drop-off |
| **Webhook** | Same session ID, status changes to Resubmitted → final status | New session lifecycle from Not Started |
| **Audit trail** | Complete history in one place | Spread across multiple sessions |
***
## Review best practices
Apply the same standards to every review. Document your reasoning so other team members can understand and replicate your decisions.
If the problem is a blurry photo or a technical error, requesting resubmission preserves the session and avoids unnecessary friction for genuine users.
Always zoom into document images to check for subtle tampering, microprinting, or security features not visible at standard zoom levels.
Don't evaluate signals in isolation. A low liveness score combined with a VPN connection and mismatched geolocation is more concerning than any single factor alone.
Add detailed review notes for every decision, especially declines. These notes support internal QA, regulatory audits, and help train new team members.
Configure email or Slack alerts to get immediately notified when sessions enter **In Review** status. Fast response times improve both compliance and user experience.
If a user has multiple resubmission cycles, review the full history carefully. Repeated failures on the same feature may indicate a systemic issue.
# Marketplace
Source: https://docs.didit.me/console/marketplace
Choose which engine runs each workflow feature: Didit's own, or a third-party provider you connect from the Marketplace, with automatic fallback to Didit.
The **Marketplace** lets you connect third-party identity verification providers to your organization and choose, feature by feature, which engine actually runs the check: Didit's own engine, or a provider you connect yourself.
***
## Choosing an engine per feature
Every feature node's first configuration tab — in both **Simple** and **Advanced** workflow mode — starts with a **Verification engine** control. It shows the engine currently running that check and, next to it, how many providers are available for that feature.
Opening the control lists every provider for that feature with an explicit status:
| Status | Meaning |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Active** | This is the engine currently selected for the feature. |
| **Switch** | A provider you've already connected. Select it to make it the active engine for this feature. |
| **Connect** | A provider you have not connected yet. Selecting it opens the setup dialog so you can connect it without leaving the workflow builder. |
Features that only Didit's own engine supports show that engine with a "no other providers" note and a direct link into the Marketplace, so the control stays consistent across every feature even where there is nothing to switch to.
## Automatic fallback to Didit
Once you select a connected third-party engine for a feature, a **Fall back to Didit if my provider fails** switch appears for that feature:
* **On** (default): if your provider is unavailable, Didit's own engine runs automatically for that step, so the verification never fails outright.
* **Off**: if your provider is unavailable, the step fails instead of silently running on Didit.
## Configuring engine selection through the API
Engine selection and fallback are not part of the [workflow feature config reference](/management-api/workflows/feature-configs) — the Management API accepts them as `provider_key` and `fallback_to_native` on the feature's `config` object, but they are managed through this Marketplace UI rather than being documented per-feature fields:
| Field | Type | Description |
| -------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `provider_key` | string | The key of the connected provider to run this feature. Omit it, or leave it unset, to use Didit's own engine. |
| `fallback_to_native` | boolean | Whether to fall back to Didit's own engine if the selected provider is unavailable. Defaults to `true`. |
***
Build the workflow that each feature's engine selection applies to.
The full config reference for every workflow feature.
# Model training preference
Source: https://docs.didit.me/console/model-training-preference
Control whether your organization's verification data can be selected for future model training and evaluation.
Didit allows model training by default. An organization owner or administrator can opt the entire organization out at any time.
## Change the preference
1. Open the Business Console and select the organization.
2. Go to **Settings**.
3. Find **Help improve Didit's models**.
4. Turn the switch off and confirm **Opt out**.
The control is available only to roles with permission to update the organization. Other organization members can see the current state but cannot change it.
## What opting out does
Once the change takes effect, Didit excludes both historical and newly collected data belonging to the organization from future:
* model training and fine-tuning;
* model evaluation and validation;
* dataset curation; and
* training-data exports.
Opting out does not delete verification records, change retention settings, or affect live verification, fraud detection, support, security, or compliance processing. It also does not reverse updates already incorporated into a model before the opt-out took effect.
Turning the switch on again makes the organization's eligible data available for future model work. Each effective preference change is recorded for audit purposes.
For deletion or other privacy rights, follow the process in Didit's [Privacy Policy](https://didit.me/terms/privacy-policy) or contact [privacy@didit.me](mailto:privacy@didit.me).
# Networks
Source: https://docs.didit.me/console/networks
Detect fraud rings automatically by clustering applicants sharing device, IP, face, document, phone, email, or address signals.
Networks automatically groups applicants, businesses, and transaction parties in your organization that share device, IP, biometric, document, or contact signals, so a ring of fraudulent accounts surfaces as one investigation instead of dozens of unrelated flags.
***
## What a network is
A network is a cluster of subjects (vendor users, vendor businesses, or transaction parties) connected through one or more shared signals. Didit builds networks continuously as verification sessions, business sessions, and transactions complete, clustering **organization-wide** across every application in your organization so a fraud ring that touches two of your products still surfaces as one network. Each application only sees the networks that include at least one of its own subjects.
Clustering runs on production data only. Sandbox applications are excluded entirely, sandbox sessions never create or join a network.
## Signal types
A network forms when two or more subjects match on any of these signals:
The same device fingerprint appears across sessions.
Device fingerprints similar enough to indicate the same physical device with minor variation.
Sessions or transactions originating from the same IP address.
Proof-of-address or registry data resolving to the same address.
Liveness captures sharing a visually similar background, a signal of a single operator running multiple sessions from one location.
Proof-of-address documents similar enough to indicate template reuse or a shared source document.
Three additional exact-match signals extend the same clustering without a dedicated design pattern yet:
| Signal | Trigger |
| ---------------------- | ------------------------------------------------------------ |
| Shared document number | The same ID document number appears on more than one subject |
| Shared phone number | The same phone number appears on more than one subject |
| Shared email address | The same email address appears on more than one subject |
Signal values are never stored or displayed in the clear. Every signal except the selfie-background and face-similarity signals is identified by a keyed hash of its normalized value, exact matches link subjects without Didit (or you) ever seeing the underlying device ID, IP, document number, phone, or email again.
A signal that links an unusually large number of subjects (a shared office IP, a kiosk device, a data-center IP) is flagged as a **hub** rather than treated as automatic fraud evidence, hub signals are common in legitimate high-traffic settings and are surfaced for review, not auto-declined.
## Viewing a network
The **Networks** list is a table of every network visible to your application: network id, name, status, risk, the patterns and signals detected, and first/last activity, with the same status, signal, pattern, and risk-band filters as the API, plus free-text search by network name, member name, or network id.
Opening a network gives you four ways to explore it:
Subject and signal nodes connected by edges. Focus on one applicant and bound the view to 1, 2, or 3 hops to isolate their immediate connections instead of the whole network.
Every member as a row, with outcome status, risk, tags, and signal coverage, for scanning or exporting a large network.
Geographic points where members' addresses and locations converge, useful for spotting a ring anchored on one address or city.
Chronological history of the network: when it was first detected, when each signal was observed, and every status change with its reason.
## Lifecycle
A network moves through exactly four statuses:
| Status | Meaning |
| ----------- | ------------------------------------------------------------------------------------------ |
| `Active` | Newly detected or still accumulating members, not yet reviewed |
| `In review` | An analyst is actively investigating |
| `Resolved` | Investigation complete, the network was a legitimate cluster or the fraud has been handled |
| `Dismissed` | Reviewed and determined not to warrant action |
Every status change is recorded with a reason and the acting analyst, forming the network's audit trail alongside its signal and detection history.
Network status describes the investigation, not any one member. Each member also carries its own **outcome status** inside the network (`Approved`, `In review`, `Declined`, `Not completed`), which is that applicant's own verification decision. A network can be `Active` while individual members are already `Approved` or `Declined`.
## Acting on a network
From a network's detail page you can:
* **Create a case** to hand the network to your compliance team for a formal investigation, anchored to the network rather than a single subject.
* **Add to blocklist** a member's identity, or the signal itself, so future sessions matching it are automatically declined.
* **Change status** to move the network to In review, Resolved, or Dismissed, with a required reason.
* **Dismiss** a network in one step when it is clearly not fraud, this also closes out an active review if one is open.
## Related pages
Retrieve networks for your organization through the API.
Look up which networks a session, user, business, or transaction belongs to.
How blocklisting a document, face, phone, or email affects future sessions.
Investigate a network case through to resolution.
# Roles & Permissions
Source: https://docs.didit.me/console/roles-permissions
Manage team access with role-based permissions in the Didit Console. Use built-in roles or create custom roles with granular per-resource permission control.
This chapter of the Didit Academy dashboard and roles lesson picks up at team members and roles.
The Academy lesson on teams reaches members and roles at 4:04, then walks the five built-in roles and custom roles.
## Overview
Didit uses a role-based access control (RBAC) system to manage what team members can do in the console. Each member is assigned a **role**, and each role has a set of **permissions** that control access to specific features.
## Default roles
Didit provides five built-in (system) roles that cover common team structures:
| Role | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Owner** | Full access to all features including organization management, billing, and role management. Only owners can transfer ownership or delete the organization. |
| **Admin** | Full management access to verification features, team members, and application settings. Cannot delete the organization or manage roles. |
| **Compliance Officer** | Focused on reviewing sessions, managing AML/blocklists, viewing audit logs, and handling transactions and business profiles. |
| **Developer** | Manages workflows, webhooks, API keys, questionnaires, and application configuration. Read-only access to verification sessions. |
| **Reader** | Read-only access to all console sections. Cannot modify any data. |
System roles cannot be edited or deleted.
## Custom roles
Organization owners can create **custom roles** with specific permission combinations tailored to your team's needs.
To create a custom role:
1. Go to **Settings** > **Roles**
2. Click **Create Role**
3. Enter a name, slug, and description
4. Select the permissions you want to grant
5. Click **Create Role**
Custom roles can be edited and deleted at any time. You cannot delete a role that is currently assigned to members — reassign them first.
## Permission reference
Permissions follow an `action:resource` format. The available actions are:
* **read** — View a resource
* **write** — Update a resource
* **create** — Create new instances of a resource
* **delete** — Remove a resource
* **list** — List multiple instances of a resource
### Resource permissions
| Resource | Available actions | Console section |
| ---------------- | --------------------------------- | ----------------------------------------------------------- |
| `organization` | read, write, delete | Organization settings |
| `members` | read, list, write, delete | Team members |
| `roles` | read, list, write, create, delete | Role management |
| `applications` | read, list, write, create, delete | Application settings |
| `sessions` | read, list, create, write, delete | Verification sessions, status changes, and session deletion |
| `users` | read, list | End-user directory |
| `businesses` | read, list, write | Business profiles (KYB) |
| `transactions` | read, list, create, write | Transaction monitoring |
| `workflows` | read, write, create, delete | Workflow editor |
| `questionnaires` | read, write, create, delete | Questionnaires |
| `customization` | read, write | White-label branding |
| `lists` | read, write, create, delete | Lists (blocklists/allowlists) |
| `blocklist` | read, write, create | Blocklist management |
| `webhooks` | read, write, create, delete | Webhook destinations |
| `api-keys` | read, write | API key management |
| `analytics` | read | Analytics dashboard |
| `audit-logs` | read, list | Audit logs |
| `subscription` | read, write | Billing and usage |
| `invoices` | read, list | Invoice management |
| `saml` | read, write | SSO/SAML configuration |
## Assigning roles
When inviting a new team member or editing an existing member, you select from all available roles (both system and custom).
Members can only be assigned one role at a time. To change a member's role, go to **Settings** > **Team** and edit the member.
## Invitation links
Each invitation is emailed as a single-use link scoped to the invited address and expires after **30 days**. If an invitee reports that their link shows **Invitation unavailable**, the original invitation is no longer valid - go to **Settings** > **Team** and send them a new one rather than resending the old email.
# Session chats
Source: https://docs.didit.me/console/session-chats
Collaborate on verification reviews with in-session chats. Mention teammates, document compliance decisions, and get real-time notifications via WebSocket.
Session Chats enable compliance teams to collaborate directly within verification sessions. Leave comments, mention colleagues with `@email`, and receive real-time notifications — all within the Didit Console.
This feature transforms the manual review process from an isolated task into a collaborative workflow, ensuring your team can efficiently discuss edge cases, escalate concerns, and document decisions without leaving the platform.
***
## Why session chats?
| Challenge | Solution |
| ------------------------------- | ------------------------------------------------ |
| Context switching between tools | All discussion happens directly on the session |
| Lost audit trails | Every comment and activity is permanently logged |
| Delayed responses | Real-time notifications via WebSocket and email |
| Unclear ownership | @mentions notify specific team members instantly |
| Scattered documentation | Comments create a searchable history per session |
***
## Core features
### Comments and mentions
Add comments to any verification session to discuss findings, request clarifications, or document decisions. Use `@email` syntax to mention specific team members.
```text theme={null}
"I've reviewed the document and the address doesn't match.
@maria@company.com can you verify with the customer?"
```
When you mention a colleague:
* They receive an **instant notification** in the Console
* The notification includes a direct link to the session
***
## Notification types
| Type | Trigger | Description |
| ------------ | ----------------------------------------- | ------------------------------------------------------------------- |
| **Mention** | `@email` in comment | You were directly mentioned in a comment |
| **Comment** | New comment on a session you've reviewed | Someone added a comment to a session you previously interacted with |
| **Activity** | Any activity on a session you've reviewed | Status change, file upload, tag change, etc. |
***
## Best practices
When you need a specific person's attention, use `@email` to ensure they're notified immediately.
Create an audit trail by explaining why you approved, rejected, or escalated a session.
Add a tag like `needs-review` and leave a comment explaining what needs attention.
# Transaction monitoring
Source: https://docs.didit.me/console/transaction-monitoring
Review transactions, tune rules, investigate alerts, manage cases, and monitor wallet and travel-rule exposures. AML, KYT, and crypto monitoring in one console.
Use **Transactions** in the Business Console to:
* Review incoming and outgoing transactions with scores, tags, and rule matches.
* Use the **Overview** tab to monitor transaction count, transaction volume, average amount, action-type and category breakdowns, user geography, top rules triggered, analyst performance, and score distribution.
* Start from a built-in KYT rule library covering finance monitoring, AML/CTF red flags, device-intelligence patterns, crypto-monitoring exposures, Travel Rule escalations, and responsible-gaming scenarios.
* Build custom monitoring rules with velocity windows and remediation actions.
* Investigate alerts, open cases, and assign analysts.
* Monitor wallet-risk snapshots, travel-rule obligations, and network graph payloads.
* Configure default currencies, thresholds, and remediation workflows.
When a rule moves a transaction to `AWAITING_USER`, Didit creates a linked remediation session and returns a verification URL so you can continue the flow without leaving the transaction workspace.
## Built-in rule bundles
Didit seeds a preset KYT rule library for every application with transaction monitoring enabled. The starting library covers common regulatory typologies, including:
* **AML/CTF and finance**: structuring, cumulative volume, large single transactions, rapid in-and-out movement, sanctions, PEP exposure, and high-risk jurisdictions.
* **Device intelligence and fraud prevention**: repeated bonus-campaign use from the same fingerprint, multiple device fingerprints for one subject, and multi-accounting patterns.
* **Crypto monitoring**: mixer exposure and high-risk wallet exposures.
* **Travel Rule**: pending counterparty, pending action, and missing required data scenarios.
* **Responsible gaming**: rapid deposit bursts and other gaming-focused escalation patterns.
# Unilinks
Source: https://docs.didit.me/console/uni-links
Launch identity verification without code using Didit Unilinks — one reusable URL per workflow. Share via email, SMS, QR code, or button for instant KYC.
**Unilink** is a single, reusable link per workflow that starts the hosted Didit verification flow — no backend required. Put it behind a button on your site, send it to users, or print it as a **QR code**. Each visit spins up a session for that workflow; results are visible in the **Business Console** for manual review.
***
## When Unilink is the right choice
Choose **Unilink** when you want **zero backend integration** and **maximum speed to market**, especially for manual or operational review scenarios:
No API integration or backend setup needed.
Validate your verification flow before investing in a full integration.
Use the QR code in-store, at a branch, or on a kiosk.
Share the link via email, SMS, or WhatsApp.
***
## When NOT to use Unilink
Skip Unilink if you need any of the following — use the [API (Create Session)](/sessions-api/create-session) instead:
* **Deep backend control** over sessions and lifecycle
* **Fully automated** user journey (no manual steps)
* **Silent** or background checks
* **Dynamic workflows** based on user attributes
* **Complex conditional logic** (branching by country, risk, document type)
* **High-volume, highly customized** enterprise orchestration
***
## Unilink vs API
| Use case | Best option |
| --------------------------------- | ----------- |
| Quick launch / MVP | **Unilink** |
| No backend | **Unilink** |
| Send a link to a user | **Unilink** |
| QR verification (kiosk / branch) | **Unilink** |
| Manual operator review in Console | **Unilink** |
| Fully custom flow | **API** |
| Backend automation | **API** |
| Conditional risk logic | **API** |
| Mass scale with per-user context | **API** |
**Simple rule of thumb:** if you want **speed and simplicity**, choose Unilink. If you want **control and automation**, choose the API integration.
***
## How to use Unilink
In the **Business Console**, set up your verification steps, liveness/AML checks, and branding.
Each workflow has a unique Unilink. Copy it from the workflow settings.
Share the link via:
* A button on your website
* Email / SMS / WhatsApp
* Printed or on-screen QR code
Operators review results in the **Console** and match to internal records.
### Example: website button
```html theme={null}
Verify your identity
```
***
## Best practices
1. **Start with Unilink** for manual ops and MVPs; evolve to [Create Session](/sessions-api/create-session) as automation needs grow.
2. **Sandbox vs. Live** — test with sandbox keys/links; swap for production at go-live.
3. **Security** — if you expose the link publicly, consider bot-mitigation on the host page.
4. **Webhooks** — verify signatures, retry on failure, and store event history if you automate later. See the [Webhooks guide](/integration/webhooks).
# Verification links
Source: https://docs.didit.me/console/verification-links
Create secure verification links and QR codes from the Didit console or API. Launch hosted identity verification in minutes with no frontend development.
A Verification Link is a unique, secure URL that directs your end user to a verification flow hosted entirely by Didit. You design the verification steps and logic in your Business Console, and Didit handles the user interface, data capture, and security. This allows you to launch a robust verification process in minutes, often with no code required.
This method is part of the **Orchestrated Workflows** integration path, giving you the power of the workflow engine with maximum simplicity.
***
## How it works
In the [Didit Business Console](https://business.didit.me), use the no-code editor to design a workflow with the exact sequence of checks you need (e.g., ID Document Scan → Liveness Check → AML Screening). Each workflow has a unique `workflow_id`.
Create a unique session for a user. This can be done **no-code** directly from the Business Console, or **low-code** via a single API call to the [Create Session](/sessions-api/create-session) endpoint.
Send the generated URL to your user through any channel — email, SMS, in-app message — or embed it in an iframe.
Didit sends automated updates to your configured [webhook URL](/integration/webhooks) as the user progresses and when the final verification result is ready.
***
## Generating verification links
### Method 1: No-code generation (via Business Console)
The perfect method for getting started instantly, for manual processes, or for teams without developer resources.
1. Navigate to the **Verifications** section in your [Didit Business Console](https://business.didit.me).
2. Click **+ Create Verification**.
3. Select the `workflow_id` you want to use for this session.
4. Optionally enter `vendor_data` (like a user ID from your system) to map the verification back to your user.
5. A **unique URL and QR code** are generated instantly.
6. Copy the link, have the user scan the QR code, or send the link directly to the user's email.
### Method 2: Low-code generation (via API)
This method offers full automation and is the standard way to integrate verification links into your application logic.
Send a single `POST` request to the `/v3/session/` endpoint with your API key and `workflow_id`:
```bash theme={null}
curl --request POST \
--url https://verification.didit.me/v3/session/ \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"workflow_id": "550e8400-e29b-41d4-a716-446655440000",
"vendor_data": "your_internal_user_id_123",
"callback": "https://yourapp.com/didit/webhook/handler"
}'
```
The API returns a JSON object containing the `session_id` and the unique verification `url`:
```json theme={null}
{
"session_id": "11111111-2222-3333-4444-555555555555",
"session_number": 1234,
"vendor_data": "your_internal_user_id_123",
"status": "Not Started",
"workflow_id": "550e8400-e29b-41d4-a716-446655440000",
"callback": "https://yourapp.com/didit/webhook/handler",
"url": "https://verify.didit.me/session/abcdef123456"
}
```
Full API details are available in the [Create Session API Reference](/sessions-api/create-session).
***
## When to use verification links
Launch a complete, secure, and compliant verification process in hours, not weeks.
Offload the entire user interface — document capture, liveness checks, and user guidance — to Didit's pre-built UI.
Send verification requests via email, SMS, support chat, or any other communication channel.
Use the QR code for in-person scenarios where a user needs to complete verification on their own device.
***
## Next steps
* **Configure your first workflow** — head to the [Business Console](https://business.didit.me) to design your verification journey.
* **Set up your webhooks** — learn how to receive real-time status updates in the [Handling Webhooks](/integration/webhooks) guide.
* **Explore Unilinks** — for a reusable, zero-backend alternative, see [Unilinks](/console/uni-links).
# White Label
Source: https://docs.didit.me/console/white-label
Fully white-label the Didit verification UI with your colors, logo, typography, and custom domain. Deliver a seamless branded KYC experience to your users.
White-label the verification flow to match your brand identity, creating a seamless experience for your users.
The workflow Academy lesson reaches white-labelling at 13:43, applying your branding to a live flow.
## Customization
In the Style Editor you can customize **everything** in the verification UI:
| Category | What you can customize |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| **Colors** | Buttons, text, panels, backgrounds — every color element |
| **Typography** | Fonts that match your brand |
| **Logos** | Square and rectangular logo uploads |
| **Layout** | Border radius for panels and buttons |
| **Login screen** | Show or skip the login screen |
| **Custom domain** | Host on your own subdomain instead of `verify.didit.me` — see [Custom domain](/console/custom-domain) |
## Activate custom style in your workflow
After configuring your custom style, you must **enable it per workflow** for it to apply:
1. Go to **Workflows** in the sidebar
2. Open the workflow you want to brand
3. Click **Settings** (or the gear icon)
4. Under **Options**, find **Include custom style**
5. **Enable** the toggle
Custom styles only apply to workflows where **Include custom style** is enabled. If you don't enable it, the workflow will use the default Didit branding.
## Custom text
Beyond colors, logos, and layout, the **Texts** tab lets you override the wording of specific verification-flow screens and change how the completion screen behaves, without forking translations for every locale.
Open **Console → White Label → Texts** to configure:
* **Text overrides** — replace the wording of a fixed set of strings across the welcome screen, document selection, document capture, selfie intro, questionnaire navigation buttons, the completion screen, and common buttons like Continue and Retry. An override applies for one locale at a time, and you can set a different value per locale.
* **Completion screen mode** — choose **Decision** (default) to show the real outcome on the final screen, or **Submitted for review** to always show a neutral "we received your data and are reviewing it" message, even when Didit already approved the session. Use this if your own product communicates the final decision to users later.
Text overrides and the completion screen mode apply everywhere the verification flow renders: the hosted web flow, the iOS SDK, and the Android SDK.
### Dynamic placeholders
Override text can include these placeholders, resolved per user at render time:
| Placeholder | Resolves to |
| ------------- | ------------------------------------------------ |
| `{appName}` | Your application's public name |
| `{stepIndex}` | The current step number in the user's workflow |
| `{stepTotal}` | The total number of steps in the user's workflow |
`{stepIndex}` and `{stepTotal}` are computed from the actual workflow the user is running, so the same stored string correctly renders "step 1 of 3" for a three-step workflow and "step 1 of 2" for a two-step workflow.
Each overridden string has a 500-character limit. The Texts tab supports every locale your application can translate into, which is a longer list than the language selector in the Style Editor. Use **Auto-translate** to draft overrides for other locales from a value you've written by hand, then review before saving.
Custom text and the completion screen mode are configured entirely from the Console and only apply where **Include custom style** is enabled for the workflow, same as the rest of white-label.
## Setup
Navigate to **Console → White Label → Style Editor**.
* Select your brand colors using the color picker
* Upload logos in both square and rectangular formats
* Adjust typography and border radius settings
Serve the flow from your own subdomain instead of `verify.didit.me`. Follow [Custom domain](/console/custom-domain) for the DNS records and verification steps.
Open the **Texts** tab to change the wording of specific screens per locale, or switch the completion screen to **Submitted for review**. See [Custom text](#custom-text).
For each workflow that should use your branding: **Workflow → Settings → Options → Include custom style** → enable.
Use the live preview to see changes in real-time. Test the full verification flow before going live.
## Compliance responsibilities in white-label flows
White-labeling changes the **branding** of the verification flow. It does **not** remove your obligations as the controller of that user journey.
Before you launch a white-label flow, make sure you:
1. Tell the end user that **your company** is requesting the verification and that **Didit** powers the verification workflow.
2. Link to **your own privacy notice** and any controller-side legal terms that apply to the journey.
3. Link to Didit's [Verification Privacy Notice](https://didit.me/terms/verification-privacy-notice) and [End User Terms for Identity Verification](https://didit.me/terms/identity-verification).
4. Collect **explicit affirmative consent** before document capture, selfie capture, liveness, or biometric processing whenever the applicable law or your legal position requires it.
5. Keep any proof of notice or consent that your legal team requires in your own systems.
Using a custom domain or removing visible Didit branding does not eliminate Didit's role as the verification provider. If you use a custom UI or an API-driven flow, you must surface the required disclosures in your own interface.
# Workflows
Source: https://docs.didit.me/console/workflows
Build no-code identity verification flows with the visual workflow builder. Drag-and-drop ID, liveness, AML, NFC, branching logic. Pay only for completed steps.
Didit's Orchestrated Workflows are the most powerful and flexible way to design and deploy comprehensive, multi-step identity verification journeys.
This Didit Academy lesson builds a verification workflow in the real console, from template to published flow: picking checks on each node, rules and thresholds, AML branching, webhook nodes, and version rollback.
This integration path allows you to leverage the full intelligence of the Didit V2 platform, creating sophisticated verification sequences with our no-code visual builder. You define the logic once in your Business Console, and Didit handles the entire user-facing experience, state management, and conditional steps.
Choose this path when you need a complete, end-to-end solution for user onboarding (KYC), age verification, or re-authentication, and you want to launch quickly with minimal development effort while retaining maximum control over the process.
## Country identity methods
In ID Verification, open **Countries and allowed ID methods** to configure document capture, non-doc lookup, and digital identity wallets for each country.
**Additional settings** contains document subtypes, lookup attempts, and fallback rules.
Non-doc lookup uses your organization's Database Validation service pricing.
Each completed attempt is charged separately, including partial matches and no matches: two completed attempts mean two charges.
Format errors and provider failures that return no answer are not charged.
If several database services answer one attempt, their applicable service prices are added together.
The default is **one attempt before fallback**, configurable from one to five.
Document capture is charged additionally when a completed lookup falls back to documents.
Live applications can enable only wallets available for production.
Wallets awaiting launch stay disabled.
Sandbox applications can test every wallet in the catalog with simulated results and no charges; sandbox availability does not indicate production availability.
See [Sandbox testing](/integration/sandbox-testing) for lookup and wallet failure scenarios.
## Workflow Versioning
Workflows support **draft/publish versioning**. This means you can safely iterate on your workflow without affecting live sessions:
* **Draft versions** are fully editable — add, remove, or restructure nodes freely
* **Publishing** a draft creates an immutable version that new sessions will use
* **Previous versions** are preserved, so you can inspect the exact configuration used for any past session
* **Sessions always reference the specific version** they were created with, ensuring consistency even after you publish updates
To make changes to a published workflow, create a new draft from the published version, edit it, and publish when ready. The version history is available via the Management API.
***
## Two Ways to Build Workflows
Didit offers **two distinct approaches** to creating verification workflows, allowing you to choose the right level of complexity for your needs:
### 1. Simple Mode: Template-Based Builder
The **Simple Mode** is perfect for getting started quickly. Select a pre-built template, toggle the features you need on or off, and you're ready to go.
**Best for:**
* Quick setup and deployment
* Standard verification flows
* Teams new to identity verification
* Use cases that fit common patterns
**How it works:**
1. Choose a workflow template (KYC, Age Verification, etc.)
2. Toggle features on/off (Liveness, Face Match, AML, etc.)
3. Configure basic settings for each feature
4. Publish and start verifying
***
### 2. Advanced Mode: Visual Graph Builder
The **Advanced Mode** unlocks the full power of Didit's orchestration engine. Build complex, conditional verification flows using our visual graph editor with drag-and-drop nodes, branches, and custom logic.
**Best for:**
* Complex, multi-path verification journeys
* Conditional logic based on user data or verification results
* Custom business rules and branching
* Enterprise-grade compliance requirements
**Key capabilities:**
* **Visual node editor**: Drag, drop, and connect nodes on an infinite canvas
* **Smart connections**: Drag from a node handle to empty space to instantly create and connect a new node
* **Conditional branches**: Route users based on extracted data, verification status, country, document type, [document subtype](/core-technology/id-verification/document-subtypes-id-verification), date of issue, age, webhook JSON response paths, or custom rules
* **Action automation**: Add tags, set metadata, or route to manual review based on flow outcomes
* **Keyboard shortcuts**: Undo (Ctrl/Cmd+Z), Redo (Ctrl/Cmd+Shift+Z), Delete (Delete/Backspace)
* **Zoom and pan**: Navigate complex flows with scroll-to-zoom and drag-to-pan
#### Graph Builder Node Types
| Node Type | Icon Color | Description | Examples |
| ----------------- | --------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Feature Nodes** | Blue | Verification checks that process user data | ID Verification (OCR), Liveness, Face Match, NFC, AML, Phone/Email Verification, Questionnaire, Proof of Address, Document AI, Database Validation, Device & IP Analysis, Age Estimation |
| **Branch Nodes** | Purple | Conditional routing based on data or results | Route by country, risk score, document type, document subtype, age, date of issue, months since issue, verification status |
| **Action Nodes** | Amber | Automation triggers that modify session or user data | Add tag, Remove tag, Set metadata, Add review note |
| **Webhook Nodes** | Cyan | HTTP requests that can feed data into later branches | Send session data to your server, parse JSON responses, branch on paths such as `category`, `status`, or `tags.0` |
| **Status Nodes** | Green/Red/Amber | Final outcomes that end the verification flow | Approved, Declined, In Review |
#### Feature Node Categories
**User-Interactive Features** (can start a workflow):
* ID Verification (OCR)
* Liveness Detection
* Face Match
* Age Estimation
* Phone Verification
* Email Verification
* Questionnaire
* Proof of Address
* Document AI
* NFC / ePassport
**Backend-Only Features** (run automatically, no user interaction):
* AML Screening (requires OCR first)
* Database Validation (requires OCR first)
* Device & IP Analysis
> **Note:** Backend-only features are marked with a special indicator in the builder and execute automatically without user interaction once their dependencies are met.
Each feature node's first configuration tab starts with a **Verification engine** control: run the check with Didit's own engine, or switch it to a third-party provider you connect from the [Marketplace](/console/marketplace), with automatic fallback to Didit if that provider is unavailable.
#### How Branch Conditions Read Session Data
**Which record a condition reads.** A branch condition is bound to the feature node it was built from, and it reads that node's result. When a single node produced more than one record, the condition reads the **most recent** one. This matters most for **Device & IP Analysis**: Didit records one observation per device and IP address a session is opened from, so a user who switches phone or network mid-session leaves several records on the same node. A rule such as `IP COUNTRY is in [...]` is then evaluated against the **latest** observation - the same record shown first in the session's Device & IP Analysis panel and in the `ip_analyses` array of the decision payload. Sessions with more than one observation also carry the `MULTIPLE_DEVICES_IN_SESSION` warning, which is the signal to review the full list rather than the branch outcome alone.
**Country values.** Country conditions are matched on the country itself, not on the notation. Pick countries from the field picker and Didit normalizes both sides of the comparison, so a rule written in ISO 3166-1 alpha-3 (`UKR`) still matches a value captured as alpha-2 (`UA`).
**Resubmissions.** Branch nodes are evaluated on every attempt, including a resubmission. When you send a session back for specific steps, the branches and actions placed between those steps run again as the user re-completes them, against the data of the **new** attempt. A user who moves to a restricted jurisdiction between attempts is therefore routed by the branch that covers it, and a branch that ends on a Declined outcome ends the resubmitted session there.
***
## Workflow Templates: Your Starting Point
Think of templates not as rigid types, but as **smart, pre-configured starting points** designed for common use cases. In Simple Mode, these are ready to use. In Advanced Mode, they provide a foundation you can customize extensively.
### Available Templates
#### **1. KYC Workflow**
The comprehensive solution for onboarding new users and meeting full Know Your Customer (KYC) compliance.
* **Starts with:** Core ID Document Verification.
* **Commonly Added Features:**
* `[+]` **Liveness Detection:** Ensure the user is physically present and prevent spoofing.
* `[+]` **Face Match 1:1:** Biometrically match the user's selfie to their ID photo.
* `[+]` **AML Screening:** Check users against global sanctions, PEP, and adverse media lists.
* `[+]` **NFC Verification:** Add a layer of government-grade security by reading e-passport/e-ID chips.
* `[+]` **Proof of Address (PoA):** Verify the user's residential address.
* `[+]` **Phone Verification:** Validate phone number ownership as an additional factor.
* `[+]` **Email Verification:** Validate email address ownership as an additional factor.
* `[+]` **Database Validation:** Validate against official government and credit databases.
* `[+]` **Questionnaire:** Collect structured attestations and supporting documents via customizable forms.
* `[+]` **Device & IP Analysis:** Analyze location and connection risk.
***
#### **2. Adaptive Age Verification Workflow**
A low-friction, privacy-preserving flow for age-gated services.
* **Starts with:** Selfie-based Age Estimation.
* **Key Logic:**
* If the estimated age is clearly above your threshold (e.g., estimated 25+ for an 18+ service), the user passes instantly.
* If the estimate is below or within a "buffer zone" (e.g., estimated 16-20), you can configure the workflow to automatically trigger a **fallback to full ID Verification** to confirm the exact date of birth.
* **Commonly Added Features:**
* `[+]` **Device & IP Analysis:** Restrict access based on geographic location.
***
#### **3. Biometric Authentication Workflow**
A fast and secure way to re-verify returning users without asking for their documents again.
* **Starts with:** A Liveness Detection check to confirm the user is present.
* **Core Logic:**
* The system performs a Face Match between the new live selfie and the trusted biometric template from the user's initial, approved KYC verification.
* If the user already has a stored face under the same `vendor_data` (approved liveness face, ePassport photo, ID document portrait, or an enrolled profile face), you can omit `portrait_image` when creating the session and Didit reuses the stored face automatically. Otherwise pass `portrait_image` (Base64-encoded image, max 2MB); without either, session creation fails with `400`.
* **Commonly Added Features:**
* `[+]` **Phone Verification:** Link the address to a verified phone number.
* `[+]` **Email Verification:** Validate email address ownership as an additional factor.
* `[+]` **Device & IP Analysis:** Flag suspicious login attempts from new locations.
***
#### **4. Address Verification Workflow**
A dedicated flow for when Proof of Address (PoA) is the primary requirement.
* **Starts with:** The user submitting a Proof of Address document (e.g., utility bill, bank statement).
* **Core Logic:** Our AI extracts and validates the name and address from the document.
* **Commonly Added Features:**
* `[+]` **Phone Verification:** Link the address to a verified phone number.
* `[+]` **Email Verification:** Validate email address ownership as an additional factor.
* `[+]` **Device & IP Analysis:** Compare the document address to the user's current geo-location for added assurance.
***
#### **5. Questionnaire Verification Workflow**
A focused flow when your primary goal is to collect structured attestations and supporting documents via customizable forms.
* **Starts with:** Questionnaire.
* **Core Logic:** The system presents your configured questionnaire (sections, translated content, required fields, uploads).
* **Commonly Added Features:**
* `[+]` **Device & IP Analysis:** Add location and connection context to your questionnaire submissions.
> **Full Customization**
> Each feature within a workflow can be further customized with specific parameters to meet your exact requirements. Explore the settings for each check in the builder.
***
## Build rules from marital status
In **Advanced Mode**, you can use the OCR field `kyc.marital_status` in conditional branches and custom status rules. Choose the value from the field picker so the rule uses the supported enum rather than free-form text. If the document does not provide marital status, the value is empty and your fallback branch applies.
The workflow copilot can also create and configure **Database Validation** steps in one action. When you ask it to add this check, specify the countries you need; the console resolves the supported service for each country and shows the saved configuration on the canvas.
***
## Simple vs Advanced Mode Comparison
| Capability | Simple Mode | Advanced Mode |
| ---------------------------------- | ----------- | -------------- |
| Template selection | ✅ | ✅ |
| Toggle features on/off | ✅ | ✅ |
| Basic feature configuration | ✅ | ✅ |
| Visual graph editor | ❌ | ✅ |
| Conditional branching | ❌ | ✅ |
| Action automation (tags, metadata) | ❌ | ✅ |
| Parallel verification paths | ❌ | ✅ |
| Risk-based decisions | Limited | ✅ Full control |
| Undo/Redo support | ❌ | ✅ |
| Keyboard shortcuts | ❌ | ✅ |
***
## Integration Flow
Integrating an Orchestrated Workflow is straightforward. Your server creates a session with Didit, receives a unique URL, and redirects your user to that URL. Didit handles the rest and notifies your server of the results via webhooks.
```mermaid theme={null}
sequenceDiagram
participant Customer
participant Client
participant Server
participant Didit
Customer->>Client: Go to verification page
Client->>Server: POST /create-verification-session
Server->>Didit: POST /v3/session/
Didit-->>Server: Return new verification session
Server-->>Client: Return verification session's url
Client->>Customer: Redirect customer to url
Customer->>Didit: Complete verification steps
Didit->>Server: Send status updates
Server->>Customer: Notify of status changes
Customer->>Customer: Return to callback URL
```
***
## Common Use Cases
Use Case
Recommended Template
Recommended Mode
Suggested Features
Basic identity verification
KYC
Simple
Liveness, Face Match
High-security onboarding
KYC
Advanced
NFC, Liveness, Face Match, AML, Phone Verification
Age-gated content/services
Adaptive Age Verification
Simple
ID Verification fallback
Returning user authentication
Biometric Authentication
Simple
Device & IP Analysis
Address verification
Proof of Address
Simple
Phone Verification
Financial services onboarding
KYC
Advanced
Liveness, Face Match, Proof of Address, AML, Database Validation
Region-specific compliance
KYC
Advanced
Conditional routing by country, different checks per region
Risk-based verification
KYC
Advanced
Conditional branches based on risk scores, escalation paths
***
## Getting Started
1. **New to Didit?** Start with **Simple Mode** and a pre-built template
2. **Need customization?** Switch to **Advanced Mode** to add conditional logic
> Contact our sales team to discuss custom workflow configurations or to get guidance on the best setup for your specific requirements.
# Age Estimation Overview
Source: https://docs.didit.me/core-technology/age-estimation/overview
Verify user age from selfies with AI facial analysis. Pay-per-call $0.10, ±3.5 year accuracy, configurable ID-verification fallback for edge cases.
Didit's Age Estimation technology provides enterprise-grade age verification through advanced facial analysis and machine learning. Our system delivers high accuracy with typical estimation within ±3.5 years for most age ranges.
## Age Estimation Methods
Our platform implements age estimation in conjunction with different liveness verification technologies:
Method
Description
Security Level
Best For
**`3D Action & Flash`**
• Combines multi-factor biometric verification with a **randomized action sequence** and **dynamic light pattern analysis**.
• At the start, the user is prompted to perform a simple action—like **blinking** or **nodding**—ensuring real-time interaction.
• Simultaneously, the system projects a sequence of light patterns onto the face, analyzing the **reflections** to confirm the face's three-dimensional structure.
• **Deep learning algorithms** examine micro-expressions and the light reflection responses to verify the presence of a live person.
• Offers the **highest security** by integrating behavioral (action) and physical (light-based depth) cues, making it nearly impossible to spoof with static images, videos, or even advanced masks.
Highest
Banking, healthcare, government applications
**`3D Flash`**
• Uses **dynamic light pattern analysis** to validate facial topology without requiring user interaction.
• Projects a series of light patterns onto the face at over **30 frames per second**, analyzing the reflections to create a **depth map**.
• This depth map confirms the face's three-dimensional structure, distinguishing it from flat images or 2D spoofs.
• Provides a **seamless experience** while maintaining **high security** against presentation attacks like photos or screens.
• Relies on **single-frame deep learning analysis** to detect signs of liveness.
• For privacy, the user's face appears blurry in the interface, assuring them that their image is being analyzed for age estimation only, not for identification.
• Examines the image for **artifacts**, **texture patterns**, and other subtle indicators that differentiate a real face from a spoof.
• A **convolutional neural network (CNN)** validates facial features and identifies anomalies, such as those from printed photos or digital screens.
• Offers **fast and convenient** verification but provides **standard security**, suitable for low-risk use cases.
Standard
Low-friction scenarios, consumer applications
Each method generates a precise age estimate along with confidence scores and supplementary gender estimation data.
### Configurable Thresholds
You can customize security levels by setting different thresholds for age estimation. For example:
These thresholds can be adjusted based on your risk tolerance and security requirements.
### Per-Country Age Restrictions
Age restrictions can be configured on a **per-country basis**, reflecting the fact that the legal age of majority varies across jurisdictions. Instead of setting a single global minimum or maximum age, you can define specific age limits for each country -- and even for individual states or regions within a country.
#### How It Works
* **Country-level configuration**: Set a minimum and/or maximum age for each country (identified by the document's issuing state). For example, you might require a minimum age of 18 in the United States, 19 in South Korea, and 21 in the United Arab Emirates.
* **State/Region overrides**: For countries with sub-national variation (such as the United States or Mexico), you can configure overrides per state. For example, Mississippi may require a minimum age of 21 while the US default is 18. The system matches the state using the region extracted from the document by OCR.
* **Age of majority defaults**: The console provides a one-click "Apply age of majority" button that auto-populates each country's minimum age based on the known legal age of majority worldwide. You can then customize individual countries or states as needed.
* **Configurable actions**: When a user's age falls below the minimum or exceeds the maximum for their document's country, you can choose the action to take: **Decline** or **Review**.
#### Example Configuration
| Country | Min Age | Max Age | State Overrides |
| ------- | ------- | ------- | ---------------------------- |
| USA | 18 | -- | Mississippi: 21, Alabama: 19 |
| KOR | 19 | -- | -- |
| GBR | 18 | 65 | -- |
| ARE | 21 | -- | -- |
Per-country age restrictions are available in both standard KYC workflows and Adaptive Age Verification workflows. In adaptive workflows, when ID verification is triggered for borderline cases, the age check uses the per-country settings configured in the ID Verification step.
## How It Works
The user provides a clear facial image through API upload or completes a liveness verification process.
| Check | Description |
| ------------------------- | ---------------------------------------------- |
| **Image quality** | Validates lighting, positioning, and clarity |
| **Liveness verification** | Ensures the subject is present and not a spoof |
| **Multi-frame analysis** | Selects the optimal frame for best accuracy |
| **Adaptive capture** | Adjusts to various device capabilities |
Advanced computer vision isolates the face and maps key facial landmarks.
* **80+ reference points** mapped across the face
* Deep learning algorithms analyze facial morphology, proportions, and texture
* Demographic-specific features identified with precise pixel mapping
* Facial regions segmented for specialized analysis (eye region, jawline, skin texture)
Convolutional neural networks (CNNs) process the extracted features through multiple layers.
* Model trained on **millions of diverse faces** across age ranges, ethnicities, and genders
* Feature vectors compared against age-correlated datasets with demographic calibration
* Multiple sub-models employed for different age brackets to enhance accuracy
* Cross-validation against complementary models for robust estimation
The system generates a comprehensive result:
| Output | Description |
| ------------------------- | -------------------------------------------- |
| **Age estimate** | Primary estimate with confidence scoring |
| **Gender estimation** | Supplementary demographic context (optional) |
| **Confidence metrics** | Overall reliability assessment |
| **Environmental factors** | Impact assessment on estimation quality |
Your configured business rules are applied to the estimation results.
* Age estimate compared against your **configured thresholds** (min/max per country)
* Confidence scores checked against your minimum requirements
* **Borderline cases** can trigger ID verification fallback (Adaptive mode)
* Results documented with detailed **audit trail** for compliance
## Adaptive Age Estimation
For scenarios where precise age verification is critical, our platform offers adaptive age estimation with ID verification fallback. This approach provides a balance between user convenience and regulatory compliance.
### How Adaptive Age Estimation Works
The system first attempts to estimate the user's age using facial analysis. A confidence score is generated alongside the age estimate.
The estimated age is compared against your configured thresholds. Three outcomes:
| Outcome | Description |
| -------------- | ------------------------------------------------------------------- |
| **Clear Pass** | User is clearly above your required age threshold |
| **Clear Fail** | User is clearly below your required age threshold |
| **Borderline** | Estimated age falls within an uncertain range or has low confidence |
* **Clear pass/fail**: Verification completes immediately with the appropriate result
* **Borderline cases**: System automatically initiates an **ID verification flow**
* This ensures regulatory compliance while minimizing friction for most users
For borderline cases, the user provides a government-issued ID document:
* Document authenticity and age information are verified
* **Per-country age restrictions** from the ID Verification step are applied
* The system checks date of birth against the minimum/maximum age for the document's issuing country and region
Adaptive age estimation can be configured using [Adaptive Age Verification](/console/workflows) workflows type. You can define the borderline age thresholds that determine when ID verification is triggered. The final age-based approval or decline for borderline cases is then governed by the per-country age restrictions configured in the ID Verification step.
### Benefits of Adaptive Age Estimation
* **Reduced Friction**: Most users complete verification with just a selfie
* **Enhanced Compliance**: Uncertain cases receive thorough document verification with country-specific age rules
* **Cost Efficiency**: ID verification is only used when necessary
* **Customizable Risk Tolerance**: Adjust thresholds based on your regulatory requirements
* **Jurisdiction-Aware**: Automatically applies the correct age of majority based on the user's document issuing country and region
This approach is particularly valuable for age-gated services like online gaming, alcohol delivery, and adult content platforms where balancing user experience with regulatory compliance is essential.
## Model Performance and Statistics
Our age estimation technology is built on advanced deep learning models that deliver industry-leading accuracy. Below are key performance metrics based on extensive validation across diverse datasets.
### Accuracy Metrics
Metric
Value
Description
**Mean Absolute Error (MAE)**
3.5 years
Average difference between estimated and actual age across all age ranges
**Standard Deviation**
1.2 years
Variation in estimation error across the dataset
**Accuracy within ±5 years**
89%
Percentage of estimations within 5 years of actual age
**Accuracy within ±3 years**
76%
Percentage of estimations within 3 years of actual age
### Performance Across Demographics
Our models are trained on diverse datasets to ensure consistent performance across different demographic groups.
Demographic Group
MAE (years)
Confidence Score
-18 age range
1.5
High
18-25 age range
2.8
High
26-40 age range
3.2
High
41-60 age range
3.9
Medium-High
60+ age range
4.5
Medium
Our models are regularly retrained and validated to ensure consistent performance across changing visual conditions and demographic representation.
# Age Estimation Report
Source: https://docs.didit.me/core-technology/age-estimation/report-age-estimation
Parse Age Estimation responses: estimated age, passive liveness score, face quality metrics, decline thresholds, and warning codes. Pay-per-call from $0.10.
The Age Estimation report captures a model-predicted age (in years) for the largest face detected in an image, combined with a passive liveness check that confirms the subject is a real person rather than a photo, screen, or mask. The two checks always run together — Age Estimation never returns an age without a liveness score.
This page documents the JSON shape returned by both call paths: the standalone `POST /v3/age-estimation/` endpoint and the embedded `age_estimation` field on workflow liveness reports.
## Overview
Age Estimation has two delivery modes:
* **Standalone API.** `POST /v3/age-estimation/` accepts a single face image (`user_image`) and returns the predicted age and passive liveness score synchronously. See the [Age Estimation standalone API reference](/standalone-apis/age-estimation).
* **Embedded in a workflow.** Every workflow liveness report carries the predicted age under `age_estimation` — the value is always populated from the largest detected face. Age thresholds are only *enforced* (raising age warnings) on age-estimation flows: adaptive age verification workflows or `AGE_ESTIMATION` workflow nodes.
Each report contains:
* The overall `status` (`Approved`, `Declined`).
* The liveness `method` (always `PASSIVE` on the standalone API; `ACTIVE_3D` / `FLASHING` / `PASSIVE` in workflows).
* A liveness `score` (0–100; higher is more confident the subject is live; `null` is treated as `0` for threshold checks).
* The predicted `age_estimation` in years (float — for example `27.33`; `null` when no face age could be estimated).
* Standalone only: a `user_image` object with an `entities[]` array (one entry per detected face — `age`, `bbox`, `confidence`, `gender`) and the `best_angle` orientation.
* Workflow only: `reference_image`, `video_url`, `matches[]`, and the passive-liveness quality metrics `face_quality` (0–100%) and `face_luminance` (0–100%). The standalone response does **not** include these fields.
* A `warnings[]` array — risk events emitted during the check (see [Age Estimation warnings](/core-technology/age-estimation/warnings-age-estimation)).
The standalone decision is `Declined` whenever `warnings` is non-empty — `Approved` requires an empty array. Warnings fire when:
* The liveness `score` is at or below `face_liveness_score_decline_threshold` (default `30`) — `LOW_LIVENESS_SCORE`.
* The predicted age is below `age_estimation_decline_threshold` (default `18`) — `AGE_BELOW_MINIMUM`. Set the threshold to `0` to disable the age check.
* No face is detected (`NO_FACE_DETECTED`) or no face age could be estimated (`AGE_NOT_DETECTED`).
* The liveness model flags a presentation attack (`LIVENESS_FACE_ATTACK`).
## Where it appears in API responses
### Standalone API — `POST /v3/age-estimation/`
The standalone response wraps the result under a single top-level `age_estimation` object, alongside `request_id` and the echoed `vendor_data` / `metadata`. When `save_api_request=true` (the default), `request_id` is the persisted session id and the result can also be fetched later from the decision endpoint; when `false`, it is a one-off correlation UUID.
```json theme={null}
{
"request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"age_estimation": {
"status": "Approved",
"method": "PASSIVE",
"score": 97.5,
"age_estimation": 27.33,
"user_image": { "entities": [ /* ... */ ], "best_angle": 0 },
"warnings": []
},
"vendor_data": "user-123",
"metadata": { "flow": "age-gate" },
"created_at": "2026-06-12T02:24:11.512941+00:00"
}
```
### Workflow decision — `GET /v3/session/{session_id}/decision/`
In a workflow session, the predicted age is **embedded inside the liveness report — there is no separate age-estimation array**. The V3 decision endpoint returns liveness reports under the plural array key **`liveness_checks`** (`ReducedSessionV3DecisionSerializer.get_liveness_checks`), and each entry carries an `age_estimation` float — the estimated age of the largest detected face — or `null` if no age was computed (`LivenessV2Serializer.get_age_estimation`).
```json theme={null}
{
"session_id": "11111111-1111-1111-1111-111111111111",
"status": "Approved",
"features": ["AGE_ESTIMATION"],
"liveness_checks": [
{
"node_id": "feature_age_estimation_1",
"status": "Approved",
"method": "PASSIVE",
"score": 89.92,
"age_estimation": 24.3,
"...": "..."
}
]
}
```
Read the age from `liveness_checks[].age_estimation`. Persisted standalone calls (`save_api_request=true`) appear the same way on the decision endpoint, with `features` containing `AGE_ESTIMATION`.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#age-estimation) page. The standalone response shape is documented by `AgeEstimationResponseSerializer` and assembled in `AgeEstimationAPIView`; the workflow value is produced by `LivenessV3Serializer`.
```typescript theme={null}
interface AgeEstimationStandalone {
request_id: string; // Session id when save_api_request=true
age_estimation: {
status: "Approved" | "Declined";
method: "PASSIVE";
score: number | null; // Passive liveness score (0-100); null counts as 0
age_estimation: number | null; // Predicted age in years of the largest face (float)
user_image: {
entities: Array<{
age: number; // Estimated age of this face
bbox: [number, number, number, number]; // [x_min, y_min, x_max, y_max]
confidence: number; // Face-detection confidence (0-1)
gender: "male" | "female";
}>;
best_angle: number | null; // Rotation (0/90/180/270) with best detection
};
warnings: Warning[];
};
vendor_data: string | null; // Echoed from the request
metadata: object | null; // Echoed from the request
created_at: string; // ISO 8601
}
interface WorkflowLivenessWithAge {
node_id: string | null;
status: "Approved" | "Declined" | "In Review";
method: "ACTIVE_3D" | "FLASHING" | "PASSIVE";
score: number | null; // Liveness score (0-100)
reference_image: string; // Presigned URL
video_url: string | null; // Presigned URL (active liveness only)
age_estimation: number | null; // Predicted age in years (float)
face_quality: number | null; // 0-100% (passive liveness only)
face_luminance: number | null; // 0-100% (passive liveness only)
warnings: Warning[];
matches: FaceMatch[];
}
```
### Status values
Status strings come from the shared feature-status enum (see [Status enums](/reference/data-models#status-enums)). The standalone endpoint only ever returns:
| Status | Meaning |
| ---------- | ------------------------------------------------------------------------------------------------- |
| `Approved` | No warnings fired — the subject is live and the predicted age meets the configured threshold. |
| `Declined` | At least one warning fired — low liveness score, age below threshold, no face, no age, or attack. |
In workflow sessions, the liveness check can also resolve to `In Review` when a configurable risk fires with a review action (for example `POSSIBLE_DUPLICATED_FACE`). See the [status enum reference](/reference/data-models#status-enum-reference).
## Examples
### Approved — standalone, adult subject
```json theme={null}
{
"request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"age_estimation": {
"status": "Approved",
"method": "PASSIVE",
"score": 97.5,
"age_estimation": 27.33,
"user_image": {
"entities": [
{ "age": 27.33, "bbox": [40, 40, 100, 100], "confidence": 0.72, "gender": "male" }
],
"best_angle": 0
},
"warnings": []
},
"vendor_data": "user-123",
"metadata": { "flow": "age-gate" },
"created_at": "2026-06-12T02:24:11.512941+00:00"
}
```
### Declined — standalone, predicted age below threshold
```json theme={null}
{
"request_id": "b2c3d4e5-f6a7-8901-2345-67890abcdef0",
"age_estimation": {
"status": "Declined",
"method": "PASSIVE",
"score": 92.0,
"age_estimation": 15.8,
"user_image": {
"entities": [
{ "age": 15.8, "bbox": [55, 60, 110, 110], "confidence": 0.81, "gender": "female" }
],
"best_angle": 0
},
"warnings": [
{
"risk": "AGE_BELOW_MINIMUM",
"feature": "LIVENESS",
"additional_data": null,
"log_type": "error",
"short_description": "Age below minimum",
"long_description": "The age of the face is below the minimum age threshold for the application."
}
]
},
"vendor_data": "user-456",
"metadata": null,
"created_at": "2026-06-12T02:26:40.118332+00:00"
}
```
### Approved — embedded in a workflow liveness report
```json theme={null}
{
"node_id": "feature_age_estimation_1",
"status": "Approved",
"method": "PASSIVE",
"score": 89.92,
"reference_image": "https:///.../reference.jpg?signature=...",
"video_url": null,
"age_estimation": 24.3,
"matches": [],
"face_quality": 87.5,
"face_luminance": 54.12,
"warnings": []
}
```
## Security note
All image and video URLs returned in the response are pre-signed links with a limited validity window. Treat these as short-lived references — do not share them publicly, and re-call the decision endpoint to refresh expired URLs. As a best practice, store only the verification status and confidence values on your side rather than the underlying biometric media.
## Related
* [Age Estimation overview](/core-technology/age-estimation/overview) — feature behavior, pricing, decision logic.
* [Age Estimation warnings](/core-technology/age-estimation/warnings-age-estimation) — every risk code and decline trigger.
* [Standalone API reference](/standalone-apis/age-estimation) — `POST /v3/age-estimation/` request and response.
* [Data models — age estimation](/reference/data-models#age-estimation) — canonical field-by-field schema.
* [Passive liveness](/core-technology/liveness/overview) — the shared liveness model used in age-estimation responses.
# Age Estimation Warnings
Source: https://docs.didit.me/core-technology/age-estimation/warnings-age-estimation
Reference for every Age Estimation risk code — AGE_BELOW_MINIMUM, AGE_NOT_DETECTED, LOW_LIVENESS_SCORE, NO_FACE_DETECTED — with thresholds and actions.
The Age Estimation feature emits **warnings** whenever a risk signal fires on the combined age + passive liveness check: the predicted age is below your minimum, the age could not be computed, the subject failed liveness, no face was found, or a presentation attack was detected. This page lists every code, what triggers it, and how to configure the workflow response.
## Overview
Age Estimation always runs **two checks in one call** — a passive liveness test and an age regression model — so its warnings flow through the shared liveness pipeline and are tagged with feature `LIVENESS`. They appear on the report under `age_estimation.warnings[]` (standalone API) or `liveness_checks[].warnings[]` (embedded in a workflow). The warning object schema is documented under [Data models — Warning object](/reference/data-models#warning-object).
Producers:
* **Standalone API** — the endpoint generates the five warnings listed under [Decline triggers](#decline-triggers).
* **Workflows** — the shared liveness producers plus the age-specific checks. Age warnings only fire on age-estimation flows (adaptive age verification workflows or `AGE_ESTIMATION` workflow nodes) — a plain liveness step reports an estimated age but never raises age warnings.
Severities: the five auto-decline risks (`NO_FACE_DETECTED`, `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, `AGE_BELOW_MINIMUM`, `AGE_NOT_DETECTED`) are always `log_type: "error"` and force `Declined`; the remaining risks resolve to `error` / `warning` / `information` based on thresholds or the configured action.
## Decline triggers
On the standalone API, the decision is `Declined` whenever **any** warning fires:
* **Age below threshold** — predicted age strictly below `age_estimation_decline_threshold` (default `18`; `0` disables the age check). Raises `AGE_BELOW_MINIMUM`.
* **Age not computed** — no face age could be estimated (no detectable face, or no usable face for the age model). Raises `AGE_NOT_DETECTED`.
* **Liveness score at or below threshold** — `score <= face_liveness_score_decline_threshold` (default `30`); a `null` score counts as `0`. Raises `LOW_LIVENESS_SCORE`. Suppressed when `NO_FACE_DETECTED` fires — the two are mutually exclusive.
* **No face detected** — the liveness model found no face. Raises `NO_FACE_DETECTED`.
* **Presentation attack** — the liveness model flagged a screen, printed photo, mask, or other spoof. Raises `LIVENESS_FACE_ATTACK`.
In workflows, the age checks compare the truncated estimated age (whole years) against the configured thresholds:
* **With ID-verification fallback enabled** (`enable_id_verification_fallback`, default on): an age below `borderline_minimum_age_threshold` (default `18`) raises `AGE_BELOW_MINIMUM`; ages in the borderline band (up to `borderline_maximum_age_threshold`) continue to document verification instead of raising a warning, and `AGE_NOT_DETECTED` is never raised — an undetected age routes to document verification too.
* **With fallback disabled**: no estimated age raises `AGE_NOT_DETECTED`, and an age below `minimum_age_threshold` (default `18`) raises `AGE_BELOW_MINIMUM`.
## Configurable risks
The following risks let you tune thresholds or the action per workflow setting:
| Setting | Risk code | Configurable options |
| ------------------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Age thresholds | `AGE_BELOW_MINIMUM` | Standalone: `age_estimation_decline_threshold` (default `18`, `0` disables). Workflows: `minimum_age_threshold` / `borderline_minimum_age_threshold` (default `18`) plus the ID-verification fallback for borderline ages. |
| Low Liveness Score | `LOW_LIVENESS_SCORE` | Separate **Decline threshold** (default `30` → `error` + decline) and **Review threshold** (default `60` → `warning` + review) applied against the liveness score. |
| Possible Duplicated Face | `POSSIBLE_DUPLICATED_FACE` | Action on detection: `DECLINE` / `REVIEW` / `NO_ACTION` (default `NO_ACTION`). Workflow sessions only. |
| Multiple Faces | `MULTIPLE_FACES_DETECTED` | Action on detection: `DECLINE` / `REVIEW` / `NO_ACTION` (default `NO_ACTION`). Workflow passive liveness only. |
Workflow sessions also screen the face against your lists: `FACE_IN_BLOCKLIST` always auto-declines (`error`), while `POSSIBLE_FACE_IN_BLOCKLIST` moves the session to `In Review`.
## Warnings produced
| Tag | Severity (`log_type`) | Description |
| -------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AGE_BELOW_MINIMUM` | `error` — auto-decline | The estimated age is below the active threshold (standalone `age_estimation_decline_threshold`; workflow `minimum_age_threshold` / `borderline_minimum_age_threshold`; all default `18`). `additional_data` is always `null`. |
| `AGE_NOT_DETECTED` | `error` — auto-decline | No face age could be estimated. Always raised by the standalone API when the age is `null`; in workflows only when the ID-verification fallback is disabled. |
| `LOW_LIVENESS_SCORE` | `error` at/below decline threshold; `warning` at/below review threshold | The liveness score is at or below the configured threshold (standalone and workflow decline default `30`; workflow review default `60`), indicating potential use of a static photo, screen, or video. Not raised when `NO_FACE_DETECTED` fires. |
| `NO_FACE_DETECTED` | `error` — auto-decline | No face was detected in the submitted image. Re-prompt the user to center the camera with adequate lighting. |
| `LIVENESS_FACE_ATTACK` | `error` — auto-decline | The liveness model flagged a presentation attack (screen replay, printed photo, mask, deepfake). |
| `MULTIPLE_FACES_DETECTED` | Per configured action (default `information`, no action) | More than one face was found in the image; the largest face drives the result. Workflow passive liveness only — the standalone API does not raise it. |
| `FACE_IN_BLOCKLIST` | `error` — auto-decline | The detected face matches an entry in your face blocklist managed via the [Lists API](/management-api/lists/overview). Workflow sessions only. |
| `POSSIBLE_DUPLICATED_FACE` | Per configured action (default `information`, no action) | The detected face is significantly similar to a previously approved face on another session (matches sharing the same `vendor_data` are excluded). Workflow sessions only. |
## Example
```json theme={null}
{
"warnings": [
{
"risk": "LOW_LIVENESS_SCORE",
"feature": "LIVENESS",
"additional_data": null,
"log_type": "error",
"short_description": "Low liveness score",
"long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data."
},
{
"risk": "AGE_BELOW_MINIMUM",
"feature": "LIVENESS",
"additional_data": null,
"log_type": "error",
"short_description": "Age below minimum",
"long_description": "The age of the face is below the minimum age threshold for the application."
}
]
}
```
In workflow decision payloads (`liveness_checks[].warnings[]`), each warning also carries a `node_id` identifying the workflow node that raised it.
## Warning types
Each risk is assigned a severity based on your application's configuration. Severities fall into three categories:
## Related
* [Age Estimation overview](/core-technology/age-estimation/overview) — feature behavior, pricing, and decision logic.
* [Age Estimation report](/core-technology/age-estimation/report-age-estimation) — full response shape for both the standalone API and the embedded workflow value.
* [Standalone API reference](/standalone-apis/age-estimation) — `POST /v3/age-estimation/` request and response.
* [Passive liveness](/core-technology/liveness/overview) — the shared liveness model used in age-estimation responses.
* [Data models — age estimation](/reference/data-models#age-estimation) — canonical field-by-field schema.
# AML Match Score
Source: https://docs.didit.me/core-technology/aml-screening/aml-match-score
Learn how AML match scores measure identity confidence via name, DOB, and country matching. Tune weights and thresholds to cut false positives.
The **Match Score** is a weighted confidence metric that determines **how closely a potential AML match corresponds to the screened individual**. This score is used to classify individual matches as either **False Positives** or **Possible Matches** that require further review.
> ⚠️ **Important:** The Match Score determines **individual match classification**, NOT the final AML status. The final AML status (Approved/In Review/Declined) is determined by the [Risk Score](/core-technology/aml-screening/aml-risk-score) of non-false-positive matches.
## Overview
When screening a person against AML watchlists, each potential match receives a **match score from 0-100**. This score answers the question: **"Is this match actually the same person we're screening?"**
### Match Score vs Risk Score
| Aspect | Match Score | Risk Score |
| ------------- | ---------------------------------------------------- | ------------------------------------ |
| **Question** | Is this the same person? | How risky is this entity? |
| **Purpose** | Classify matches as False Positive vs Possible Match | Determine final AML status |
| **Factors** | Name, DOB, Country, Document Number | Country, Category, Criminal Records |
| **Threshold** | Match Score Threshold (default: 93) | Approve Threshold / Review Threshold |
***
## How Match Score Determines Review Status
Each match is classified based on its match score:
| Match Score | Review Status | Meaning |
| --------------------------------------- | ------------------ | ----------------------------------- |
| Score below Match Score Threshold | **False Positive** | Match is likely NOT the same person |
| Score at or above Match Score Threshold | **Unreviewed** | Match requires manual review |
> **Default threshold:** 93%
**Example:** With a threshold of 93:
* Match with score 85 → **False Positive** (auto-dismissed)
* Match with score 95 → **Unreviewed** (needs review, risk score determines urgency)
### Review Statuses Explained
All AML matches start with one of two initial statuses based on their match score:
| Status | Description | When Set |
| ------------------ | -------------------------------------------------------------------------------------------------- | ------------------------ |
| **False Positive** | The match is likely NOT the same person as the screened individual. Excluded from risk assessment. | Match score \< threshold |
| **Unreviewed** | The match is a possible match that requires manual review to confirm or dismiss. | Match score ≥ threshold |
After manual review, compliance officers can update the status to:
| Status | Description |
| ------------------- | ---------------------------------------------------- |
| **Confirmed Match** | The match has been verified as the same person. |
| **Inconclusive** | Unable to determine if the match is the same person. |
### **Tip:** You can change a match's review status in the Console by viewing the AML overview or clicking on a specific match to see its details.
***
## How the Match Score is Calculated
### Step 1: Base Score Calculation
The base score is calculated using three components with configurable weights:
```
Base Score = (Name Score × Name Weight) + (DOB Score × DOB Weight) + (Country Score × Country Weight)
```
| Component | Default Weight | Description |
| ----------------------- | -------------- | ------------------------------------------------------------ |
| **Name** | 60% | Fuzzy string similarity between screened name and match name |
| **Date of Birth** | 25% | Exact, partial (year only), or mismatch scoring |
| **Country/Nationality** | 15% | Comparison between screened nationality and match countries |
> **Note:** Weights must always sum to 100%.
### Step 2: Document Number "Golden Key" Logic
After calculating the base score, the document number is evaluated separately using special override logic:
| Scenario | Effect | Example |
| ----------------- | -------------------------- | --------------------------------------------- |
| **Match** | Override score to **100** | Passport numbers match exactly |
| **Neutral** | Keep base score unchanged | Different document types, or one side missing |
| **Hard Mismatch** | Apply penalty (-50 points) | Same document type but different values |
This approach recognizes that a matching document number is definitive proof of identity (the "Golden Key"), while mismatched document types shouldn't penalize the score.
***
## Component Scoring Details
### Name Similarity (0-100)
Name matching uses the **WRatio algorithm** from RapidFuzz, which is robust to:
* Typos and misspellings
* Word order differences ("John Smith" vs "Smith, John")
* Middle name variations ("Robert J. Smith" vs "Robert James Smith")
* Length differences
### Date of Birth Scoring
| Scenario | Score | Example |
| ------------------------------------ | --------------- | ----------------------------------------------------------------------------------------- |
| **Exact Match** | 100% | "1985-03-15" matches "1985-03-15" |
| **Year Match (match has year only)** | 100% | "1985-03-15" matches "1985" — full match because match only provides year-level precision |
| **Year Match (different day/month)** | 50% | "1985-03-15" matches "1985-06-20" — same year but dates differ |
| **Year Mismatch** | -100% (penalty) | "1985-03-15" does not match "1990-03-15" |
| **No Data** | 0% (neutral) | Either side missing DOB |
> **Important:** When the match only provides a year (e.g., "1974"), matching that year counts as a **full match** because that's all the information available to verify.
### Country/Nationality Scoring
| Scenario | Score | Example |
| -------------------- | -------------- | -------------------------------- |
| **Exact Match** | 100% | "ES" matches "Spain" or "ESP" |
| **No Data on Match** | 0% (neutral) | Match has no country information |
| **Mismatch** | -50% (penalty) | "ES" does not match "France" |
The system automatically converts between:
* ISO alpha-2 codes (ES)
* ISO alpha-3 codes (ESP)
* Full country names (Spain)
It also checks the `citizenship` field in addition to `countries`.
***
## Score Normalization (Re-weighting)
When data is missing from either the screened person or the match, the system uses **score normalization** to avoid penalizing for unavailable information.
### Example: Name-Only Screening
If you screen with only a name (no DOB or country):
| Original Weights | Normalized Weights |
| ---------------- | ---------------------------- |
| Name: 60% | Name: **100%** |
| DOB: 25% | DOB: 0% (not comparable) |
| Country: 15% | Country: 0% (not comparable) |
**Result:** The name score becomes the entire match score.
### Example: Missing Country on Match
If the match doesn't have country data but has DOB:
| Original Weights | Normalized Weights |
| ---------------- | ---------------------------- |
| Name: 60% | Name: **70.6%** |
| DOB: 25% | DOB: **29.4%** |
| Country: 15% | Country: 0% (not comparable) |
This ensures fair scoring regardless of data availability.
***
## Configuration Options
You can customize the match score calculation via the API or workflow settings:
### Match Score Threshold
| Setting | Default | Description |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Match Score Threshold | 93 | Matches below this are classified as **False Positive**. Matches at or above are **Unreviewed** (Possible Matches) that need review. |
### Weights (must sum to 100)
| Setting | Default | Description |
| -------------- | ------- | -------------------------------------- |
| Name Weight | 60 | Weight for name similarity (0-100) |
| DOB Weight | 25 | Weight for date of birth (0-100) |
| Country Weight | 15 | Weight for country/nationality (0-100) |
***
## API Request Example
```json theme={null}
{
"full_name": "John Doe",
"date_of_birth": "1990-01-01",
"nationality": "USA",
"document_number": "SAMPLE-DOC-12345",
"aml_name_weight": 60,
"aml_dob_weight": 25,
"aml_country_weight": 15,
"aml_match_score_threshold": 93
}
```
***
## Response: Score Breakdown
Each match in the response includes:
* `match_score` — The calculated match score (0-100)
* `risk_score` — The calculated risk score (0-100) - see [Risk Score](/core-technology/aml-screening/aml-risk-score)
* `review_status` — "False Positive" or "Unreviewed" based on match score threshold
* `score_breakdown` — Detailed breakdown of the match score calculation
```json theme={null}
{
"caption": "David Sanchez",
"match_score": 95,
"risk_score": 65.5,
"review_status": "Unreviewed",
"score_breakdown": {
"name_score": 95,
"name_weight": 60,
"name_weight_normalized": 70.59,
"name_contribution": 67.06,
"dob_score": 100,
"dob_weight": 25,
"dob_weight_normalized": 29.41,
"dob_contribution": 29.41,
"country_score": 0,
"country_weight": 15,
"country_weight_normalized": 0,
"country_contribution": 0,
"document_number_match_type": "NEUTRAL",
"document_number_effect": "No document number provided for screening",
"total_score": 95
}
}
```
***
## Complete Flow: From Match Score to Final AML Status
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ AML SCREENING FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. RETRIEVE MATCHES FROM WATCHLISTS │
│ └── All matches with score ≥ 75% are included │
│ │
│ 2. CALCULATE MATCH SCORE FOR EACH MATCH │
│ Match Score = (Name × W1) + (DOB × W2) + (Country × W3) │
│ + Golden Key document number logic │
│ │
│ 3. CLASSIFY MATCHES BY MATCH SCORE (Match Score Threshold = 93) │
│ ├── Match Score < 93 → FALSE POSITIVE (excluded from risk assessment) │
│ └── Match Score ≥ 93 → UNREVIEWED (included in risk assessment) │
│ │
│ 4. CALCULATE RISK SCORE FOR NON-FALSE-POSITIVE MATCHES │
│ Risk Score = (Country × 30%) + (Category × 50%) + (Criminal × 20%) │
│ │
│ 5. DETERMINE FINAL AML STATUS (based on highest risk score) │
│ ├── Highest Risk Score < approve_threshold → APPROVED │
│ ├── Highest Risk Score ≤ review_threshold → IN REVIEW │
│ └── Highest Risk Score > review_threshold → DECLINED │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
***
## Calculation Examples
### Example 1: Strong Match → Unreviewed
**Screened Data:**
* Name: "Robert J. Smith"
* DOB: "1985-03-15"
* Country: "US"
**Match Data:**
* Name: "Robert James Smith"
* DOB: "1985"
* Country: "United States"
**Calculation:**
1. Name Score: 90% (fuzzy match)
2. DOB Score: 100% (match only has year, year matches)
3. Country Score: 100% (exact match)
```
Match Score = (90 × 0.60) + (100 × 0.25) + (100 × 0.15)
= 54 + 25 + 15
= 94
```
**Result:** Match Score (94) ≥ threshold (93) → **Unreviewed**
→ Risk score will be calculated and used for final AML status
### Example 2: Weak Match → False Positive
**Screened Data:**
* Name: "John Smith"
* DOB: "1990-05-20"
* Country: "US"
**Match Data:**
* Name: "Johnny Smithson"
* DOB: "1975"
* Country: "Canada"
**Calculation:**
1. Name Score: 72% (weak fuzzy match)
2. DOB Score: -100% (year mismatch penalty)
3. Country Score: -50% (country mismatch penalty)
```
Match Score = (72 × 0.60) + (-100 × 0.25) + (-50 × 0.15)
= 43.2 - 25 - 7.5
= 10.7 → 11
```
**Result:** Match Score (11) \< threshold (93) → **False Positive**
→ This match is excluded from risk assessment
### Example 3: Golden Key Override
**Screened Data:**
* Name: "John D. Smith"
* Document Number: "A12345678"
**Match Data:**
* Name: "Jonathan David Smith"
* Document Number: "A12345678"
**Calculation:**
1. Base Score: \~70 (name fuzzy match)
2. Document Number: **MATCH** (exact match)
**Result:** Score overridden to **100** (Golden Key match) → **Unreviewed**
***
## Managing Review Status
After the initial classification, you can manually update each match's review status:
### In the Console
1. Navigate to the session's AML overview
2. Click on a specific match to view details
3. Use the status dropdown to change the review status:
* **Confirmed Match** — Verify the match is the same person
* **False Positive** — Mark the match as not matching
* **Inconclusive** — Unable to determine
***
## Best Practices
1. **Start with default threshold (93)** — This provides a good balance between catching true matches and filtering false positives.
2. **Lower threshold for high-risk scenarios** — If you need to be more cautious, lower the Match Score Threshold to catch more potential matches.
3. **Use document numbers when available** — They provide the strongest identity confirmation and can override low match scores.
4. **Review normalized weights** — Check the normalized weight fields in the score breakdown to understand how missing data affected the score.
5. **Monitor false positive rates** — If too many legitimate matches are being marked as false positives, lower the threshold.
# AML Risk Score
Source: https://docs.didit.me/core-technology/aml-screening/aml-risk-score
Understand how AML risk scores are calculated from country, category, and criminal record factors. Tune thresholds to automate AML decisions.
The **Risk Score** is a quantitative assessment that evaluates **how risky an AML hit entity is**. This score is used to determine the **final AML status** (Approved/In Review/Declined) based on the highest risk score among all non-false-positive hits.
> ⚠️ **Important:** The Risk Score determines the **final AML status**, NOT individual hit classification. Individual hits are first classified as False Positive vs Unreviewed using the [Match Score](/core-technology/aml-screening/aml-match-score).
## Overview
Each AML hit receives a **risk score from 0-100**, calculated by combining three key risk factors. This score answers the question: **"If this is a true match, how risky is this entity?"**
### Risk Score vs Match Score
| Aspect | Match Score | Risk Score |
| -------------- | --------------------------------------------- | ----------------------------------- |
| **Question** | Is this the same person? | How risky is this entity? |
| **Purpose** | Classify hits as False Positive vs Unreviewed | Determine final AML status |
| **Factors** | Name, DOB, Country, Document Number | Country, Category, Criminal Records |
| **Applied to** | All hits | Only non-false-positive hits |
***
## How Risk Score Determines Final AML Status
The final AML status is determined by the **highest risk score** among all hits that are NOT classified as False Positive:
| Highest Risk Score | Final AML Status |
| ------------------------------------------- | ---------------- |
| Score below Approve Threshold | **Approved** |
| Score between Approve and Review Thresholds | **In Review** |
| Score above Review Threshold | **Declined** |
> **Default thresholds:**
>
> * Approve Threshold: 80
> * Review Threshold: 100
**Example:** With default thresholds:
* Highest risk score = 50 → **Approved** (low risk)
* Highest risk score = 90 → **In Review** (medium risk, needs manual review)
* Highest risk score = 101 (impossible, but >100) → **Declined** (high risk)
***
## Risk Score Formula
The overall risk score is calculated using a weighted average:
```
Risk Score = (Country Score × 0.30) + (Category Score × 0.50) + (Criminal Score × 0.20)
```
| Component | Weight | Description |
| -------------------- | ------ | --------------------------------------------------- |
| **Country** | 30% | Geographic risk assessment based on AML/CFT factors |
| **Category** | 50% | Risk level based on the type of watchlist listing |
| **Criminal Records** | 20% | Risk from criminal history and convictions |
***
## Risk Levels
Based on the calculated risk score, entities are classified into three risk tiers:
| Risk Level | Score Range | Description |
| ------------------ | ----------- | ---------------------------------------------------- |
| 🟢 **Low Risk** | \< 30 | Minimal compliance concern, standard due diligence |
| 🟡 **Medium Risk** | 30 – 49 | Elevated concern, enhanced due diligence recommended |
| 🔴 **High Risk** | ≥ 50 | Significant concern, thorough investigation required |
***
## Component Scoring Details
### Country Score (30% Weight)
The country score reflects the inherent AML/CFT risk associated with a jurisdiction. Factors include:
* Money laundering and terrorist financing risks
* Compliance with FATF recommendations
* Presence of international sanctions
* Corruption perception indices
* Regulatory framework strength
**Scoring Range:** 0-100 (higher = more risk)
> **Note:** When a hit is associated with multiple countries, the **highest country score** is used in the calculation.
#### Country Risk Scores
| Country | Code | Risk Score | Risk Level |
| ------------------------------------- | ---- | ---------- | ---------- |
| 🇮🇷 Iran | `IR` | 81.66 | 🔴 High |
| 🇰🇵 North Korea | `KP` | 78.20 | 🔴 High |
| 🇲🇲 Myanmar | `MM` | 75.09 | 🔴 High |
| 🇦🇫 Afghanistan | `AF` | 74.63 | 🔴 High |
| 🇸🇾 Syria | `SY` | 74.49 | 🔴 High |
| 🇭🇹 Haiti | `HT` | 74.30 | 🔴 High |
| 🇨🇩 Democratic Republic of the Congo | `CD` | 71.66 | 🔴 High |
| 🇷🇺 Russia | `RU` | 71.25 | 🔴 High |
| 🇻🇪 Venezuela | `VE` | 71.09 | 🔴 High |
| 🇸🇸 South Sudan | `SS` | 70.77 | 🔴 High |
| 🇾🇪 Yemen | `YE` | 68.40 | 🔴 High |
| 🇱🇧 Lebanon | `LB` | 67.76 | 🔴 High |
| 🇸🇴 Somalia | `SO` | 65.38 | 🔴 High |
| 🇲🇱 Mali | `ML` | 62.78 | 🔴 High |
| 🇱🇾 Libya | `LY` | 61.96 | 🔴 High |
| 🇲🇿 Mozambique | `MZ` | 57.15 | 🔴 High |
| 🇧🇮 Burundi | `BI` | 56.48 | 🔴 High |
| 🇵🇭 Philippines | `PH` | 56.44 | 🔴 High |
| 🇬🇼 Guinea-Bissau | `GW` | 55.99 | 🔴 High |
| 🇰🇪 Kenya | `KE` | 55.49 | 🔴 High |
| 🇽🇰 Kosovo | `XK` | 55.49 | 🔴 High |
| 🇪🇷 Eritrea | `ER` | 54.72 | 🔴 High |
| 🇵🇸 State of Palestine | `PS` | 54.41 | 🔴 High |
| 🇳🇬 Nigeria | `NG` | 54.40 | 🔴 High |
| 🇻🇳 Vietnam | `VN` | 54.24 | 🔴 High |
| 🇮🇶 Iraq | `IQ` | 54.15 | 🔴 High |
| 🇹🇿 Tanzania | `TZ` | 53.22 | 🔴 High |
| 🇭🇷 Croatia | `HR` | 53.03 | 🔴 High |
| 🇩🇿 Algeria | `DZ` | 52.28 | 🔴 High |
| 🇵🇦 Panama | `PA` | 52.28 | 🔴 High |
| 🇨🇮 Cote D'Ivoire | `CI` | 51.61 | 🔴 High |
| 🇨🇲 Cameroon | `CM` | 51.35 | 🔴 High |
| 🇨🇫 Central African Republic | `CF` | 50.98 | 🔴 High |
| 🇸🇩 Sudan | `SD` | 49.52 | 🔴 High |
| 🇦🇴 Angola | `AO` | 49.47 | 🟡 Medium |
| 🇳🇮 Nicaragua | `NI` | 49.07 | 🟡 Medium |
| 🇹🇹 Trinidad and Tobago | `TT` | 48.45 | 🟡 Medium |
| 🇧🇫 Burkina Faso | `BF` | 47.94 | 🟡 Medium |
| 🇹🇷 Turkey | `TR` | 47.93 | 🟡 Medium |
| 🇻🇺 Vanuatu | `VU` | 47.93 | 🟡 Medium |
| 🇳🇪 Niger | `NE` | 47.02 | 🟡 Medium |
| 🇬🇹 Guatemala | `GT` | 47.01 | 🟡 Medium |
| 🇧🇦 Bosnia and Herzegovina | `BA` | 46.75 | 🟡 Medium |
| 🇺🇬 Uganda | `UG` | 46.67 | 🟡 Medium |
| 🇿🇦 South Africa | `ZA` | 46.60 | 🟡 Medium |
| 🇺🇦 Ukraine | `UA` | 46.43 | 🟡 Medium |
| 🇨🇳 China | `CN` | 45.92 | 🟡 Medium |
| 🇨🇺 Cuba | `CU` | 45.89 | 🟡 Medium |
| 🇧🇬 Bulgaria | `BG` | 45.70 | 🟡 Medium |
| 🇦🇪 United Arab Emirates | `AE` | 45.39 | 🟡 Medium |
| 🇿🇼 Zimbabwe | `ZW` | 45.37 | 🟡 Medium |
| 🇦🇱 Albania | `AL` | 45.34 | 🟡 Medium |
| 🇯🇲 Jamaica | `JM` | 45.11 | 🟡 Medium |
| 🇧🇧 Barbados | `BB` | 45.04 | 🟡 Medium |
| 🇬🇳 Guinea | `GN` | 44.80 | 🟡 Medium |
| 🇸🇽 Sint Maarten | `SX` | 44.79 | 🟡 Medium |
| 🇱🇷 Liberia | `LR` | 44.66 | 🟡 Medium |
| 🇷🇸 Serbia | `RS` | 44.26 | 🟡 Medium |
| 🇲🇪 Montenegro | `ME` | 43.47 | 🟡 Medium |
| 🇲🇨 Monaco | `MC` | 43.13 | 🟡 Medium |
| 🇪🇹 Ethiopia | `ET` | 42.85 | 🟡 Medium |
| 🇩🇯 Djibouti | `DJ` | 41.96 | 🟡 Medium |
| 🇳🇦 Namibia | `NA` | 41.83 | 🟡 Medium |
| 🇱🇦 Laos | `LA` | 41.73 | 🟡 Medium |
| 🇹🇳 Tunisia | `TN` | 41.35 | 🟡 Medium |
| 🇬🇮 Gibraltar | `GI` | 41.21 | 🟡 Medium |
| 🇧🇾 Belarus | `BY` | 41.17 | 🟡 Medium |
| 🇬🇾 Guyana | `GY` | 40.75 | 🟡 Medium |
| 🇰🇲 Comoros | `KM` | 40.50 | 🟡 Medium |
| 🇨🇼 Curacao | `CW` | 39.88 | 🟡 Medium |
| 🇵🇰 Pakistan | `PK` | 39.49 | 🟡 Medium |
| 🇸🇱 Sierra Leone | `SL` | 39.44 | 🟡 Medium |
| 🇸🇷 Suriname | `SR` | 39.38 | 🟡 Medium |
| 🇲🇰 North Macedonia | `MK` | 39.14 | 🟡 Medium |
| 🇦🇿 Azerbaijan | `AZ` | 38.69 | 🟡 Medium |
| 🇹🇩 Chad | `TD` | 38.54 | 🟡 Medium |
| 🇰🇮 Kiribati | `KI` | 37.94 | 🟡 Medium |
| 🇰🇭 Cambodia | `KH` | 37.90 | 🟡 Medium |
| 🇬🇶 Equatorial Guinea | `GQ` | 37.90 | 🟡 Medium |
| 🇸🇨 Seychelles | `SC` | 37.60 | 🟡 Medium |
| 🇲🇩 Moldova | `MD` | 37.54 | 🟡 Medium |
| 🇧🇿 Belize | `BZ` | 37.48 | 🟡 Medium |
| 🇭🇰 Hong Kong | `HK` | 37.47 | 🟡 Medium |
| 🇨🇴 Colombia | `CO` | 37.26 | 🟡 Medium |
| 🇵🇬 Papua New Guinea | `PG` | 37.20 | 🟡 Medium |
| 🇸🇿 Eswatini | `SZ` | 37.00 | 🟡 Medium |
| 🇦🇬 Antigua and Barbuda | `AG` | 36.74 | 🟡 Medium |
| 🇻🇬 British Virgin Islands | `VG` | 36.70 | 🟡 Medium |
| 🇸🇹 Sao Tome and Principe | `ST` | 36.64 | 🟡 Medium |
| 🇧🇯 Benin | `BJ` | 36.60 | 🟡 Medium |
| 🇲🇻 Maldives | `MV` | 36.20 | 🟡 Medium |
| 🇱🇨 Saint Lucia | `LC` | 36.18 | 🟡 Medium |
| 🇰🇳 Saint Kitts and Nevis | `KN` | 36.03 | 🟡 Medium |
| 🇫🇲 Micronesia | `FM` | 35.97 | 🟡 Medium |
| 🇹🇻 Tuvalu | `TV` | 35.94 | 🟡 Medium |
| 🇨🇬 Congo | `CG` | 35.92 | 🟡 Medium |
| 🇹🇯 Tajikistan | `TJ` | 35.90 | 🟡 Medium |
| 🇸🇳 Senegal | `SN` | 35.88 | 🟡 Medium |
| 🇵🇾 Paraguay | `PY` | 35.77 | 🟡 Medium |
| 🇹🇭 Thailand | `TH` | 35.71 | 🟡 Medium |
| 🇼🇸 Samoa | `WS` | 35.68 | 🟡 Medium |
| 🇬🇦 Gabon | `GA` | 35.54 | 🟡 Medium |
| 🇪🇨 Ecuador | `EC` | 35.29 | 🟡 Medium |
| 🇭🇳 Honduras | `HN` | 35.28 | 🟡 Medium |
| 🇸🇻 El Salvador | `SV` | 35.15 | 🟡 Medium |
| 🇲🇽 Mexico | `MX` | 35.01 | 🟡 Medium |
| 🇹🇬 Togo | `TG` | 35.01 | 🟡 Medium |
| 🇧🇷 Brazil | `BR` | 34.94 | 🟡 Medium |
| 🇬🇩 Grenada | `GD` | 34.87 | 🟡 Medium |
| 🇹🇲 Turkmenistan | `TM` | 34.75 | 🟡 Medium |
| 🇦🇷 Argentina | `AR` | 34.71 | 🟡 Medium |
| 🇲🇬 Madagascar | `MG` | 34.67 | 🟡 Medium |
| 🇸🇮 Slovenia | `SI` | 34.62 | 🟡 Medium |
| 🇳🇵 Nepal | `NP` | 34.60 | 🟡 Medium |
| 🇰🇬 Kyrgyzstan | `KG` | 34.54 | 🟡 Medium |
| 🇹🇱 East Timor | `TL` | 34.50 | 🟡 Medium |
| 🇮🇩 Indonesia | `ID` | 34.44 | 🟡 Medium |
| 🇬🇭 Ghana | `GH` | 34.18 | 🟡 Medium |
| 🇦🇮 Anguilla | `AI` | 34.17 | 🟡 Medium |
| 🇻🇨 Saint Vincent and the Grenadines | `VC` | 34.08 | 🟡 Medium |
| 🇨🇷 Costa Rica | `CR` | 33.84 | 🟡 Medium |
| 🇧🇴 Bolivia | `BO` | 33.75 | 🟡 Medium |
| 🇧🇸 The Bahamas | `BS` | 33.48 | 🟡 Medium |
| 🇩🇲 Dominica | `DM` | 33.16 | 🟡 Medium |
| 🇹🇴 Tonga | `TO` | 33.13 | 🟡 Medium |
| 🇪🇭 Western Sahara | `EH` | 33.03 | 🟡 Medium |
| 🇵🇪 Peru | `PE` | 32.99 | 🟡 Medium |
| 🇰🇾 Cayman Islands | `KY` | 32.89 | 🟡 Medium |
| 🇨🇾 Cyprus | `CY` | 32.74 | 🟡 Medium |
| 🇦🇲 Armenia | `AM` | 32.69 | 🟡 Medium |
| 🇨🇻 Cape Verde | `CV` | 32.61 | 🟡 Medium |
| 🇳🇺 Niue | `NU` | 32.38 | 🟡 Medium |
| 🇵🇼 Palau | `PW` | 32.18 | 🟡 Medium |
| 🇲🇾 Malaysia | `MY` | 32.16 | 🟡 Medium |
| 🇸🇧 Solomon Islands | `SB` | 32.08 | 🟡 Medium |
| 🇮🇳 India | `IN` | 32.02 | 🟡 Medium |
| 🇮🇱 Israel | `IL` | 31.77 | 🟡 Medium |
| 🇬🇲 The Gambia | `GM` | 31.70 | 🟡 Medium |
| 🇦🇼 Aruba | `AW` | 31.64 | 🟡 Medium |
| 🇲🇸 Montserrat | `MS` | 31.63 | 🟡 Medium |
| 🇧🇭 Bahrain | `BH` | 31.61 | 🟡 Medium |
| 🇰🇼 Kuwait | `KW` | 31.52 | 🟡 Medium |
| 🇱🇸 Lesotho | `LS` | 31.50 | 🟡 Medium |
| 🇩🇴 Dominican Republic | `DO` | 31.28 | 🟡 Medium |
| 🇪🇬 Egypt | `EG` | 31.17 | 🟡 Medium |
| 🇲🇭 Marshall Islands | `MH` | 31.11 | 🟡 Medium |
| 🇲🇷 Mauritania | `MR` | 31.08 | 🟡 Medium |
| 🇹🇨 Turks and Caicos | `TC` | 31.08 | 🟡 Medium |
| 🇧🇩 Bangladesh | `BD` | 30.99 | 🟡 Medium |
| 🇫🇯 Fiji | `FJ` | 30.88 | 🟡 Medium |
| 🇳🇷 Nauru | `NR` | 30.84 | 🟡 Medium |
| 🇺🇿 Uzbekistan | `UZ` | 30.78 | 🟡 Medium |
| 🇱🇰 Sri Lanka | `LK` | 30.67 | 🟡 Medium |
| 🇲🇦 Morocco | `MA` | 30.03 | 🟡 Medium |
| 🇯🇴 Jordan | `JO` | 29.73 | 🟢 Low |
| 🇲🇺 Mauritius | `MU` | 29.73 | 🟢 Low |
| 🇬🇪 Georgia | `GE` | 29.40 | 🟢 Low |
| 🇰🇿 Kazakhstan | `KZ` | 28.92 | 🟢 Low |
| 🇸🇰 Slovakia | `SK` | 28.88 | 🟢 Low |
| 🇲🇳 Mongolia | `MN` | 28.82 | 🟢 Low |
| 🇷🇼 Rwanda | `RW` | 28.82 | 🟢 Low |
| 🇲🇼 Malawi | `MW` | 28.76 | 🟢 Low |
| 🇭🇺 Hungary | `HU` | 28.70 | 🟢 Low |
| 🇷🇴 Romania | `RO` | 28.62 | 🟢 Low |
| 🇲🇹 Malta | `MT` | 28.46 | 🟢 Low |
| 🇸🇦 Saudi Arabia | `SA` | 28.32 | 🟢 Low |
| 🇿🇲 Zambia | `ZM` | 28.16 | 🟢 Low |
| 🇨🇰 Cook Islands | `CK` | 28.13 | 🟢 Low |
| 🇵🇱 Poland | `PL` | 28.13 | 🟢 Low |
| 🇴🇲 Oman | `OM` | 27.90 | 🟢 Low |
| 🇹🇼 Taiwan | `TW` | 27.90 | 🟢 Low |
| 🇨🇱 Chile | `CL` | 27.74 | 🟢 Low |
| 🇧🇹 Bhutan | `BT` | 27.70 | 🟢 Low |
| 🇮🇹 Italy | `IT` | 27.66 | 🟢 Low |
| 🇧🇼 Botswana | `BW` | 27.50 | 🟢 Low |
| 🇧🇶 Caribbean Netherlands | `BQ` | 27.23 | 🟢 Low |
| 🇳🇱 Netherlands | `NL` | 27.23 | 🟢 Low |
| 🇬🇷 Greece | `GR` | 26.70 | 🟢 Low |
| 🇮🇲 Isle of Man | `IM` | 26.69 | 🟢 Low |
| 🇻🇮 U.S. Virgin Islands | `VI` | 26.64 | 🟢 Low |
| 🇮🇪 Ireland | `IE` | 25.95 | 🟢 Low |
| 🇪🇸 Spain | `ES` | 25.71 | 🟢 Low |
| 🇨🇿 Czech Republic | `CZ` | 25.46 | 🟢 Low |
| 🇧🇪 Belgium | `BE` | 25.38 | 🟢 Low |
| 🇸🇬 Singapore | `SG` | 25.31 | 🟢 Low |
| 🇺🇸 United States | `US` | 25.22 | 🟢 Low |
| 🇱🇻 Latvia | `LV` | 25.16 | 🟢 Low |
| 🇲🇴 Macao | `MO` | 25.07 | 🟢 Low |
| 🇨🇦 Canada | `CA` | 24.95 | 🟢 Low |
| 🇬🇧 United Kingdom | `GB` | 24.79 | 🟢 Low |
| 🇶🇦 Qatar | `QA` | 24.70 | 🟢 Low |
| 🇨🇭 Switzerland | `CH` | 24.51 | 🟢 Low |
| 🇲🇵 Northern Mariana Islands | `MP` | 24.19 | 🟢 Low |
| 🇦🇸 American Samoa | `AS` | 24.10 | 🟢 Low |
| 🇬🇺 Guam | `GU` | 24.10 | 🟢 Low |
| 🇯🇵 Japan | `JP` | 23.94 | 🟢 Low |
| 🇩🇪 Germany | `DE` | 23.52 | 🟢 Low |
| 🇰🇷 South Korea | `KR` | 23.48 | 🟢 Low |
| 🇱🇺 Luxembourg | `LU` | 23.41 | 🟢 Low |
| 🇵🇹 Portugal | `PT` | 23.40 | 🟢 Low |
| 🇱🇮 Liechtenstein | `LI` | 23.23 | 🟢 Low |
| 🇦🇩 Andorra | `AD` | 22.55 | 🟢 Low |
| 🇦🇹 Austria | `AT` | 22.38 | 🟢 Low |
| 🇻🇦 Holy See | `VA` | 22.17 | 🟢 Low |
| 🇦🇺 Australia | `AU` | 22.12 | 🟢 Low |
| 🇵🇷 Puerto Rico | `PR` | 22.11 | 🟢 Low |
| 🇱🇹 Lithuania | `LT` | 22.08 | 🟢 Low |
| 🇺🇾 Uruguay | `UY` | 22.06 | 🟢 Low |
| 🇧🇲 Bermuda | `BM` | 21.85 | 🟢 Low |
| 🇫🇷 France | `FR` | 21.71 | 🟢 Low |
| 🇧🇳 Brunei | `BN` | 21.40 | 🟢 Low |
| 🇪🇪 Estonia | `EE` | 21.37 | 🟢 Low |
| 🇫🇰 Falkland Islands | `FK` | 21.20 | 🟢 Low |
| 🇸🇭 Saint Helena | `SH` | 21.20 | 🟢 Low |
| 🇬🇫 French Guiana | `GF` | 21.05 | 🟢 Low |
| 🇵🇫 French Polynesia | `PF` | 21.05 | 🟢 Low |
| 🇬🇵 Guadeloupe | `GP` | 21.05 | 🟢 Low |
| 🇲🇶 Martinique | `MQ` | 21.05 | 🟢 Low |
| 🇳🇨 New Caledonia | `NC` | 21.05 | 🟢 Low |
| 🇧🇱 Saint Barthelemy | `BL` | 21.05 | 🟢 Low |
| 🇲🇫 Saint Martin | `MF` | 21.05 | 🟢 Low |
| 🇵🇲 Saint Pierre and Miquelon | `PM` | 21.05 | 🟢 Low |
| 🇼🇫 Wallis and Futuna | `WF` | 21.05 | 🟢 Low |
| 🇮🇸 Iceland | `IS` | 21.04 | 🟢 Low |
| 🇬🇱 Greenland | `GL` | 19.43 | 🟢 Low |
| 🇳🇿 New Zealand | `NZ` | 19.19 | 🟢 Low |
| 🇹🇰 Tokelau | `TK` | 19.18 | 🟢 Low |
| 🇸🇲 San Marino | `SM` | 19.10 | 🟢 Low |
| 🇸🇪 Sweden | `SE` | 18.85 | 🟢 Low |
| 🇩🇰 Denmark | `DK` | 18.46 | 🟢 Low |
| 🇫🇴 Faroe Islands | `FO` | 18.46 | 🟢 Low |
| 🇫🇮 Finland | `FI` | 18.01 | 🟢 Low |
| 🇳🇴 Norway | `NO` | 17.62 | 🟢 Low |
233 countries · Risk scores based on AML/CFT factors
### Sanctions & Watchlists
Below is a table of the Sanctions & Watchlists included in our AML screening process:
| Publisher | Country | Category |
| -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | --------- |
| Financial Intelligence Agency Albania - Administrative Measures | 🇦🇱 Albania | Sanctions |
| Financial Intelligence Agency Albania - List of Announced Persons | 🇦🇱 Albania | Sanctions |
| Algerian Financial Intelligence Processing Unit - National List of Terrorist Person and Entities | 🇩🇿 Algeria | Sanctions |
| Financial Intelligence Unit of Andorra (UIFAND) - Designated Entities | 🇦🇩 Andorra | Sanctions |
| Autoritat Financera Andorrana (AFA) - Sanctions | 🇦🇩 Andorra | Sanctions |
| Ministry of Justice and Human Rights Argentina - Public Registry of Persons and Entities linked to acts of Terrorism and its Financing | 🇦🇷 Argentina | Sanctions |
| Central Bank of Armenia-Punishments | 🇦🇲 Armenia | Sanctions |
| Central Bank of Armenia - Domestic Lists (Sanctions) | 🇦🇲 Armenia | Sanctions |
| Central Bank of Aruba - Domestic List | 🇦🇼 Aruba | Sanctions |
| Central Bank of Aruba - Warnings | 🇦🇼 Aruba | Sanctions |
| Department of Foreign Affairs and Trade(DFAT) Australia - Consolidated Sanctions | 🇦🇺 Australia | Sanctions |
| Australian National Security - Listed Terrorist Organisations | 🇦🇺 Australia | Sanctions |
| Australian Government AUSTRAC - Infringement Notices | 🇦🇺 Australia | Sanctions |
| Austria Oesterreichische Nationalbank OENB Historical Sanctions | 🇦🇹 Austria | Sanctions |
| Azerbaijan Financial Monitoring Service - Targeted Financial Sanctions | 🇦🇿 Azerbaijan | Sanctions |
| Bangladesh Domestic Sanctions List | 🇧🇩 Bangladesh | Sanctions |
| Bangladesh Financial Intelligence Unit (BFIU) - Domestic Sanction List | 🇧🇩 Bangladesh | Sanctions |
| Financial Intelligence Unit of Barbados - Declaratory Orders | 🇧🇧 Barbados | Sanctions |
| Belgium Federal Public Service Finance - Consolidated List | 🇧🇪 Belgium | Sanctions |
| Belgium Federal Public Service Finance - National Financial Sanctions | 🇧🇪 Belgium | Sanctions |
| The Financial Intelligence Unit (FIU) of Belize - Consolidated Belize Sanctions List | 🇧🇿 Belize | Sanctions |
| Government of Brazil (Securities and Exchange Commission) - Judgments and Terms of Commitment | 🇧🇷 Brazil | Sanctions |
| Systems Center - Suspension | 🇧🇷 Brazil | Sanctions |
| Transparency Portal - Disreputable and Suspended Companies | 🇧🇷 Brazil | Sanctions |
| Transparency Portal - Prohibited Non-Profit Entities | 🇧🇷 Brazil | Sanctions |
| Transparency Portal - Companies Punished | 🇧🇷 Brazil | Sanctions |
| Transparency Portal - Leniency Agreements | 🇧🇷 Brazil | Sanctions |
| Government of Canada - Consolidated Canadian Autonomous Sanctions List | 🇨🇦 Canada | Sanctions |
| Government of Canada - Justice for Victims of Corrupt Foreign Officials Regulations | 🇨🇦 Canada | Sanctions |
| Government of Canada - Freezing Assets of Corrupt Foreign Officials (Tunisia) Regulations | 🇨🇦 Canada | Sanctions |
| Government of Canada - Freezing Assets of Corrupt Foreign Officials (Ukraine) Regulations | 🇨🇦 Canada | Sanctions |
| Government of Canada-Regulations Establishing a List of Entities | 🇨🇦 Canada | Sanctions |
| Government of Canada - Enforcement Notifications | 🇨🇦 Canada | Sanctions |
| Government of Canada - United Nations Resolutions on the Suppression of Terrorism | 🇨🇦 Canada | Sanctions |
| British Columbia Securities Commission (Disciplined List) | 🇨🇦 Canada | Sanctions |
| Cayman Islands Monetary Authority - Financial Sanctions Notices | 🇰🇾 Cayman Islands | Sanctions |
| Government of Jersey - Financial Sanctions Notice | 🇯🇪 Channel Islands | Sanctions |
| Comisión Para el Mercado Financiero (CMF) - Sanctions Resolutions Issued | 🇨🇱 Chile | Sanctions |
| Comisión Para el Mercado Financiero (CMF) - Insurance Market Sanctions | 🇨🇱 Chile | Sanctions |
| Unidad De Análisis Financiero - Enforceable Sanctions | 🇨🇱 Chile | Sanctions |
| China Securities Regulatory Commission-Administrative Penalties and Regulations | 🇨🇳 China | Sanctions |
| UEL MOFCOM Unreliable Entity List | 🇨🇳 China | Sanctions |
| AMV - Autorregulador Del Mercado De Valores - Sanctions | 🇨🇴 Colombia | Sanctions |
| AMV-Autorregulador Del Mercado De Valores | 🇨🇴 Colombia | Sanctions |
| Financial Superintendence of Colombia - Administrative Sanctions | 🇨🇴 Colombia | Sanctions |
| Financial Superintendence of Colombia - Report of Final Sanctions | 🇨🇴 Colombia | Sanctions |
| Cyprus Securities and Exchange Commission (CySEC) - Administrative Sanctions | 🇨🇾 Cyprus | Sanctions |
| Ministry of Foreign Affairs of the Czech Republic-National Sanction List | 🇨🇿 Czech Republic | Sanctions |
| Danish Immigration Service - Banned Religious Preachers | 🇩🇰 Denmark | Sanctions |
| Egyptian Anti-Money Laundering and Terrorism Financing Unit - Lists of terrorist entities and domestic terrorists | 🇪🇬 Egypt | Sanctions |
| Egyptian Anti-Money Laundering and Terrorism Financing Unit - Security Council lists | 🇪🇬 Egypt | Sanctions |
| Transparency Portal - Government Ethics Tribunal | 🇸🇻 El Salvador | Sanctions |
| National Frost Registry - Sanctions List | 🇫🇷 France | Sanctions |
| The Autorité des Marchés Financiers (AMF), France - Decisions of the Sanctions Commission | 🇫🇷 France | Sanctions |
| Commission Nationale de l'Informatique et des Libertés (CNIL) - Sanctions | 🇫🇷 France | Sanctions |
| LEPL Financial Monitoring Service of Georgia - List of Sanctioned Persons | 🇬🇪 Georgia | Sanctions |
| National Bank of Georgia - Sanctions | 🇬🇪 Georgia | Sanctions |
| Federal Ministry of the Interior Germany - Bans on Associations | 🇩🇪 Germany | Sanctions |
| Bank of Greece - Sanctions | 🇬🇷 Greece | Sanctions |
| National Tax and Customs Administration (NTCA) - Canceled tax numbers | 🇭🇺 Hungary | Sanctions |
| National Tax and Customs Administration (NTCA) - Announcement on decisions deleting the tax number | 🇭🇺 Hungary | Sanctions |
| Ministry of Home Affairs India - List of Banned Organisations/Individuals | 🇮🇳 India | Sanctions |
| Ministry of Home Affairs India - Unlawful Associations | 🇮🇳 India | Sanctions |
| Financial Transaction Reporting and Analysis Centre Indonesia - List of Suspected Terrorists and Terrorist Organizations (DTTOT) | 🇮🇩 Indonesia | Sanctions |
| African Development Bank-Debarred Entities | 🌐 International | Sanctions |
| Asian Development bank - Sanction List | 🌐 International | Sanctions |
| European Bank for Research and Development - Ineligible Entities | 🌐 International | Sanctions |
| European Commission - The EU Air Safety List | 🌐 International | Sanctions |
| European Union - Consolidated List of EU Financial Sanctions | 🌐 International | Sanctions |
| EUR-Lex - Decisions | 🌐 International | Sanctions |
| United Nation Security Council - Consolidated List | 🌐 International | Sanctions |
| The World Bank - Debarred Firms and Individuals | 🌐 International | Sanctions |
| Asian Infrastructure Investment Bank - Debarment List | 🌐 International | Sanctions |
| Inter-American Development Bank (IDB) - Sanctioned Individuals and Firms | 🌐 International | Sanctions |
| International Organization of Securities Commission - Investor Alerts Portal | 🌐 International | Sanctions |
| EU Consolidated Sanctions | 🌐 International | Sanctions |
| EU Sanctions - Consolidated List of Travel Bans | 🌐 International | Sanctions |
| Uyghur Human Rights Project - Sanctions | 🌐 International | Sanctions |
| Islamic Republic of Iran Ministry of Foreign Affairs Reference List of Sanctions - Consolidated | 🇮🇷 Iran | Sanctions |
| Islamic Republic of Iran Ministry of Foreign Affairs Reference List of Sanctions | 🇮🇷 Iran | Sanctions |
| Anti-Money Laundering and Countering Financing of Terrorism Office, Iraq - Fund Freezing List | 🇮🇶 Iraq | Sanctions |
| Anti-Money Laundering and Countering Financing of Terrorism Office, Iraq - Local List | 🇮🇶 Iraq | Sanctions |
| The Isle of Man Financial Services Authority (IOMFSA) - Disqualified Directors | 🇮🇲 Isle of Man | Sanctions |
| The Isle of Man Financial Services Authority (IOMFSA) - Enforcement Action | 🇮🇲 Isle of Man | Sanctions |
| National Bureau for Counter Terror Financing - NBCTF Israel - Associations and Terrorist Organizations | 🇮🇱 Israel | Sanctions |
| National Bureau for Counter Terror Financing - NBCTF Israel - Seizures | 🇮🇱 Israel | Sanctions |
| Ministry of Finance, Israel - Designated entities and individuals | 🇮🇱 Israel | Sanctions |
| National Bureau for Counter Terror Financing - NBCTF Israel - Declarations On Activists | 🇮🇱 Israel | Sanctions |
| Ministry of Finance, Japan - Economic Sanctions and Target Lists | 🇯🇵 Japan | Sanctions |
| The Anti Money Laundering and Counter Terrorist Financing Unit - National List | 🇯🇴 Jordan | Sanctions |
| The Anti Money Laundering and Counter Terrorist Financing Unit - Sanctions List | 🇯🇴 Jordan | Sanctions |
| Kyrgyzstan State Financial Intelligence Service - National List of Sanctions | 🇰🇬 Kyrgyzstan | Sanctions |
| Financial Intelligence Service Latvia - Subjects of Sanctions | 🇱🇻 Latvia | Sanctions |
| Financial Intelligence Service Latvia - Frozen economic resources | 🇱🇻 Latvia | Sanctions |
| Latvijas Banka - Sanctions | 🇱🇻 Latvia | Sanctions |
| Internal Security Forces, Lebanon - National Sanctions List | 🇱🇧 Lebanon | Sanctions |
| Lithuania Financial Crime Investigation Service - International financial sanctions | 🇱🇹 Lithuania | Sanctions |
| Commission de Surveillance du Secteur Financier - Administrative sanctions and Warnings | 🇱🇺 Luxembourg | Sanctions |
| Commissariat aux Assurances | 🇱🇺 Luxembourg | Sanctions |
| Malaysia Ministry of Home Affairs - List of Sanctioned Entities | 🇲🇾 Malaysia | Sanctions |
| Ministry of Home Affairs, Malaysia - Sanction List | 🇲🇾 Malaysia | Sanctions |
| Securities Commission Malaysia (SC) - AOB Sanctions | 🇲🇾 Malaysia | Sanctions |
| Government of Mexico - Consolidated Sanctions | 🇲🇽 Mexico | Sanctions |
| Bank of Mexico - Financial Sanctions | 🇲🇽 Mexico | Sanctions |
| Gouvernement Princier Monaco - National Funds Freezing List | 🇲🇨 Monaco | Sanctions |
| Financial Services Commission Montserrat - Sanctions | 🇲🇸 Montserrat | Sanctions |
| Government of the Netherlands - Dutch National Sanction List | 🇳🇱 Netherlands | Sanctions |
| New Zealand Police - Designated Terrorists | 🇳🇿 New Zealand | Sanctions |
| New Zealand Foreign Affairs & Trade - Russia Sanctions Register | 🇳🇿 New Zealand | Sanctions |
| Nigeria Sanctions Committee - NIGSAC | 🇳🇬 Nigeria | Sanctions |
| National Counter Terrorism Committee, Oman - Sanctions Local List | 🇴🇲 Oman | Sanctions |
| National Counter Terrorism Authority - Proscribed Organizations | 🇵🇰 Pakistan | Sanctions |
| National Counter Terrorism Authority (NACTA) Pakistan - Proscribed Persons | 🇵🇰 Pakistan | Sanctions |
| National Counter Terrorism Authority Pakistan - Denotified List | 🇵🇰 Pakistan | Sanctions |
| Palestine Monetary Authority - Local Freezing List | 🇵🇸 Palestine | Sanctions |
| Superintendence of the Securities Market (SMV) Peru - Sanctioned Companies | 🇵🇪 Peru | Sanctions |
| Office of the Comptroller General of the Republic of Peru - Registered and Current Sanctions | 🇵🇪 Peru | Sanctions |
| Superintendency of Banking, Insurance and AFP, Peru - Sanctions for Supervised Companies | 🇵🇪 Peru | Sanctions |
| Ministry of Internal Affairs and Administration - Sanction List | 🇵🇱 Poland | Sanctions |
| Ministry of Interior National Counter Terrorism Committee Qatar - Sanction List | 🇶🇦 Qatar | Sanctions |
| Federal Security Service of the Russian Federation - Terrorist Organizations | 🇷🇺 Russia | Sanctions |
| National Antiterrorism Committee, Russia - Unified Federal List of Organizations | 🇷🇺 Russia | Sanctions |
| Financial Services Regulatory Commission - St. Kitts Branch International Sanctions | 🇰🇳 Saint Kitts and Nevis | Sanctions |
| Permanent Counter Terrorism Committee, Presidency of State Security - National List Pursuant to UNSCR 1373 | 🇸🇦 Saudi Arabia | Sanctions |
| Ministry of Economy and Finance Senegal - Consolidated List | 🇸🇳 Senegal | Sanctions |
| Administration for the Prevention of Money Laundering (APML), Serbia - Lists of Designated Persons | 🇷🇸 Serbia | Sanctions |
| Singapore Statutes Online - Terrorists and Terrorist Entities | 🇸🇬 Singapore | Sanctions |
| Monetary Authority of Singapore - Investor Alert List | 🇸🇬 Singapore | Sanctions |
| National Anti-Money Laundering Committee NAMLC - Targeted Financial Sanction List | 🇸🇴 Somalia | Sanctions |
| Financial Intelligence Centre (FIC) - South Africa Sanctions | 🇿🇦 South Africa | Sanctions |
| South African Reserve Bank Prudential Authority - Administrative Sanctions | 🇿🇦 South Africa | Sanctions |
| Securities and Exchange Commission of Sri Lanka - Administrative Sanctions | 🇱🇰 Sri Lanka | Sanctions |
| State Secretariat for Economic Affairs, Switzerland - List of Sanctioned Individuals, Entities and Organizations | 🇨🇭 Switzerland | Sanctions |
| State Secretariat for Economic Affairs, Switzerland - Sanctions List | 🇨🇭 Switzerland | Sanctions |
| Republic of Turkey Ministry of Treasury and Finance - TF Current List | 🇹🇷 Türkiye | Sanctions |
| Anti-Money Laundering Division (AMLD), Taiwan - Sanction List of Ministry of Justice | 🇹🇼 Taiwan | Sanctions |
| Financial Supervisory Commission, R.O.C.(Taiwan) - Securities and Futures Bureau | 🇹🇼 Taiwan | Sanctions |
| National Bank of Tajikistan - List of individuals convicted of terrorist crimes | 🇹🇯 Tajikistan | Sanctions |
| National Bank of Tajikistan | 🇹🇯 Tajikistan | Sanctions |
| Anti-Money Laundering Office, Thailand - High Risk Persons List | 🇹🇭 Thailand | Sanctions |
| National Commission for the Fight Against Terrorism, Tunisia - National Sanction List | 🇹🇳 Tunisia | Sanctions |
| Attorney General's Chambers - Turks and Caicos Islands | 🇹🇨 Turks and Caicos | Sanctions |
| National Security and Defense Council of Ukraine - Personal Sanctions | 🇺🇦 Ukraine | Sanctions |
| The State Financial Monitoring Service of Ukraine - Persons Related to Terrorist Activity | 🇺🇦 Ukraine | Sanctions |
| Executive Office for Control and Non-Proliferation, UAE - Sanctions List | 🇦🇪 United Arab Emirates | Sanctions |
| United Arab Emirates- Local Terrorist List | 🇦🇪 United Arab Emirates | Sanctions |
| Government of United Kingdom - The UK Sanctions List | 🇬🇧 United Kingdom | Sanctions |
| Department of State Directorate of Defense Trade Controls, United States - Statutorily Debarred Parties | 🇺🇸 United States | Sanctions |
| Department of State Directorate of Defense Trade Controls, United States - Administratively Debarred Parties | 🇺🇸 United States | Sanctions |
| U.S Department of State - Nonproliferation Sanctions | 🇺🇸 United States | Sanctions |
| U.S Department of State - Cuba Restricted List | 🇺🇸 United States | Sanctions |
| U.S Department of State - Terrorist Exclusion List | 🇺🇸 United States | Sanctions |
| Department of State, United States - Public Listings | 🇺🇸 United States | Sanctions |
| Office of Foreign Assets Control (OFAC ) - SDN and Blocked Persons List | 🇺🇸 United States | Sanctions |
| Office of Foreign Assets Control (OFAC) - Non-SDN Sanction List | 🇺🇸 United States | Sanctions |
| Office of Foreign Assets Control, United States - Sanctions Programs and Country Information | 🇺🇸 United States | Sanctions |
| Bureau of Industry and Security, United States - The Denied Persons List | 🇺🇸 United States | Sanctions |
| Code of Federal Regulations, United States - Entity List | 🇺🇸 United States | Sanctions |
| Code of Federal Regulations, United States - Unverified List | 🇺🇸 United States | Sanctions |
| Code of Federal Regulations, United States - Military End User (MEU) List | 🇺🇸 United States | Sanctions |
| United States Bureau of Industry and Security - Military Intelligence End User List (MIEUL) | 🇺🇸 United States | Sanctions |
| Bureau of Industry and Security, United States - Charging Letters | 🇺🇸 United States | Sanctions |
| United States Bureau of Industry and Security - Commercial and Private Aircraft in Potential Violation | 🇺🇸 United States | Sanctions |
| Financial Crimes Enforcement Network (FINCEN) - Money Laundering Concerns List | 🇺🇸 United States | Sanctions |
| U.S. Department of Homeland Security - UFLPA Entity List | 🇺🇸 United States | Sanctions |
| International Trade Administration - Consolidated Screening List (CSL) | 🇺🇸 United States | Sanctions |
| United States Department of Defense - List of People's Republic of China (PRC) Military Companies | 🇺🇸 United States | Sanctions |
| Office of the Comptroller of the Currency, United States - OTS Enforcement Order Archive | 🇺🇸 United States | Sanctions |
| Michigan Department of Health and Human Services (MDHHS) - List of Sanctioned Providers | 🇺🇸 United States | Sanctions |
| Department for Combating Economic Crimes under the General Prosecutor's Office of the Republic of Uzbekistan - List of Natural Persons | 🇺🇿 Uzbekistan | Sanctions |
| Vendata - Sanctions and Sanctioned Database | 🇻🇪 Venezuela | Sanctions |
| Ministry of Public Security | 🇻🇳 Vietnam | Sanctions |
172 sources across 80 countries
### Regulatory Actions & Warnings
Below is a table of the Regulatory Actions & Warnings included in our AML screening process:
| Publisher | Country | Category |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------- |
| Special Inspector General for Afghanistan Reconstruction SIGAR | 🇦🇫 Afghanistan | Warnings |
| Algerian National Television Network (ENTV) | 🇩🇿 Algeria | Warnings |
| Andorra Financial Authority Sanctions to Supervised Entities | 🇦🇩 Andorra | Warnings |
| Angola Police Wanted List | 🇦🇴 Angola | Warnings |
| Anguilla Financial Services Commission | 🇦🇮 Anguilla | Warnings |
| Antigua and Barbuda Directorate of Offshore Gaming | 🇦🇬 Antigua and Barbuda | Warnings |
| National Value Commission | 🇦🇷 Argentina | Warnings |
| Central Bank of Armenia - Punishments | 🇦🇲 Armenia | Warnings |
| Office of The Representative on International Legal Matters | 🇦🇲 Armenia | Warnings |
| Australian Transaction Reports and Analysis Centre | 🇦🇺 Australia | Warnings |
| Austria Stock Exchange | 🇦🇹 Austria | Warnings |
| Ministry of Internal Affairs Azerbaijan-Wanted list | 🇦🇿 Azerbaijan | Warnings |
| National Health Regulatory Authority, Kingdom of Bahrain | 🇧🇭 Bahrain | Warnings |
| New Age | 🇧🇩 Bangladesh | Warnings |
| Barbados Judicial System | 🇧🇧 Barbados | Warnings |
| Ministry of Antimonopoly Regulation and Trade | 🇧🇾 Belarus | Warnings |
| Data Protection Authority- Market Court Judgements | 🇧🇪 Belgium | Warnings |
| The Financial Services Commission | 🇧🇿 Belize | Warnings |
| Ministry of Justice and Legislation - Wanted Persons | 🇧🇯 Benin | Warnings |
| Bermuda Monetary Authority Enforcement Actions | 🇧🇲 Bermuda | Warnings |
| Corporate Regulatory Authority of Bhutan | 🇧🇹 Bhutan | Warnings |
| Electronic Gazette of the Trade Register of Bolivia | 🇧🇴 Bolivia | Warnings |
| SAFF Portal | 🇧🇦 Bosnia and Herzegovina | Warnings |
| Non-Bank Financial Institutions Regulatory Authority | 🇧🇼 Botswana | Warnings |
| Government of Brazil (Securities and Exchange Commission) | 🇧🇷 Brazil | Warnings |
| Financial Services Commission | 🇻🇬 British Virgin Islands | Warnings |
| Brunei Darussalam Central Bank | 🇧🇳 Brunei | Warnings |
| Financial Supervision Commission (FSC) | 🇧🇬 Bulgaria | Warnings |
| Crisis Group | 🇧🇮 Burundi | Warnings |
| Security and Exchange Regulators of Cambodia | 🇰🇭 Cambodia | Warnings |
| Business in Cameroon | 🇨🇲 Cameroon | Warnings |
| Canadian Securities Administrators (Investor Alerts) | 🇨🇦 Canada | Warnings |
| Bank of Cape Verde | 🇨🇻 Cape Verde | Warnings |
| Dutch Caribbean Securities Exchange - Termination | 🇧🇶 Caribbean Netherlands | Warnings |
| Cayman Island Monetary Authority (CIMA) | 🇰🇾 Cayman Islands | Warnings |
| Fish Farming Expert | 🇨🇱 Chile | Warnings |
| China Securities Regulatory Commission - Administrative Penalties and Regulations | 🇨🇳 China | Warnings |
| Credit China (Jilin) | 🇨🇳 China | Warnings |
| La Guajira Hoy.com | 🇨🇴 Colombia | Warnings |
| Common Market for Eastern and Southern Africa (COMESA) | 🇰🇲 Comoros | Warnings |
| Ministry of Mining | 🇨🇬 Congo | Warnings |
| National Stock Exchange | 🇨🇷 Costa Rica | Warnings |
| The Agency for the Protection of Market Competition | 🇭🇷 Croatia | Warnings |
| Curaçao Chamber of Commerce | 🇨🇼 Curacao | Warnings |
| Cyprus Securities and Exchange Commission | 🇨🇾 Cyprus | Warnings |
| Czech Republic Office for the Protection of Competition | 🇨🇿 Czech Republic | Warnings |
| Public Procurement Regulatory Authority | 🇨🇩 Democratic Republic of the Congo | Warnings |
| The Gambling Authority | 🇩🇰 Denmark | Warnings |
| Dominica Government of Commonwealth | 🇩🇲 Dominica | Warnings |
| Ministry of Transportation and Public Works | 🇪🇨 Ecuador | Warnings |
| American Center for Democracy | 🇪🇬 Egypt | Warnings |
| El Salvador Crime Stoppers Most Wanted | 🇸🇻 El Salvador | Warnings |
| Estonia Police- Wanted Persons | 🇪🇪 Estonia | Warnings |
| Federal Services Regulatory Authority | 🇸🇿 Eswatini | Warnings |
| Ethiopian Federal Police | 🇪🇹 Ethiopia | Warnings |
| Finnish Competition and Consumer Authority | 🇫🇮 Finland | Warnings |
| Economist Intelligence | 🇬🇦 Gabon | Warnings |
| Registrar's General Department | 🇬🇭 Ghana | Warnings |
| Gibraltar Stock Exchange | 🇬🇮 Gibraltar | Warnings |
| Hellenic Capital Market Commission | 🇬🇷 Greece | Warnings |
| Guam Crime Stoppers | 🇬🇺 Guam | Warnings |
| Customs Anti Narcotic Unit- Wanted | 🇬🇾 Guyana | Warnings |
| Haitian National Police | 🇭🇹 Haiti | Warnings |
| ONCAE - Procurement Regulatory Office of the State of Honduras | 🇭🇳 Honduras | Warnings |
| Hong Kong Monetary Authority Fraudulent Websites | 🇭🇰 Hong Kong | Warnings |
| Hungarian Police | 🇭🇺 Hungary | Warnings |
| Supreme Court- Judgements | 🇮🇸 Iceland | Warnings |
| Badan Pengawas Perdagangan Berjangka Komoditi | 🇮🇩 Indonesia | Warnings |
| International Organization of Securities Commission (IOSCO) - Investor Alerts Portal | 🌐 International | Warnings |
| INTERPOL Yellow Notices | 🌐 International | Warnings |
| Financial Intelligence Agency | 🇮🇷 Iran | Warnings |
| Anadolu Ajansı | 🇮🇶 Iraq | Warnings |
| Ireland National police and Security Service | 🇮🇪 Ireland | Warnings |
| Isle of Man Courts | 🇮🇲 Isle of Man | Warnings |
| שיחה מקומית | 🇮🇱 Israel | Warnings |
| Italy Italian Companies and Exchange Commission Warnings | 🇮🇹 Italy | Warnings |
| Jamaica Constabulary Force-Missing Persons | 🇯🇲 Jamaica | Warnings |
| Japan Financial Service Agency - Illegal Financial Companies | 🇯🇵 Japan | Warnings |
| Jordan Securities Commission-Circulars and Regulatory Decisions | 🇯🇴 Jordan | Warnings |
| State Register of Industrial Property Objects of the Republic of Kazakhstan | 🇰🇿 Kazakhstan | Warnings |
| Ministry of Information, Communications, Transport and Tourism Development | 🇰🇮 Kiribati | Warnings |
| Supreme Court of Kosovo - Disciplinary Actions | 🇽🇰 Kosovo | Warnings |
| Kuwait Capital Market Authority-Restricted Persons | 🇰🇼 Kuwait | Warnings |
| Ministry of Internal Affairs of Kyrgyz Republic | 🇰🇬 Kyrgyzstan | Warnings |
| Latvia Financial and Capital Market Commission | 🇱🇻 Latvia | Warnings |
| Lesotho Mounted Police Service | 🇱🇸 Lesotho | Warnings |
| Central Bank of Liberia - Gazette | 🇱🇷 Liberia | Warnings |
| The University of Edinburgh | 🇱🇾 Libya | Warnings |
| Liechtenstein Gazette | 🇱🇮 Liechtenstein | Warnings |
| Lithuania Public Procurement Office | 🇱🇹 Lithuania | Warnings |
| Commission de Surveillance du Secteur Financier (CSSF) - Administrative sanctions and Warnings | 🇱🇺 Luxembourg | Warnings |
| Luxembourg Police | 🇱🇺 Luxembourg | Warnings |
| UNICEF Madagascar | 🇲🇬 Madagascar | Warnings |
| Anti-Corruption Bureau Malawi (Arrested for illegal activities) | 🇲🇼 Malawi | Warnings |
| Bank Negara Malaysia | 🇲🇾 Malaysia | Warnings |
| Ministry of Finance Maldives | 🇲🇻 Maldives | Warnings |
| Australian National Security Website | 🇲🇱 Mali | Warnings |
| Malta Transport Maritime | 🇲🇹 Malta | Warnings |
| Corporate and Business Registration Department | 🇲🇺 Mauritius | Warnings |
| Mexico Comisión Nacional de Seguros y Fianzas - Sanciones a personas que intermediaron sin autorización | 🇲🇽 Mexico | Warnings |
| Moldovan Ban List of Economic Operators | 🇲🇩 Moldova | Warnings |
| Monaco Fund Freezing Measures | 🇲🇨 Monaco | Warnings |
| Mongolian Stock Exchange - Ceased Trading | 🇲🇳 Mongolia | Warnings |
| Montenegro Agency for Prevention of Corruption | 🇲🇪 Montenegro | Warnings |
| Moroccan Capital Market Authority | 🇲🇦 Morocco | Warnings |
| The Bank of Mozambique | 🇲🇿 Mozambique | Warnings |
| Reporters Without Borders | 🇲🇲 Myanmar | Warnings |
| Ministry of Labour, Industrial Relation and Employment Creation | 🇳🇦 Namibia | Warnings |
| New Business Age-Fine | 🇳🇵 Nepal | Warnings |
| Authority for Financial Markets Netherlands-Warnings | 🇳🇱 Netherlands | Warnings |
| Michigan-Fine | 🇳🇨 New Caledonia | Warnings |
| Financial Market Authority New Zealand-Penalties | 🇳🇿 New Zealand | Warnings |
| Nicaraguan Stock Exchange | 🇳🇮 Nicaragua | Warnings |
| Nigeria Sexual Offender & Service Provider Database | 🇳🇬 Nigeria | Warnings |
| District Court for the Northern Mariana Islands | 🇲🇵 Northern Mariana Islands | Warnings |
| Dagsavisen Norway | 🇳🇴 Norway | Warnings |
| Pakistan Competition Authority Decisions | 🇵🇰 Pakistan | Warnings |
| Financial Institution Commission | 🇵🇼 Palau | Warnings |
| Palestine Monetary Authority - Local Freezing List | 🇵🇸 Palestine | Warnings |
| Superintendency of Securities Market of Panama | 🇵🇦 Panama | Warnings |
| Ministry of Interior Peru | 🇵🇪 Peru | Warnings |
| Philippines Anti-Money Laundering | 🇵🇭 Philippines | Warnings |
| Bird & Bird-Fine | 🇵🇱 Poland | Warnings |
| Portugal OSAE | 🇵🇹 Portugal | Warnings |
| Puerto Rico Police | 🇵🇷 Puerto Rico | Warnings |
| International Adviser-Fine | 🇶🇦 Qatar | Warnings |
| United Nations Security Council | 🇶🇦 Qatar | Warnings |
| Municipality of Gjorche Petrov Skopje - Gazette | 🇲🇰 Republic of North Macedonia | Warnings |
| The National Supervisory Authority For Personal Data Processing | 🇷🇴 Romania | Warnings |
| Rwanda Ministry of Foreign Affairs and International Cooperation | 🇷🇼 Rwanda | Warnings |
| Financial Services Regulatory Authority | 🇱🇨 Saint Lucia | Warnings |
| St. Vincent & Grenadines Financial Intelligence Unit Alerts | 🇻🇨 Saint Vincent And The Grenadines | Warnings |
| Samoa Police | 🇼🇸 Samoa | Warnings |
| Saudi Arabia Stock Exchange Unlisted Corporates Sukuk/Bonds | 🇸🇦 Saudi Arabia | Warnings |
| NDARINFO.COM Senegal | 🇸🇳 Senegal | Warnings |
| Serbia Securities and Exchange Commission Public Censure | 🇷🇸 Serbia | Warnings |
| Official Gazette of Republic of Seychelles | 🇸🇨 Seychelles | Warnings |
| Committee To Protect Journalists | 🇸🇱 Sierra Leone | Warnings |
| Monetary Authority of Singapore - Investor Alert List | 🇸🇬 Singapore | Warnings |
| Singapore Monetary Authority Enforcement Actions Companies | 🇸🇬 Singapore | Warnings |
| St. Maarten Chamber of Commerce | 🇸🇽 Sint Maarten | Warnings |
| Gambling Regulation Office - Court Orders | 🇸🇰 Slovakia | Warnings |
| Official Gazette of the Republic of Slovenia | 🇸🇮 Slovenia | Warnings |
| Wakaaladda Wararka Qaranka Soomaaliyeed | 🇸🇴 Somalia | Warnings |
| South Africa National Treasury | 🇿🇦 South Africa | Warnings |
| The Guardian-Most Wanted | 🇪🇸 Spain | Warnings |
| UTHR(J), SRI LANKA | 🇱🇰 Sri Lanka | Warnings |
| ISRAEL - ILSHABAK - Israel Security Agency - SHABAK | 🇵🇸 State of Palestine | Warnings |
| صوت الھامش | 🇸🇩 Sudan | Warnings |
| Aviation Analysis-Wanted person | 🇸🇷 Suriname | Warnings |
| Ústavný súd Slovenskej republiky | 🇸🇰 Slovakia | Warnings |
| ITALY - CONSOB - National Comm. Borsa | 🇮🇹 Italy | Warnings |
| Global Times | 🇸🇾 Syria | Warnings |
| Financial Supervisory Commission (Insurance Bureau)-Sanctions | 🇹🇼 Taiwan | Warnings |
| Ministry of Internal Affairs-Missing Person | 🇹🇯 Tajikistan | Warnings |
| Bank of Tanzania | 🇹🇿 Tanzania | Warnings |
| THAILAND - THAMLO-SANC - Thailand Anti Money Laundering Office - Sanctions | 🇹🇭 Thailand | Warnings |
| Compliance Commission of The Bahamas | 🇧🇸 The Bahamas | Warnings |
| Regulatory Authority for Electronic Communications & Posts | 🇹🇬 Togo | Warnings |
| Trinidad and Tobago Securities and Exchange Commission (TTSEC) | 🇹🇹 Trinidad and Tobago | Warnings |
| Ministry of border control and immigration services- Repatriations | 🇹🇨 Turks and Caicos | Warnings |
| Capital Market Authority (CMA) | 🇺🇬 Uganda | Warnings |
| Ukraine SFMS Blacklist | 🇺🇦 Ukraine | Warnings |
| Dubai International Finance Centre | 🇦🇪 United Arab Emirates | Warnings |
| Global Influence Operations Report | 🇬🇧 United Kingdom | Warnings |
| Georgia Department of Banking and Finance | 🇺🇸 United States | Warnings |
| The U.S. Department of State - Directorate of Defense Trade Controls (DDTC) - Administratively Debarred Parties | 🇺🇸 United States | Warnings |
| Ministry of Industry, Energy and Mining | 🇺🇾 Uruguay | Warnings |
| Ministry of Internal Affairs of the Republic of Uzbekistan | 🇺🇿 Uzbekistan | Warnings |
| Judiciary of Republic of Vanuatu | 🇻🇺 Vanuatu | Warnings |
| Institute for Works of Religion | 🇻🇦 Vatican City | Warnings |
| Tribunal of Supreme Court of Justice | 🇻🇪 Venezuela | Warnings |
| The Economic Times- Blacklisted firms | 🇻🇳 Vietnam | Warnings |
| Securities and Exchange Commission Zambia | 🇿🇲 Zambia | Warnings |
| Zimbabwe Legal Information Institute (ZimLII) | 🇿🇼 Zimbabwe | Warnings |
175 sources across 168 countries
# Biometric Authentication
Source: https://docs.didit.me/core-technology/biometric-auth/overview
Authenticate returning users with liveness + face match. No documents, sub-2-second passwordless verification. Pay-per-call $0.10.
Didit's Biometric Authentication solution provides a streamlined verification experience for returning users. This workflow can be configured to perform a liveness-only check for simple presence verification, or combine liveness with facial recognition for stronger identity confirmation against a stored portrait. This flexibility creates a frictionless experience while maintaining high security standards.
## Key Features
#### Fast Re-Verification
* No document scanning required
* Complete verification in seconds
* Reduces user friction and abandonment
#### Advanced Security
* Uses the same neural network architecture as Face Match 1:1
* Prevents account takeover attempts
* Includes liveness detection to prevent spoofing
#### Integration Flexibility
* Available as web-based
* Configurable matching thresholds
* Optional Device & IP Analysis for enhanced security
## How It Works
When you create a biometric authentication session for a workflow with **face matching**, Didit needs a reference face to compare the live selfie against.
You have two options:
* **Send `portrait_image`** in Base64 (max 2MB) from your own database. It always takes precedence when provided.
* **Omit `portrait_image`** and pass the user's `vendor_data`. Didit automatically reuses the face already stored for that user, resolved in this order:
1. Face captured during an **approved liveness check**
2. **ePassport chip photo** from an approved ID verification
3. **Portrait cropped from the ID document** of an approved ID verification
4. **Manually enrolled profile face** (Users API or Console upload)
```json theme={null}
{
"workflow_id": "11111111-2222-3333-4444-555555555555",
"vendor_data": "user-123",
"callback": "https://example.com/verification/callback",
"metadata": { "login_attempt": "2" }
}
```
Only faces from **approved** sessions (or faces you enrolled yourself) are ever reused. If you omit `portrait_image` and the user has no stored face, session creation fails with `400` and the message `No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first.` If you omit `vendor_data` as well, the `400` message asks you to send a `portrait_image` or a `vendor_data` with a stored face.
During the authentication process:
| Check | Description |
| ------------------------- | -------------------------------------------------------------- |
| **Liveness verification** | Prevents spoofing using Passive Liveness or 3D Action & Flash |
| **Image quality** | System evaluates lighting, positioning, and clarity |
| **Retry guidance** | Poor quality images are rejected with improvement instructions |
| **Real-time feedback** | User sees positioning guides for optimal capture |
The system processes the verification based on your workflow configuration:
If **Face Match is disabled** in the workflow, the system only confirms the user's liveness; no reference face is needed and `portrait_image` is not required. A successful check results in an **approved** authentication, useful for simple presence verification.
If **Face Match is enabled** in the workflow (reference face from `portrait_image` or the user's stored face):
1. System performs the **liveness check** first
2. If liveness passes, compares the new selfie with the reference face
3. A **similarity score (0–100%)** is generated
4. Score above your configured threshold → **Approved**
5. Score below threshold → **Declined**
Results are available via **API response**, **Business Console**, and **webhooks**.
# Biometric Authentication Report
Source: https://docs.didit.me/core-technology/biometric-auth/report-biometric-authentication
Parse Biometric Authentication responses with liveness and optional face match against a stored portrait, returned via liveness_checks and face_matches.
The Biometric Authentication report is how Didit reports back a returning-user re-verification. The workflow runs a fresh liveness check and, when Face Match is enabled in the workflow, a 1:1 face match against the reference face resolved at session creation (the `portrait_image` you supplied, or the user's stored face looked up by `vendor_data`). Both checks appear as standard feature items in the V3 session decision response.
## Overview
Biometric Authentication is a workflow type (`workflow_type: "biometric_authentication"`), not a separate response object. When the workflow runs it emits:
* One **liveness check** that captures a live selfie and scores it for spoofing.
* One **face match** comparing the captured face against the reference face resolved at session creation (only when Face Match is enabled in the workflow).
Whenever the workflow includes Face Match, session creation needs a reference face. Send `portrait_image` (base64-encoded image, max 2MB), or omit it and Didit reuses the face already stored for the `vendor_data` user: an approved liveness face first, then the ePassport chip photo, then the ID document portrait, then a manually enrolled profile face. If neither `portrait_image` nor a stored face is available, session creation returns `400` with `{"portrait_image": "No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first."}`. In both cases the resolved reference is recorded under the new session, so the face-match item's `source_image_session_id` is set to that session's own `session_id` (it tells you the reference came from the session's stored portrait rather than from a document portrait captured in the same session, which would leave the field `null`).
## Where it appears
| Decision response key | Plural | Item schema |
| --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- |
| `liveness_checks[]` | One per liveness step (usually 1 in biometric auth) | [data-models#liveness-check](/reference/data-models#liveness-check) |
| `face_matches[]` | One per face-match step (`null` in liveness-only workflows) | [data-models#face-match](/reference/data-models#face-match) |
Both arrays are returned by [`GET /v3/session/{sessionId}/decision/`](/sessions-api/retrieve-session) for sessions whose workflow type is `biometric_authentication`. They are always arrays — never singular objects — and each is `null` until its step has produced data. Each item carries a `node_id` so that multi-instance workflows can disambiguate steps.
## Schema reference
The full field list is defined once on the data-models page so every report that surfaces these features shares one source of truth:
* [Liveness item schema](/reference/data-models#liveness-check) — `status`, `method`, `score`, `reference_image`, `video_url`, `age_estimation`, `matches[]`, `face_quality`, `face_luminance`, `warnings[]`, `node_id`.
* [Face match item schema](/reference/data-models#face-match) — `status`, `score`, `source_image_session_id`, `source_image`, `target_image`, `warnings[]`, `node_id`.
* [Warning entry shape](/reference/data-models#warning-object) — `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`.
## Status values
Both `liveness_checks[].status` and `face_matches[].status` use the shared feature lifecycle ([feature-level statuses](/reference/data-models#feature-level-statuses)):
| Status | Meaning |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The step has not produced a final result yet (user has not completed it, or dropped off). Before a step starts at all, its array is simply `null` in the decision response. |
| `Approved` | The check passed all configured thresholds. |
| `In Review` | The score fell in the review band, or a configurable warning was set to review. |
| `Declined` | The score fell at or below the decline threshold, or an auto-decline warning fired (face attack, no face detected, face blocklisted, no reference image for face match). |
| `Resub Requested` | A reviewer asked the user to retry this step. |
The overall session `status` aggregates both items: any feature in `Declined` declines the session, any in `In Review` puts the session in `In Review`, otherwise `Approved` (after every feature finishes).
For threshold behavior and the full list of warning codes, see [Biometric Authentication warnings](/core-technology/biometric-auth/warnings-biometric-authentication).
## Example — liveness + face match (approved)
Examples are abridged: the full decision envelope also carries `session_url`, `metadata`, `callback`, `reviews`, `contact_details`, `expected_details`, `environment`, `created_at`, and `expires_at`.
```json theme={null}
{
"session_id": "11111111-2222-3333-4444-555555555555",
"session_kind": "user",
"session_number": 1234,
"status": "Approved",
"workflow_id": "018e7b2c-5555-7777-9999-aaaaaaaaaaaa",
"features": ["LIVENESS", "FACE_MATCH"],
"vendor_data": "user-123",
"liveness_checks": [
{
"node_id": "feature_liveness",
"status": "Approved",
"method": "PASSIVE",
"score": 89.92,
"reference_image": "https:///.../reference.jpg",
"video_url": "https:///.../video.mp4",
"age_estimation": null,
"matches": [],
"face_quality": 92.4,
"face_luminance": 58.1,
"warnings": []
}
],
"face_matches": [
{
"node_id": "feature_face_match",
"status": "Approved",
"score": 88.47,
"source_image_session_id": "11111111-2222-3333-4444-555555555555",
"source_image": "https:///.../source.jpg",
"target_image": "https:///.../target.jpg",
"warnings": []
}
],
"id_verifications": null,
"nfc_verifications": null,
"poa_verifications": null,
"phone_verifications": null,
"email_verifications": null,
"aml_screenings": null
}
```
## Example — liveness-only (Face Match disabled in the workflow)
```json theme={null}
{
"session_id": "22222222-3333-4444-5555-666666666666",
"session_kind": "user",
"status": "Approved",
"workflow_id": "018e7b2c-6666-7777-9999-bbbbbbbbbbbb",
"features": ["LIVENESS"],
"vendor_data": "user-456",
"liveness_checks": [
{
"node_id": "feature_liveness",
"status": "Approved",
"method": "ACTIVE_3D",
"score": 96.10,
"reference_image": "https:///.../reference.jpg",
"video_url": "https:///.../video.mp4",
"age_estimation": null,
"matches": [],
"face_quality": null,
"face_luminance": null,
"warnings": []
}
],
"face_matches": null
}
```
Liveness-only mode is a workflow configuration: when Face Match is disabled, no reference face is needed at session creation and `face_matches` stays `null`. When Face Match **is** enabled, omitting `portrait_image` does not fall back to liveness-only: Didit reuses the face stored for the `vendor_data` user, and if no stored face exists session creation fails with `400`.
## Example — face-match declined on low similarity
```json theme={null}
{
"session_id": "33333333-4444-5555-6666-777777777777",
"status": "Declined",
"workflow_id": "018e7b2c-5555-7777-9999-aaaaaaaaaaaa",
"liveness_checks": [
{
"node_id": "feature_liveness",
"status": "Approved",
"method": "PASSIVE",
"score": 91.20,
"reference_image": "https:///.../reference.jpg",
"video_url": "https:///.../video.mp4",
"age_estimation": null,
"matches": [],
"face_quality": 90.15,
"face_luminance": 55.30,
"warnings": []
}
],
"face_matches": [
{
"node_id": "feature_face_match",
"status": "Declined",
"score": 41.18,
"source_image_session_id": "33333333-4444-5555-6666-777777777777",
"source_image": "https:///.../source.jpg",
"target_image": "https:///.../target.jpg",
"warnings": [
{
"feature": "FACEMATCH",
"risk": "LOW_FACE_MATCH_SIMILARITY",
"additional_data": null,
"log_type": "error",
"short_description": "Low face match similarity",
"long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"node_id": "feature_face_match"
}
]
}
]
}
```
## Security note
`reference_image`, `video_url`, `source_image`, and `target_image` are signed URLs that expire after a limited validity window (4 hours by default). They point at biometric data — do not cache or surface them publicly. Store the verification result (status, score, `source_image_session_id`) on your side; re-fetch the decision endpoint if you need fresh URLs.
## Related
Warning codes, thresholds, and automatic decline conditions.
Full field reference for `liveness_checks[]` items.
Full field reference for `face_matches[]` items.
# Biometric Authentication Warnings
Source: https://docs.didit.me/core-technology/biometric-auth/warnings-biometric-authentication
Handle biometric auth warnings: LOW_LIVENESS_SCORE, face attacks, blocklist matches, and low face-match similarity. Configure thresholds and decline actions.
Biometric Authentication combines a liveness check with an optional 1:1 face match against the reference face resolved at session creation (the supplied `portrait_image`, or the user's stored face looked up by `vendor_data`). Each step emits its own warnings in the V3 decision payload. This page lists every code, where it appears, and how thresholds translate into a final session status.
## Overview
When a biometric authentication session runs, Didit:
* Captures a live selfie using 3D Action, Flash, or Passive Liveness (`method`: `ACTIVE_3D`, `FLASHING`, or `PASSIVE`).
* Extracts the largest face from the capture and scores its liveness.
* If the workflow includes Face Match, compares the captured face against the session's reference face (supplied `portrait_image` or the user's stored face).
* Writes the outcome of each step plus any warnings into the session decision.
Warnings keep the shared [warning object](/reference/data-models#warning-object) shape used across the platform: `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, and `node_id`.
## Where warnings appear
| Decision response key | Feature tag on the warning | When it fires |
| ------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `liveness_checks[].warnings[]` | `LIVENESS` | Liveness scoring, face quality, luminance, multiple-faces, and blocklist / allowlist / duplicate-face checks. |
| `face_matches[].warnings[]` | `FACEMATCH` | Face match against the session's reference face (supplied `portrait_image` or the user's stored face). |
In a typical biometric-auth session there is one item in each array. In multi-instance workflows the `node_id` on each warning disambiguates which step produced it.
## Automatic decline conditions
The following warnings always decline their step (`log_type: "error"`) regardless of how you configured the workflow:
| Risk | Feature | Why it auto-declines |
| ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
| `FACE_IN_BLOCKLIST` | `LIVENESS` | The face matched an entry in your face blocklist. |
| `NO_FACE_DETECTED` | `LIVENESS` | No face was located during the liveness capture, or the user exhausted the capture retry attempts. |
| `LIVENESS_FACE_ATTACK` | `LIVENESS` | A presentation attack was detected (mask, screen replay, deepfake-class spoof). |
| `NO_REFERENCE_IMAGE` | `FACEMATCH` | No reference image was available to run the comparison. When this fires, `LOW_FACE_MATCH_SIMILARITY` is suppressed. |
When `NO_FACE_DETECTED` fires, `LOW_LIVENESS_SCORE` is suppressed (there is no face to score).
## Configurable thresholds and actions
These warnings are scored and then mapped to a status through thresholds and actions you set per workflow:
| Risk | Feature | Configurable behavior |
| ---------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LOW_LIVENESS_SCORE` | `LIVENESS` | Score at or below the review threshold → `In Review`; at or below the decline threshold → `Declined`. |
| `LOW_FACE_MATCH_SIMILARITY` | `FACEMATCH` | Score at or below the review threshold (default 70) → `In Review`; at or below the decline threshold (default 50) → `Declined`. |
| `LOW_FACE_QUALITY` | `LIVENESS` | Review threshold (default 15) → `In Review`; decline threshold (default 0, i.e. decline disabled) → `Declined`. Passive Liveness only. |
| `LOW_FACE_LUMINANCE` | `LIVENESS` | Below the minimum luminance threshold (default 20): No action / Review (default) / Decline. Passive Liveness only. |
| `HIGH_FACE_LUMINANCE` | `LIVENESS` | Above the maximum luminance threshold (default 80): No action / Review (default) / Decline. Passive Liveness only. |
| `MULTIPLE_FACES_DETECTED` | `LIVENESS` | No action (default) / Review / Decline when more than one face is captured. Passive Liveness only. |
| `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` | `LIVENESS` | One shared duplicate-face action: No action (default) / Review / Decline for matches against other users' approved or imported faces. |
| `DUPLICATED_FACE_NAME_MISMATCH` | `LIVENESS` | Own action (`face_liveness_duplicated_face_name_mismatch_action`): No action / Review (default) / Decline. Emitted alongside `DUPLICATED_FACE` when the matched session's identity does not match this session's resolved name — see [Liveness warnings](/core-technology/liveness/warnings-liveness#cross-session-face-matching). |
`POSSIBLE_FACE_IN_BLOCKLIST` (`LIVENESS`) is **not** configurable — a lower-confidence blocklist match is always routed to Review (`log_type: "warning"`).
## Capture retries
Failures driven only by fixable capture-quality risks (low score, no face, quality, luminance, multiple faces — and low similarity on face match) enter a retry loop before the status sticks: the user gets up to `face_liveness_max_attempts` / `face_match_max_attempts` total attempts (default 3, configurable 2–5 per workflow node). When the budget is exhausted, the last attempt's computed status applies and `LIVENESS_MAX_ATTEMPTS_EXCEEDED` or `FACE_MATCH_MAX_ATTEMPTS_EXCEEDED` is logged as `information` to record it.
## Liveness warnings
The API returns these exact `short_description` and `long_description` strings:
| `risk` | `short_description` | `long_description` |
| ------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LOW_LIVENESS_SCORE` | Low liveness score | The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data. |
| `NO_FACE_DETECTED` | No Face Detected in liveness | The system couldn't identify a face during the liveness check, which may be due to poor image quality, improper positioning, or technical issues. |
| `LIVENESS_FACE_ATTACK` | Liveness Face Attack | The system detected a potential attempt to bypass the liveness check. |
| `FACE_IN_BLOCKLIST` | Face in blocklist | The system identified a face in the blocklist, which means the face is not allowed to be verified. |
| `POSSIBLE_FACE_IN_BLOCKLIST` | Possible face in blocklist | The system identified a possible face in the blocklist, which means the face is not allowed to be verified. |
| `FACE_IN_ALLOWLIST` | Face in allowlist | The face matched the application's face allowlist, so duplicate-face actions were skipped for this signal. |
| `POSSIBLE_FACE_IN_ALLOWLIST` | Possible face in allowlist | The face possibly matched the application's face allowlist, so possible duplicate-face actions were skipped for this signal. |
| `DUPLICATED_FACE` | Duplicated face from other approved session | The system identified a duplicated face from another approved session, requiring further investigation. |
| `POSSIBLE_DUPLICATED_FACE` | Possible duplicated face from other approved session | The system identified a possible duplicate face from another approved session, requiring further investigation. |
| `DUPLICATED_FACE_NAME_MISMATCH` | Duplicated face under a different name | The same face was already approved on another session, but under a different name. The identity behind the duplicate does not match the identity declared on this session, which is a strong indicator of identity fraud. |
| `MULTIPLE_FACES_DETECTED` *(Passive only)* | Multiple faces detected | Multiple faces were detected in the liveness image. The system uses the largest face for liveness verification and face comparison, but the presence of multiple faces may require additional review. |
| `LOW_FACE_QUALITY` *(Passive only)* | Low face quality | The facial image quality is below the acceptable threshold, which may affect the reliability of liveness detection. This could be due to camera resolution, focus, or compression artifacts. |
| `LOW_FACE_LUMINANCE` *(Passive only)* | Low face luminance | The facial image is too dark, which may affect the accuracy of liveness detection. Better lighting conditions are recommended. |
| `HIGH_FACE_LUMINANCE` *(Passive only)* | High face luminance | The facial image is too bright or overexposed, which may affect the accuracy of liveness detection. Reduced lighting or avoiding direct light is recommended. |
| `LIVENESS_MAX_ATTEMPTS_EXCEEDED` | Maximum liveness attempts exceeded | The maximum number of liveness capture attempts has been reached. The last attempt's computed status (decline or review) has been applied. |
Blocklist, allowlist, and duplicate matches carry `additional_data` with the matched session (`blocklisted_session_id` / `allowlisted_session_id` / `duplicated_session_id`, the matching `*_session_number`, and `api_service`). Only one of those six codes appears per report. `DUPLICATED_FACE_NAME_MISMATCH` is the exception: it is emitted **in addition to** `DUPLICATED_FACE`, never instead of it, when the matched session's identity does not match this session's resolved name. Its `additional_data` adds `name_match_score` to the same duplicate-session fields.
## Face-match warnings
| `risk` | `short_description` | `long_description` |
| ---------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `LOW_FACE_MATCH_SIMILARITY` | Low face match similarity | The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch. |
| `NO_REFERENCE_IMAGE` | No source image found for performing face match | A reference image for facial comparison is missing, preventing the system from completing the face matching process. |
| `FACE_MATCH_MAX_ATTEMPTS_EXCEEDED` | Maximum face match attempts exceeded | The maximum number of face match capture attempts has been reached. The last attempt's computed status (decline or review) has been applied. |
## Example — declined on face attack
```json theme={null}
{
"liveness_checks": [
{
"node_id": "feature_liveness",
"status": "Declined",
"method": "ACTIVE_3D",
"score": 12.40,
"reference_image": "https:///.../reference.jpg",
"video_url": "https:///.../video.mp4",
"age_estimation": null,
"matches": [],
"face_quality": null,
"face_luminance": null,
"warnings": [
{
"feature": "LIVENESS",
"risk": "LIVENESS_FACE_ATTACK",
"additional_data": null,
"log_type": "error",
"short_description": "Liveness Face Attack",
"long_description": "The system detected a potential attempt to bypass the liveness check.",
"node_id": "feature_liveness"
}
]
}
],
"face_matches": null
}
```
## Example — in review on low similarity
```json theme={null}
{
"face_matches": [
{
"node_id": "feature_face_match",
"status": "In Review",
"score": 58.70,
"source_image_session_id": "11111111-2222-3333-4444-555555555555",
"source_image": "https:///.../source.jpg",
"target_image": "https:///.../target.jpg",
"warnings": [
{
"feature": "FACEMATCH",
"risk": "LOW_FACE_MATCH_SIMILARITY",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face match similarity",
"long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"node_id": "feature_face_match"
}
]
}
]
}
```
The score (58.70) sits between the decline threshold (default 50) and the review threshold (default 70), so the warning carries `log_type: "warning"` and the face-match status is `In Review`. In this example the warning fired on a session whose `session_id` is `11111111-2222-3333-4444-555555555555` — `source_image_session_id` carries the session's own id because the reference came from the portrait stored at session creation.
## Warning types
Each risk is assigned a warning type based on your application's configuration. Warnings fall into three severity categories:
## Related
Where `liveness_checks[]` and `face_matches[]` appear in the V3 decision.
Full liveness-warning reference including duplicate-face matching.
Full face-match warning reference for 1:1 comparisons.
# Database Validation Matching Methods
Source: https://docs.didit.me/core-technology/database-validation/database-validation-matching-methods
How 1x1 and 2x2 outcomes are derived from per-service matches: fuzzy name thresholds, partialMatchAction rules, waterfall logic. From $0.05/check.
Didit's [Database Validation](/core-technology/database-validation/overview) API checks identity data against the authoritative civil registry, tax authority or electoral roll for each country — RENAPER (Argentina), Receita Federal (Brazil), Tribunal Electoral (Panama), RENAPO (Mexico), Junta Central Electoral (Dominican Republic), Registraduría Nacional (Colombia) and more. Each query is billed per successful call, from **\$0.05/check** with no monthly minimums.
Outcomes are derived from per-service results: **1×1** when a single registry returns a full match, **2×2** when two independent registries corroborate the data. This page documents how those outcomes are computed, the fuzzy-match thresholds applied to names, and the decision logic that governs `partialMatchAction` and `noMatchAction` in your workflow rules.
## 1×1 matching
This method involves matching one input data point (typically a national ID or similar identifier) against our database. If a direct match is found, we confirm the identity. However, if the initial attempt fails, we may try alternate trusted data sources in a waterfall sequence. This means we continue validating through successive providers until we either achieve a match or exhaust all options. Importantly, a partial match does not stop the process—we only stop once we find a full and conclusive match or determine that no match exists.
The 1x1 Matching logic requires matching a user’s personal data against one source to receive a Full Match on their identity.\*
| Verification Result | Name Category | ID Category | Date of Birth Category |
| :------------------------- | :------------------------- | :---------------------- | :--------------------- |
| Full Match | Full Match | Full Match | Any Value |
| Partial Match | Partial Match | Full Match | Any Value |
| No Match | All other combinations | | |
*\*The applicable country sources are screened until a Full Match on the identity is made. If a Full Match is not found, the system then repeatedly screens those sources for a Partial Match on the user’s identity.*
## 2×2 matching
This method requires matching two input data points (e.g., name + date of birth, or national ID + phone number) against two corresponding fields in our database. Just like with 1x1, we follow a waterfall approach, querying multiple data sources sequentially. We persist through each step until we find a complete match across both data fields. Partial or single-field matches are not sufficient; the validation process continues until a definitive 2-field match is achieved or all sources have been checked.
The 2x2 Matching logic requires matching a user’s personal data against two sources to receive a Full Match on their identity.\*
| Verification Result | 1st Data Source | 2nd Data Source |
| :------------------------- | :-------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| Full Match | Name Full Match + National ID Full Match | Name Full Match + National ID Full Match |
| Full Match | Name Full Match + National ID Full Match | Name Partial Match + National ID Full Match |
| Full Match | Name Full Match + National ID Full Match | Name Full Match + Date of Birth Full Match |
| Full Match | Name Full Match + National ID Any Value | Name Full Match + National ID Any Value |
| Partial Match | Name Full Match + National ID Full Match | |
| Partial Match | Name Partial Match + National ID Full Match | |
| Partial Match | Name Full Match + Date of Birth Full Match | |
| Partial Match | Name Full Match + National ID Any Value | |
| No Match | All other combinations | |
*\*The applicable country sources are screened until a Full Match on the identity is made. If a Full Match is not found, the system then repeatedly screens those sources for a Partial Match on the user’s identity.*
***
## Data category and attribute matching
The following are the scenarios that result in a Full Match for the Name, Date of Birth, and ID data categories. A Partial Match is only possible for the Name category.
### Name category matching
A **Full Match** on the Name category is considered when any of the following data attribute matching scenarios is met:
* Full Name Concatenation Full Match (First Name + Last Name combined with 85% similarity threshold)
* First Name Full Match + Last Name Full Match
* First Name Full Match + Maternal Name Full Match
* First Name Full Match + Paternal Name Full Match
A **Partial Match** on the Name category is considered when any of the following data attribute matching scenarios is met:
* First Name Full Match
* Last Name Full Match
* Paternal Name Full Match
* Maternal Name Full Match
* Maternal Name Full Match + Paternal Name Full Match
### Date of birth category matching
A **Full Match** on the Date of Birth category is considered only when the following data attribute matching scenario is met:
* Year of Birth Full Match + Month of Birth Full Match + Day of Birth Full Match
A Partial Match on the Date of Birth category is not possible.
### ID category matching
A **Full Match** on the ID category is considered only when the following data attribute matching scenario is met:
* Identification Number Full Match
A Partial Match on the ID category is not possible.
***
## Fuzzy matching for data attributes
These are the criteria for fuzzy matching on different data attributes against country sources.
* **Name Data Attributes**
For a Name category data attribute to generate a Full Match, it should be within 70% of the Levenshtein character similarity in comparison to the records in the data sources. For full name concatenation (First Name + Last Name combined), a higher threshold of 85% similarity is used. E.g., if the Name attribute input is 'Christophel' and the actual name is 'Christopher', a Full Match is returned based on the fuzzy matching logic. This system is applicable to the *First Name*, *Last Name*, *Paternal Name*, and *Maternal Name* data attributes.
* **Date of Birth and ID Data Attributes**
We do not employ any kind of fuzzy matching on data attributes within the Date of Birth or ID data categories. An exact match is required for these fields.
### Examples
Below are examples of data attribute inputs that would satisfy the criteria of the fuzzy matching logic and generate a Full Match.
| Data Attribute | Input | Data Source Record | Result |
| :------------- | :---------- | :----------------- | :---------------------- |
| first\_name | Christopher | Christopher | Full Match |
| first\_name | Christophel | Christopher | Full Match |
| first\_name | Chris | Christopher | No Match |
| last\_name | Smith | Smith | Full Match |
| last\_name | Smyth | Smith | Full Match |
| last\_name | Smitty | Smith | No Match |
## See also
* [Database Validation overview](/core-technology/database-validation/overview) — how the API works end-to-end, supported registries, pricing.
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes) — full list of provider response codes (deceased, minor, document not found, biometric mismatch).
* [Warnings](/core-technology/database-validation/database-validation-warnings) — what we surface when a service can't run due to missing input.
* [Supported Countries & Services](/core-technology/database-validation/database-validation-supported-countries) — every live registry, required fields and per-call price.
* [Reports](/core-technology/database-validation/database-validation-report) — monthly per-service billing exports.
# Database Validation Outcome Codes
Source: https://docs.didit.me/core-technology/database-validation/database-validation-outcome-codes
Every outcome code Didit's Database Validation returns: match, no match, deceased, minor, document not found, biometric mismatch. From $0.05/check.
Didit's [Database Validation](/core-technology/database-validation/overview) API queries the authoritative civil registry for each country — RENAPER (Argentina, \$0.20/check with biometric face-match), Receita Federal (Brazil, \$0.20), Tribunal Electoral SIB (Panama, \$0.75 biometric), Junta Central Electoral (Dominican Republic, \$0.05) and 14 more — billed per successful query, no monthly minimums.
When you run a Database Validation, you get back two layers of result:
1. A **standard outcome code** that works the same way across every supported country, so you can write one piece of handling logic that covers all of them.
2. An optional **country-specific detail code** that tells you exactly which condition the government registry returned — useful for support, analytics, and deciding whether to prompt the user to retry.
This page lists every value you can see and tells you how to react to each one.
## Standard outcome codes
These codes are identical across every country. Build your review, decline, and retry logic on top of these — not on the per-country detail codes.
| Outcome code | Meaning | Retryable? | Recommended handling |
| :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MATCH` | The registry confirmed the identity and every checked field matched. | — | Approve. |
| `PARTIAL_MATCH` | The identification number was found but one or more personal fields did not fully match (usually name or date of birth). | — | Send to review or configure a [Partial Match Action](/core-technology/database-validation/database-validation-warnings) to auto-decline. |
| `NO_MATCH` | The registry returned no match for the submitted data. | No | Decline, or send to review depending on your policy. |
| `DOCUMENT_SUPERSEDED` | The registry confirmed the person's identity, but the **edition of the document** presented is not the one it holds on file — a newer edition has since been issued, so the credential in hand is stale. Argentina's DNI *ejemplar* letter is the canonical case. Distinct from `NO_MATCH` (the person did not match) and `DOCUMENT_NOT_FOUND` (no record at all): only the physical credential is out of date. | No | Send to review and ask for the current edition of the document. It rides your **No Match Action** and raises [`DATABASE_VALIDATION_DOCUMENT_SUPERSEDED`](/core-technology/database-validation/database-validation-warnings). |
| `DOCUMENT_NOT_FOUND` | The identification number is syntactically valid but does not exist in the government registry. | No | Decline. |
| `INVALID_DOCUMENT_FORMAT` | The submitted identification number is malformed (wrong length, non-numeric, bad checksum). | No | Ask the user to re-enter their document number. |
| `INVALID_INPUT` | A required input field (selfie, gender, date of birth, etc.) was missing or malformed. | No | Ask the user to resubmit the missing field. The session will automatically reprocess once the data is provided — see [`COULD_NOT_PERFORM_DATABASE_VALIDATION`](/core-technology/database-validation/database-validation-warnings). |
| `MINOR_BLOCKED` | The registry withheld the record because the subject is a minor. Applies in jurisdictions with data-protection laws for minors (Brazil LGPD, Argentina). | No | Decline. Respect the local legal framework — do not retry. |
| `DECEASED` | The registry reports that the subject is deceased. | No | Decline. |
| `BIOMETRIC_NO_MATCH` | For biometric validations (Argentina, Panama), the face-match score was below the acceptance threshold. | No | Decline, or send to review. |
| `BIOMETRIC_IMAGE_UNUSABLE` | The selfie could not be processed — image was empty, had no face, had low quality, or the registry could not read it. | Yes | Prompt the user to retake the selfie. |
| `INCONCLUSIVE` | The registry could not determine whether the person matches — the result is genuinely uncertain (they may or may not be in the registry). Distinct from `NO_MATCH` (definitively absent) and from `BIOMETRIC_IMAGE_UNUSABLE` (a technical image problem): nothing was confirmed either way, so `match_type` is `null`. | Maybe | Send to review. A retry with a clearer selfie or additional data may resolve it. |
| `REGISTRY_UNAVAILABLE` | The government registry is temporarily unreachable (timeout, connectivity error, upstream 5xx). | Yes | Retry after a short delay. Didit already retries transient errors automatically, so seeing this in the final report means the retries were exhausted. |
| `REGISTRY_ERROR` | The registry returned an unexpected failure that does not fit any of the buckets above. | Maybe | Escalate to support with the detail code. |
The `outcome_code` lives inside each entry of the `validations` array on the validation report. See [Database Validation Report](/core-technology/database-validation/database-validation-report) for where it fits in the response.
`REGISTRY_UNAVAILABLE` and `REGISTRY_ERROR` mean the source registry never answered the query, so those two outcomes are **not billed** - see [pricing](/core-technology/database-validation/overview). Every other outcome code is a real, paid response from the registry, even one that confirms nothing (`INCONCLUSIVE`, `BIOMETRIC_IMAGE_UNUSABLE`) and even one that rejects what you sent (`INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT` — as outcome codes these mean the registry itself refused the data, after the query went out).
Input that never reaches a registry produces no outcome code at all: `POST /v3/database-validation/` answers `400` when a value fails the country format rules, and a workflow service with missing or malformed fields is skipped with [`COULD_NOT_PERFORM_DATABASE_VALIDATION`](/core-technology/database-validation/database-validation-warnings). Neither is billed. Full breakdown on the [Database Validation pricing page](/getting-started/database-validation-pricing).
## Country-specific detail codes
The detail code is returned as `outcome_detail` alongside `outcome_code`. Only countries listed below produce detail codes beyond the standard outcome — every other supported country returns the standard codes only.
### Argentina (RENAPER)
Argentina uses a biometric 1×1 validation against the RENAPER national identity registry, combining the DNI, a selfie, and declared gender.
| Detail code | Standard outcome | What it means |
| :--------------- | :------------------------- | :--------------------------------------------------------------- |
| `300` | `INVALID_DOCUMENT_FORMAT` | The DNI field must be a 7–8 digit number. |
| `301` | `DOCUMENT_NOT_FOUND` | The submitted DNI does not match a valid record. |
| `303` | `INVALID_INPUT` | The selfie field was empty. |
| `304` | `INVALID_INPUT` | The gender field was empty or not one of `M`, `F`, `X`. |
| `305` | `NO_MATCH` | The DNI and gender combination was not found. |
| `306` | `REGISTRY_UNAVAILABLE` | The query cannot be carried out at this time. |
| `307` | `REGISTRY_UNAVAILABLE` | Error connecting to the public identity validation system. |
| `311` | `REGISTRY_ERROR` | The integration does not have access to this product. |
| `313` | `MINOR_BLOCKED` | The record belongs to an underage person. |
| `400` | `REGISTRY_UNAVAILABLE` | Connection error. |
| `401` | `NO_MATCH` | No results found for the provided data. |
| `405` | `BIOMETRIC_IMAGE_UNUSABLE` | Conflict with the image; it cannot be processed. |
| `406` | `BIOMETRIC_IMAGE_UNUSABLE` | Error in image processing. |
| `NO.HIT` | `BIOMETRIC_NO_MATCH` | The face-match score was below the acceptance threshold (\< 50). |
| `INTERNAL_ERROR` | `REGISTRY_UNAVAILABLE` | Timeout exceeded. |
### Brazil (Receita Federal — Consulta CPF)
Brazil validates the CPF against the Receita Federal's official CPF service, with explicit handling for minors under the LGPD (Lei Geral de Proteção de Dados) and the Children's and Adolescents' Digital Statute.
| Detail code | Standard outcome | What it means |
| :---------- | :------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | `MATCH` | CPF found and data returned successfully. |
| `206` | `MATCH` | CPF found, but some non-essential fields were not returned. |
| `400` | `INVALID_DOCUMENT_FORMAT` | CPF number is not valid. |
| `404` | `DOCUMENT_NOT_FOUND` | No CPF exists with the submitted number. |
| `422` | `MINOR_BLOCKED` | Data for a minor under 18 is withheld per LGPD (Law 13.709/2018). The response includes `minor_under_18: true`. |
| `451` | `MINOR_BLOCKED` | Data for a minor under 16 is withheld per LGPD and the Children's and Adolescents' Digital Statute (Law 15.211/2025). The response includes `minor_under_18: true` and `minor_under_16: true`. |
| `500` | `REGISTRY_UNAVAILABLE` | Upstream server error. |
| `504` | `REGISTRY_UNAVAILABLE` | Gateway timeout — the request did not reach the Consulta CPF API. |
For `MINOR_BLOCKED` outcomes in Brazil, `source_data` still returns the `lgpd_minor`, `minor_under_18`, and `minor_under_16` flags so you can apply age-appropriate flows without retrying.
### Panama (SIB — Tribunal Electoral)
Panama uses biometric validation against the SIB (Servicio de Identificación Biométrica) registry operated by the Tribunal Electoral. If a technical SIB error occurs, the session falls back to a 1×1 lookup automatically.
| Detail code | Standard outcome | What it means |
| :---------- | :------------------------- | :--------------------------------------- |
| `601` | `REGISTRY_UNAVAILABLE` | Operator does not exist in the database. |
| `602` | `MINOR_BLOCKED` | Citizen is under 12 years old. |
| `603` | `DECEASED` | Citizen is deceased. |
| `604` | `NO_MATCH` | Citizen has an invalid registration. |
| `605` | `NO_MATCH` | Citizen not recognized. |
| `606` | `INVALID_INPUT` | Cédula field is empty. |
| `607` | `DOCUMENT_NOT_FOUND` | Citizen not found. |
| `608` | `REGISTRY_ERROR` | No SIB access for this client. |
| `610` | `BIOMETRIC_IMAGE_UNUSABLE` | General facial validation error. |
| `611` | `BIOMETRIC_IMAGE_UNUSABLE` | Image for comparison was not sent. |
| `612` | `BIOMETRIC_IMAGE_UNUSABLE` | Image has no content. |
| `613` | `BIOMETRIC_IMAGE_UNUSABLE` | Invalid image format. |
| `614` | `BIOMETRIC_IMAGE_UNUSABLE` | No face detected in the image. |
| `615` | `BIOMETRIC_NO_MATCH` | Similarity threshold not reached. |
### Other supported countries
The countries below return the standard outcome codes only — there are no country-specific detail codes to handle.
* **Bolivia, Colombia, Costa Rica, El Salvador, Guatemala, Honduras, Paraguay, Spain, Uruguay, Venezuela** — 1×1 lookup against the corresponding civil registry. Results map directly to `MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, or `REGISTRY_UNAVAILABLE`.
* **Chile (Registro Civil)** — RUT lookup. Maps to the standard outcomes plus `MINOR_BLOCKED` when the registry flags an underage record.
* **Dominican Republic (JCE)** — Cédula lookup that returns a boolean validity; maps to `MATCH` or `NO_MATCH`.
* **Ecuador (Registro Civil)** — Cédula lookup.
* **Mexico (RENAPO, plus INE for 2×2)** — CURP lookup via RENAPO. When `validation_type` is `two_by_two`, an additional INE cross-reference runs; both feed into the same standard outcome codes.
* **Peru (RENIEC)** — DNI lookup.
## Handling recipe
The simplest robust handler looks like this:
```typescript theme={null}
switch (result.outcome_code) {
case "MATCH":
return approve();
case "PARTIAL_MATCH":
return review();
case "NO_MATCH":
case "DOCUMENT_NOT_FOUND":
case "DECEASED":
case "MINOR_BLOCKED":
case "BIOMETRIC_NO_MATCH":
return decline();
case "INVALID_DOCUMENT_FORMAT":
case "INVALID_INPUT":
return askUserToFixInput(result.outcome_detail);
case "BIOMETRIC_IMAGE_UNUSABLE":
return askUserToRetakeSelfie();
case "REGISTRY_UNAVAILABLE":
case "REGISTRY_ERROR":
return review(); // Didit already retried; a human can decide.
default:
return review();
}
```
You don't need to branch on `outcome_detail` for business logic — use it for analytics, debugging, and support tickets. The standard `outcome_code` is guaranteed to stay stable across countries and over time.
## See also
* [Database Validation Warnings](/core-technology/database-validation/database-validation-warnings) — the session-level warning tags (`COULD_NOT_PERFORM_DATABASE_VALIDATION`, `DATABASE_VALIDATION_PARTIAL_MATCH`, `DATABASE_VALIDATION_NO_MATCH`) and how to configure your application's review/decline actions.
* [Database Validation Report](/core-technology/database-validation/database-validation-report) — the full response structure and where `outcome_code` / `outcome_detail` appear.
* [Database Validation Supported Countries](/core-technology/database-validation/database-validation-supported-countries) — the full country and matching-method list.
# Database Validation Report
Source: https://docs.didit.me/core-technology/database-validation/database-validation-report
Parse Database Validation responses: per-service match outcomes, registry source data, biometric face-match scores, and warning codes. Pay-per-call from $0.05.
Didit's [Database Validation](/core-technology/database-validation/overview) feature cross-references each user's identity data against the authoritative source for that country — RENAPER (Argentina), Receita Federal (Brazil), Tribunal Electoral (Panama, with biometric face-match), Junta Central Electoral (Dominican Republic), and more — billed per successful query with no monthly minimums.
Each entry in `database_validations[]` is one full Database Validation report — one run of a Database Validation node against one issuing state. The per-service registry outcomes live inside that entry's `validations[]` sub-array. This page documents the JSON shape so you can parse match outcomes, registry source data, and biometric scores returned by each service.
## Overview
A Database Validation report is produced every time a workflow node runs the Database Validation feature. Each entry in the `database_validations[]` array represents **one node run** against **one issuing state**; if the node runs multiple services for that country (for example CPF + CNH in Brazil), each service appears as a separate item inside the entry's `validations[]` sub-array. Each report contains:
* The overall feature `status` — a [`FeatureStatusChoices`](/reference/data-models#status-enum-reference) value (`Not Finished`, `Approved`, `Declined`, `In Review`).
* A roll-up `match_type` (`full_match`, `partial_match`, `no_match`, or `null` when no service returned a usable result) aggregated across services via the [matching method](/core-technology/database-validation/database-validation-matching-methods).
* The `issuing_state` (ISO 3166-1 alpha-3) and the derived `validation_type` (`one_by_one`, `two_by_two`, or `not_enabled` — never `null`).
* The `screened_data` block — the user-provided data that was sent to the registry.
* A `validations[]` array — one entry per service that produced a result, with the catalog `service_id`/`service_name`, the vendor-neutral `outcome_code`, an optional `outcome_detail`, the per-field `validation` map and the cleaned `source_data` lifted from the registry response.
* An `errors[]` array — per-service errors when a lookup failed or was skipped; the key is **omitted entirely** when there are no errors.
* A `warnings[]` array — risk events emitted during the validation (see [Database Validation warnings](/core-technology/database-validation/database-validation-warnings)).
* A `node_id` that identifies which workflow graph node produced the report (V3 sessions only).
## Where it appears in API responses
The report appears as **`database_validations[]`** in `GET /v3/session/{sessionId}/decision/` — always a JSON array, never a singular object. The array contains one entry per Database Validation node in the workflow graph; in turn, each entry's `validations[]` sub-array contains one item per registry service that produced a result.
```json theme={null}
{
"session_id": "11111111-1111-1111-1111-111111111111",
"status": "Approved",
"database_validations": [
{ "node_id": "feature_db_validation_1", "status": "Approved", "...": "..." }
]
}
```
A `null` value means no Database Validation step has run yet.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#database-validation) page. It is produced by `DatabaseValidationV3Serializer`.
```typescript theme={null}
interface DatabaseValidation {
node_id: string | null;
status: "Not Finished" | "Approved" | "Declined" | "In Review"; // FeatureStatusChoices
issuing_state: string | null; // ISO 3166-1 alpha-3, e.g. "ARG"
validation_type: "one_by_one" | "two_by_two" | "not_enabled"; // derived, never null
match_type: "full_match" | "partial_match" | "no_match" | null; // null when no service returned a usable result
screened_data: { // What we sent to the registry (varies by country/service)
tax_number?: string;
personal_number?: string;
document_number?: string;
first_name?: string;
last_name?: string;
date_of_birth?: string; // YYYY-MM-DD
selfie?: string; // Presigned URL (biometric services)
cnh_qr_code_image?: string; // Presigned URL (Brazil CNH QR + face match)
document_image?: string; // Presigned URL (services that take a document image)
gender?: string; // Biometric registry services (e.g. Argentina RENAPER)
};
validations: Array<{
// Catalog metadata
service_id?: string; // e.g. "arg_renaper"
service_name?: string; // e.g. "Argentina - DNI verification (RENAPER)"
// Per-field match outcome — keys vary by service
// (full_name, first_name, last_name, date_of_birth, identification_number, address, …)
validation?: Record;
outcome_code?: string; // Vendor-neutral outcome, see below
outcome_detail?: string; // Raw upstream status detail backing the outcome code
source_data?: { // Cleaned registry record
identification_number?: string;
first_name?: string;
last_name?: string;
full_name?: string;
date_of_birth?: string;
place_of_birth?: string;
gender?: string;
nationality?: string;
issue_date?: string;
expiration_date?: string;
photo?: string; // Pre-signed JPEG URL
signature?: string; // Pre-signed JPEG URL
face_match_score?: number; // Biometric services only (0-100)
};
}>;
errors?: Array<{ // Omitted entirely when empty
service_id: string;
code?: string; // e.g. "invalid_screened_data", "empty_provider_response"
message?: string;
missing_required_fields?: string[];
invalid_format_fields?: string[];
field_reasons?: Record;
}>;
warnings: Warning[]; // See /reference/data-models#warning-object
}
```
### Status values
`status` uses the standard feature-level enum (see [Status enums](/reference/data-models#status-enums)) — see the [Status enum reference](/reference/data-models#status-enum-reference). The enum also defines `Resub Requested`, which is not produced by Database Validation in practice.
| Status | Meaning |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The validation has not produced a result yet (initial state; also recorded when a standalone call fails across all services). |
| `Approved` | No risk fired, or the only fired risks are configured `NO_ACTION` (the partial-match default). |
| `Declined` | A no-match or partial-match outcome whose configured action is `DECLINE`. |
| `In Review` | A no-match or partial-match outcome whose configured action is `REVIEW`, or the validation could not run at all (`COULD_NOT_PERFORM_DATABASE_VALIDATION` — always review). |
Custom per-node status rules are applied on top of this base status. See [Database Validation warnings](/core-technology/database-validation/database-validation-warnings) for the configurable actions and their defaults.
### Outcome codes
Each entry in `validations[]` carries a vendor-neutral `outcome_code` — one of `MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT`, `MINOR_BLOCKED`, `DECEASED`, `BIOMETRIC_NO_MATCH`, `BIOMETRIC_IMAGE_UNUSABLE`, `INCONCLUSIVE`, `DOCUMENT_SUPERSEDED`, `REGISTRY_UNAVAILABLE`, `REGISTRY_ERROR`. Note the distinctions: `NO_MATCH` means the registry definitively did not confirm the data, `INCONCLUSIVE` means the registry could not determine either way (route to review, not decline), and `BIOMETRIC_IMAGE_UNUSABLE` is a technical selfie problem (retake) — unlike the definitive `BIOMETRIC_NO_MATCH`. See the full taxonomy and recommended handling on the [Outcome codes](/core-technology/database-validation/database-validation-outcome-codes) page.
### One entry per service
The `validations[]` array contains **one item for each service that produced a result payload** (a `validation` map, `source_data`, `outcome_code`, or `outcome_detail`). If your workflow runs multiple services for the same country (for example Brazil CPF + Brazil CNH), each one appears separately with its own `service_id`, `outcome_code`, and `source_data`. The roll-up `match_type` aggregates across all services (any full match → `full_match`; else any partial → `partial_match`; else `no_match`), and `validation_type` is derived from how many distinct services full-matched — see [Matching methods](/core-technology/database-validation/database-validation-matching-methods).
### Pre-signed image URLs
Image fields in `source_data` (`photo`, `signature`) and `screened_data` (`selfie`, `cnh_qr_code_image`, `document_image`) are returned as **pre-signed URLs** that expire — refresh by re-calling the decision endpoint. The raw upstream payload is intentionally not exposed via the API.
## Examples
### Approved — Brazil CPF status check (Receita Federal)
```json theme={null}
{
"node_id": "feature_db_validation_1",
"status": "Approved",
"issuing_state": "BRA",
"validation_type": "one_by_one",
"match_type": "full_match",
"screened_data": {
"tax_number": "12345678900",
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1980-01-01"
},
"validations": [
{
"service_id": "bra_cpf",
"service_name": "Brazil - CPF status check",
"outcome_code": "MATCH",
"outcome_detail": "200",
"validation": {
"full_name": "full_match",
"date_of_birth": "full_match",
"identification_number": "full_match"
},
"source_data": {
"identification_number": "12345678900",
"first_name": "JOHN",
"last_name": "DOE",
"date_of_birth": "1980-01-01",
"lgpd_minor": false,
"minor_under_16": false,
"minor_under_18": false
}
}
],
"warnings": []
}
```
### Approved — Argentina DNI biometric (RENAPER)
For Argentina, the validation uses RENAPER with a biometric face-match. The response includes a `face_match_score` and additional identity fields returned by the registry.
```json theme={null}
{
"node_id": "feature_db_validation_1",
"status": "Approved",
"issuing_state": "ARG",
"validation_type": "one_by_one",
"match_type": "full_match",
"screened_data": {
"document_number": "12345678",
"selfie": "https:///sessions//selfie.jpg?X-Amz-Signature=...",
"gender": "M",
"first_name": "John",
"last_name": "Doe"
},
"validations": [
{
"service_id": "arg_renaper",
"service_name": "Argentina - DNI verification (RENAPER)",
"outcome_code": "MATCH",
"validation": {
"full_name": "full_match",
"identification_number": "full_match"
},
"source_data": {
"identification_number": "12345678",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"date_of_birth": "1990-01-01",
"tax_id": "20000000019",
"tax_id_type": "CUIT",
"face_match_score": 97
}
}
],
"warnings": []
}
```
### In Review — registry could not run
When no service returns a usable result — required fields missing or malformed, or the registry unreachable — `match_type` stays `null`, `validation_type` is `not_enabled`, a `COULD_NOT_PERFORM_DATABASE_VALIDATION` warning is emitted, and the feature moves to `In Review` until the data is corrected. **No charge** is applied for skipped services.
```json theme={null}
{
"node_id": "feature_db_validation_1",
"status": "In Review",
"issuing_state": "PAN",
"validation_type": "not_enabled",
"match_type": null,
"screened_data": {
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-01",
"selfie": "https:///sessions//selfie.jpg?X-Amz-Signature=..."
},
"validations": [],
"errors": [
{
"code": "invalid_screened_data",
"message": "Database validation was skipped before calling the provider because the screened data has missing required fields: personal_number.",
"missing_required_fields": ["personal_number"],
"service_id": "pan_cedula_sib_plus"
}
],
"warnings": [
{
"feature": "DATABASE_VALIDATION",
"risk": "COULD_NOT_PERFORM_DATABASE_VALIDATION",
"additional_data": {
"reason": "Database validation was skipped before any provider request because the screened data is incomplete or invalid.",
"database_validation_errors": [
{
"code": "invalid_screened_data",
"message": "Database validation was skipped before calling the provider because the screened data has missing required fields: personal_number.",
"missing_required_fields": ["personal_number"],
"service_id": "pan_cedula_sib_plus"
}
],
"missing_required_fields_by_service": { "pan_cedula_sib_plus": ["personal_number"] },
"invalid_format_fields_by_service": {},
"field_reasons_by_service": {}
},
"log_type": "warning",
"short_description": "Could not perform database validation",
"long_description": "The system couldn't perform the database validation, please fill the required fields for this country to automatically proceed with database validation.",
"node_id": "feature_db_validation_1"
}
]
}
```
Fill the missing field from the session detail page in the Console — saving it re-triggers the check automatically and clears the warning on success.
## Related
* [Database Validation overview](/core-technology/database-validation/overview) — how the API works end-to-end and supported registries.
* [Matching methods](/core-technology/database-validation/database-validation-matching-methods) — how 1×1 / 2×2 outcomes (`validation_type: one_by_one` / `two_by_two`) are derived.
* [Outcome codes](/core-technology/database-validation/database-validation-outcome-codes) — full per-result taxonomy with country-specific detail codes.
* [Supported countries & services](/core-technology/database-validation/database-validation-supported-countries) — every live registry, required fields and per-call price.
* [Database Validation warnings](/core-technology/database-validation/database-validation-warnings) — what we surface when a service can't run.
* [Data models — database validation](/reference/data-models#database-validation) — canonical field-by-field schema.
# Supported Countries & Services
Source: https://docs.didit.me/core-technology/database-validation/database-validation-supported-countries
Every Database Validation service: country, source registry, required fields, per-call price. Cheapest pay-per-call government-registry KYC from $0.05.
Didit's **Database Validation** verifies the identity data extracted from a user's ID document directly against the authoritative source for that country — the national civil registry, tax authority, electoral roll or biometric service that issued the document. Each call returns a per-field match plus the underlying record.
## Why teams pick Didit for Database Validation
* **Cheapest in the market.** Pay-per-call from **\$0.05** (Dominican Republic) up to **\$7.00** for the most expensive specialist sources, priced per service rather than per country. No monthly minimums, no platform fees, no per-seat charges. Check the exact rate in the [per-service pricing table](/getting-started/database-validation-pricing) before you budget a country.
* **Fastest to integrate.** [Sign up](https://business.didit.me/), grab an API key, and you can call `POST /v3/database-validation/` within minutes. No sales call, no procurement cycle, no proof-of-concept paperwork.
* **Authoritative sources, not aggregator scrapes.** RENAPER, Receita Federal (SERPRO), Tribunal Electoral, RENAPO, RENAP, RNP, RNPN, Junta Central Electoral, Registraduría Nacional, SEGIP, Servicio de Registro Civil, Tribunal Supremo de Elecciones, Dirección General de la Policía and more.
* **Biometric face-match included** for countries where the registry stores an official photo (Argentina via RENAPER, Panama via Tribunal Electoral SIB / SIB Plus). Confirm the person on the camera is the legitimate holder of the ID — not just that the number is real.
* **Per-call billing surfaced as it happens.** Every successful query becomes a line item; you see exactly what you spent, by service, in your monthly Database Validation report.
Customise the services per workflow in the [console](/console/workflows). Required and optional inputs are derived from the union of the services you enable. See [Database Validation Pricing](/getting-started/database-validation-pricing) for full per-country price ranges.
## All live services
The table lists every service we run today, the source registry, the inputs each service requires, and the per-call price. Services tagged **`BIOMETRIC`** also face-match the user's selfie against the registry photo.
## How to call any of these
Every service is reachable through the same `POST /v3/database-validation/` endpoint. Pass the country in `issuing_state`, the service IDs you want to run in `services`, and the inputs each service requires. Omit `services` to run every configured service for that country.
```bash theme={null}
curl -X POST "https://verification.didit.me/v3/database-validation/" \
-H "x-api-key: YOUR_API_KEY" \
-F "issuing_state=BRA" \
-F "tax_number=SAMPLE-TAX-12345" \
-F "services=bra_cpf"
```
Per-country reference pages with full request/response examples live under **Database Validation** in the API Reference sidebar.
## Technical service metadata
This generated table is built from the service catalog plus captured workflow responses. It shows the valid document types, API inputs, workflow availability, consent requirement, and the exact `source_data` fields Didit has observed for each service.
If a service requires consent, collect explicit end-user consent before calling it and send `consent=true` in `POST /v3/database-validation/`. If a service requires onboarding, it appears in the console but stays disabled until Didit support enables it for your workspace.
We're constantly expanding coverage. If you need a registry that isn't on this list yet, [contact us](https://api.whatsapp.com/send/?phone=%2B34681310687) — most new integrations go live in 2–3 weeks.
## Live registries by country
Direct links to every per-service reference page — request shape, response, outcome codes and per-call price.
* [🇦🇷 Argentina — DNI verification (RENAPER, biometric, \$0.20)](/api-reference/database-validation/argentina/renaper)
* [🇧🇴 Bolivia — Cédula verification (SEGIP, 90-95%, \$0.20)](/api-reference/database-validation/bolivia/cedula)
* [🇧🇷 Brazil — CPF status check (Receita Federal, \$0.20)](/api-reference/database-validation/brazil/cpf)
* [🇨🇱 Chile — RUT verification (Servicio de Registro Civil, \$0.20)](/api-reference/database-validation/chile/rut)
* [🇨🇴 Colombia — Cédula verification (Registraduría / ANI, 90-95%, \$0.20)](/api-reference/database-validation/colombia/cedula)
* [🇨🇷 Costa Rica — Cédula verification (Tribunal Supremo de Elecciones, 90-95%, \$0.20)](/api-reference/database-validation/costa-rica/cedula)
* [🇩🇴 Dominican Republic — Cédula verification (Junta Central Electoral, \$0.05 — cheapest in catalog)](/api-reference/database-validation/dominican-republic/cedula)
* [🇪🇨 Ecuador — Cédula verification (Registro Civil, \$0.20)](/api-reference/database-validation/ecuador/cedula)
* [🇸🇻 El Salvador — DUI verification (RNPN, 90-95%, \$0.20)](/api-reference/database-validation/el-salvador/dui)
* [🇪🇸 Spain — DNI / NIE verification (Dirección General de la Policía, \$1.26)](/api-reference/database-validation/spain/dni)
* [🇬🇹 Guatemala — DPI verification (SAT, 90-95%, \$0.20)](/api-reference/database-validation/guatemala/dpi)
* [🇭🇳 Honduras — DNI verification (CNE, 90-95%, \$0.20)](/api-reference/database-validation/honduras/dni)
* [🇲🇽 Mexico — CURP verification (RENAPO, \$0.20)](/api-reference/database-validation/mexico/curp)
* [🇲🇽 Mexico — INE credential validity verification (INE, \$0.10)](/api-reference/database-validation/mexico/ine-vigencia)
* [🇵🇦 Panama — Cédula via Tribunal Electoral SIB (biometric, \$0.75)](/api-reference/database-validation/panama/sib)
* [🇵🇦 Panama — Cédula via Tribunal Electoral SIB Plus (premium biometric, \$1.50)](/api-reference/database-validation/panama/sib-plus)
* [🇵🇾 Paraguay — Cédula verification (Registro del Estado Civil, 90-95%, \$0.20)](/api-reference/database-validation/paraguay/cedula)
* [🇵🇪 Peru — DNI verification (RENIEC, \$0.17)](/api-reference/database-validation/peru/dni)
* [🇺🇾 Uruguay — Cédula verification (Registro de Estado Civil, 90-95%, \$0.20)](/api-reference/database-validation/uruguay/cedula)
* [🇻🇪 Venezuela — Cédula verification (CNE, 90-95%, \$0.20)](/api-reference/database-validation/venezuela/cedula)
* [🇿🇦 South Africa — National ID verification (DHA, \$0.50)](/api-reference/database-validation/south-africa/national-id)
* [🇿🇦 South Africa — Driving License verification (NATIS, \$0.20)](/api-reference/database-validation/south-africa/drivers-license)
* [🇿🇦 South Africa — Vehicle Ownership (NATIS, \$0.20)](/api-reference/database-validation/south-africa/vehicle-ownership)
* [🇿🇦 South Africa — Bank Account Holder Verification (AHV, \$0.40)](/api-reference/database-validation/south-africa/bank-account-holder)
* [🇿🇦 South Africa: Company Registry Lookup (CIPC, USD 0.30)](/api-reference/database-validation/south-africa/company-registry)
* [🇿🇦 South Africa — Fraud Prevention Screening (SAFPS, \$0.40)](/api-reference/database-validation/south-africa/fraud-prevention)
## Global expansion
Beyond our directly-integrated LATAM and Spain registries, Didit also fronts a broad global identity network — 81 datasets across 26 countries (identity, residential, credit, document, government and demographic data) reachable through the same `POST /v3/database-validation/` endpoint.
Coverage spans Australia, Austria, Cambodia, Canada, China, Denmark, Finland, France, Germany, India, Indonesia, Italy, Kenya, Malaysia, Netherlands, New Zealand, Nigeria, Norway, Philippines, Singapore, South Africa, Sweden, Switzerland, Thailand, United Kingdom and United States. Pricing is surfaced per service in the catalog and billing exports.
Per-country pages with the full dataset list (and which datasets are workflow-compatible vs. standalone-API-only) live under **🌐 Database Validation** in the API Reference sidebar — see e.g. [Australia](/api-reference/database-validation/australia), [United Kingdom](/api-reference/database-validation/united-kingdom), [India](/api-reference/database-validation/india), [United States](/api-reference/database-validation/united-states).
## See also
* [Database Validation overview](/core-technology/database-validation/overview) — how the API works end-to-end, biometric face-match, pricing.
* [Matching Methods](/core-technology/database-validation/database-validation-matching-methods) — how 1×1 / 2×2 outcomes are derived.
* [Outcome Codes](/core-technology/database-validation/database-validation-outcome-codes) — full per-result taxonomy with country-specific detail codes.
* [Warnings](/core-technology/database-validation/database-validation-warnings) — what we surface when a service can't run.
* [Reports](/core-technology/database-validation/database-validation-report) — monthly per-service billing exports.
* [Database Validation Pricing](/getting-started/database-validation-pricing) — the full per-country price list.
# Database Validation Warnings
Source: https://docs.didit.me/core-technology/database-validation/database-validation-warnings
Reference for every Database Validation warning: COULD_NOT_PERFORM_DATABASE_VALIDATION, partial or no match, registry unreachable, and missing fields.
Didit's [Database Validation](/core-technology/database-validation/overview) feature verifies identity data against authoritative civil registries — RENAPER (Argentina), Receita Federal (Brazil), Tribunal Electoral (Panama, biometric), Junta Central Electoral (Dominican Republic), and more — billed per successful query with no monthly minimums.
When no service can run (required fields missing or malformed, registry unavailable), the document itself cannot be checked (it does not carry the identifier the configured services need — e.g. a passport without a national ID number), or the aggregate outcome is non-conclusive (partial match, no match), the feature emits a **warning** on the session. If the issuing state isn't enabled in the workflow at all, the step is skipped silently with no warning and no charge. This page documents every warning code, what triggers it, and how to configure the workflow response.
## Overview
Warnings are produced both in workflow sessions and by the standalone API (`POST /v3/database-validation/`). All five risks are tagged with feature `DATABASE_VALIDATION`, appear on the report under `database_validations[].warnings[]`, and follow the standard [warning object](/reference/data-models#warning-object) shape (`feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`).
There are no hard auto-decline triggers for Database Validation. The two match-outcome risks, the superseded-document risk and the not-applicable risk map to a **configurable action** (`DECLINE`, `REVIEW`, or `NO_ACTION`); `COULD_NOT_PERFORM_DATABASE_VALIDATION` is the one fixed behavior — it always routes the feature to **In Review**.
## Configurable risks
| Setting | Risk code | Behavior |
| --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No Match Action | `DATABASE_VALIDATION_NO_MATCH` | `REVIEW` (workflow default) sets the feature to **In Review**; `DECLINE` declines; `NO_ACTION` records the warning without affecting status. The standalone API's `no_match_action` parameter defaults to `DECLINE` instead. |
| Partial Match Action | `DATABASE_VALIDATION_PARTIAL_MATCH` | `NO_ACTION` (default) records the warning without affecting status; `REVIEW` sets the feature to **In Review**; `DECLINE` declines. |
| No Match Action | `DATABASE_VALIDATION_DOCUMENT_SUPERSEDED` | Shares the **No Match Action** setting — there is no separate knob. The registry contradicted what was submitted, so an organization that declines on no-match declines on a stale credential too. |
| Not Applicable Action | `DATABASE_VALIDATION_NOT_APPLICABLE` | `NO_ACTION` (default) records the warning without affecting status; `REVIEW` sets the feature to **In Review**; `DECLINE` declines. Fires when the document cannot be checked at all — it does not carry the identifier the configured services query by (e.g. a passport without a national ID number). |
The configured action also drives the warning's `log_type`: `DECLINE` → `error`, `REVIEW` → `warning`, `NO_ACTION` → `information`.
The match outcome itself is aggregated from the per-service `validation` entries (any full match → `full_match`; else any partial → `partial_match`; else `no_match`) — see [Matching methods](/core-technology/database-validation/database-validation-matching-methods) for the full decision logic that derives 1×1 / 2×2 outcomes.
## Warnings produced
| Tag | Short description | Description |
| ----------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COULD_NOT_PERFORM_DATABASE_VALIDATION` | Could not perform database validation | No service returned a usable result (`match_type` stays `null`) — typically because required fields are missing or malformed, or the registry returned an error. `additional_data` carries the per-service skip summary (`reason`, `database_validation_errors`, `missing_required_fields_by_service`, `invalid_format_fields_by_service`, `field_reasons_by_service`); the standalone API uses `{ "validation_errors": [...] }` instead. **You are not charged** when this warning fires; the feature moves to **In Review** until the data is corrected. |
| `DATABASE_VALIDATION_NOT_APPLICABLE` | Database validation not applicable for this document | The document provided does not carry the identifier the configured database service(s) require (`match_type` stays `null`) — for example, a Brazilian passport carries no CPF, so the CPF registry cannot be queried. This is expected for the document type and does not indicate a problem with the document. `additional_data` carries the per-service skip summary. **You are not charged** when this warning fires. Action configurable (default `NO_ACTION`). |
| `DATABASE_VALIDATION_PARTIAL_MATCH` | Database validation partial match | The aggregate `match_type` is `partial_match` — some screened fields matched the registry but not all of them. Action configurable (default `NO_ACTION`). |
| `DATABASE_VALIDATION_NO_MATCH` | Database validation no match | The aggregate `match_type` is `no_match` — the registry could not confirm the screened identity. Action configurable (default `REVIEW` in workflows, `DECLINE` on the standalone API). |
| `DATABASE_VALIDATION_DOCUMENT_SUPERSEDED` | Superseded document edition | A service returned `DOCUMENT_SUPERSEDED`: the registry confirmed the person, but the edition of the document presented is not the one on file — a newer edition has since been issued. Raised independently of `match_type`, which stays `full_match` because every personal field did match, so the finding would otherwise be invisible. `additional_data` names the services that reported it. Follows the **No Match Action** setting. |
`COULD_NOT_PERFORM_DATABASE_VALIDATION` covers two different situations, and the warning's `long_description` (and, for a per-service failure, `database_validation_errors[].message`) now states which one applied instead of a single generic sentence: the screened data was missing or malformed before any service could be queried, or a service was queried and did not return a usable result. In the second case, when the registry reported its own diagnostic detail for that attempt, that detail is included in the message so you don't have to contact support to learn why. The upstream service is never named in the response.
When `COULD_NOT_PERFORM_DATABASE_VALIDATION` or `DATABASE_VALIDATION_NOT_APPLICABLE` fires in a workflow session, the check is automatically re-triggered once the missing data is filled in on the session detail page in the Console — saving the field re-runs the validation, and the warning is removed on success. This also works when the not-applicable action declined the session, so a declined session can be remediated by correcting the data. For `DATABASE_VALIDATION_NOT_APPLICABLE` the re-run helps when the document can carry the identifier but it wasn't read (for example, a residence card whose national ID number wasn't extracted); document types that never carry the identifier (for example, passports without a national ID number) keep the warning. **No charge** is applied for skipped services; you only pay when a service returns a billable result.
## Example
```json theme={null}
{
"warnings": [
{
"feature": "DATABASE_VALIDATION",
"risk": "DATABASE_VALIDATION_NO_MATCH",
"additional_data": null,
"log_type": "warning",
"short_description": "Database validation no match",
"long_description": "The system identified a no match in the database validation, requiring further investigation.",
"node_id": "feature_db_validation_1"
}
]
}
```
## Warning types
Each risk is assigned a severity based on your application's configuration. Severities fall into three categories:
## Related
* [Database Validation overview](/core-technology/database-validation/overview) — how the feature works end-to-end and supported registries.
* [Database Validation report](/core-technology/database-validation/database-validation-report) — full response shape including per-service `validations[]`.
* [Matching methods](/core-technology/database-validation/database-validation-matching-methods) — how 1×1 / 2×2 outcomes (`validation_type: one_by_one` / `two_by_two`) are derived from per-service matches.
* [Outcome codes](/core-technology/database-validation/database-validation-outcome-codes) — full per-result taxonomy with country-specific detail codes.
* [Supported countries & services](/core-technology/database-validation/database-validation-supported-countries) — every live registry, required fields and per-call price.
* [Data models — database validation](/reference/data-models#database-validation) — canonical field-by-field schema.
# Database Validation Overview
Source: https://docs.didit.me/core-technology/database-validation/overview
Cheapest, fastest pay-per-call government-registry verification API. From $0.05/check, 60+ live countries, biometric face-match where registry holds photo.
Didit's **Database Validation** verifies a person's identity data against the authoritative source for that country — the national civil registry, tax authority, electoral roll, credit bureau or biometric registry that issued the document in the first place. Each check returns a per-field match (`identification_number`, `first_name`, `last_name`, `date_of_birth`, and address fields when the service uses them) plus an aggregated outcome and the underlying record details when available.
It is the strongest defence against synthetic-identity fraud: a stolen real DNI or CPF will fail the registry lookup, and a fabricated number won't be in the official database at all. Database Validation pairs with our biometric face-match in countries where the registry holds an official photo (Argentina via RENAPER, Panama via Tribunal Electoral SIB / SIB Plus) so you can confirm not just that a number is real, but that the **person standing in front of the camera owns it**.
## Why Didit
* **Cheapest pay-per-call pricing.** From **\$0.05** per check (Dominican Republic) up to **\$7.00** for the most expensive specialist sources. The price is set per service, not per country — a registry that charges a high wholesale rate is priced accordingly (Kenya National ID is **\$3.15**). Always read the rate for the exact service you plan to call from the [per-service pricing table](/getting-started/database-validation-pricing). No monthly minimums, no platform fees, no per-seat billing — only successful queries are charged.
* **Fastest to integrate.** [Sign up](https://business.didit.me/), grab an API key from the console, and you can call `POST /v3/database-validation/` within minutes. No sales call, no procurement cycle, no proof-of-concept paperwork — Didit is fully self-service.
* **Direct hits to the authoritative source.** RENAPER, Receita Federal (SERPRO), Tribunal Electoral, RENAPO, RENAP, RNP, RNPN, Junta Central Electoral, Registraduría Nacional, SEGIP, Servicio de Registro Civil, Tribunal Supremo de Elecciones, Dirección General de la Policía — not aggregator scrapes.
* **Biometric face-match where the registry has an official photo on file.** Higher-than-OCR confidence with no extra integration work.
* **Production-ready latency.** p95 typically under 1.5 s end-to-end, 99.9% quarterly availability.
The Academy lesson on reading a verification result reaches database validation at 11:05 and shows full, partial, and no-match outcomes.
## Global coverage
Hover any highlighted country to see every Database Validation service we run there, the data domain (Identity, Financial, Biometric, Address, Telecom, …) and the per-call price. Live countries are production-ready today; the rest are wired in on demand once a customer asks.
**Don't see your country?** Reach out — our catalog tracks 60+ countries with hundreds of services that we activate on demand once a customer needs them. New integrations typically go live in 2–3 weeks.
## How a Database Validation runs
A Database Validation never fires speculatively. It only runs when **all** of the following are true:
1. The session resolved a country to query — from the `issuing_state` of an [ID Verification](/core-technology/id-verification/overview), or, when the workflow has no ID Verification step, from the single country the Database Validation step is configured for.
2. That country has at least one Database Validation service enabled in the workflow.
3. The steps in front of the Database Validation step supplied **every required field** the chosen service needs.
If a service is enabled but the required fields never arrived (e.g. CPF wasn't extractable from a smudged Brazilian RG), the service is skipped, **you are not charged**, and a warning is raised on the session: `DATABASE_VALIDATION_NOT_APPLICABLE` when the document simply does not carry the identifier that service queries by, `COULD_NOT_PERFORM_DATABASE_VALIDATION` when it should have been available and was not. Both are listed in the [warnings catalogue](/core-technology/database-validation/database-validation-warnings).
Someone on your team with review access to the session can then fill the missing field by hand from the session detail page in the Console, and saving it re-triggers the check automatically. That is a reviewer action, not something the end user can do — to let the **user** supply a field their document does not carry, ask for it in a Questionnaire (or a Document AI extraction, or a value you send when you create the session) and map it on the Database Validation step, as described below.
### Database Validation does not require ID Verification
A Database Validation step used to imply an ID Verification step in front of it, because an ID document's OCR was the only wired way to obtain the end user's data. That is no longer the case: **each required field declares which steps can supply it**, and a service becomes available as soon as your workflow contains a step that supplies every field it needs.
| Input source | Supplies |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ID Verification | Names, date of birth, address, document/personal number, and the country-specific identifiers printed on an ID (tax number, national ID, passport number, driving licence number, …) |
| NFC | The chip-read subset of the above, at higher trust |
| Liveness | The live selfie, for facial-biometric registry services |
| Phone Verification | The verified phone number |
| Email Verification | The verified email address |
| Proof of Address | The verified residential address and its components |
| Document AI | Any field you map to one of your extraction field keys |
| Questionnaire | Any field you map to one of your questions |
| API input | Anything, on the standalone API — the caller supplies the values directly |
This means a **facial-biometric service can run from a Liveness step alone**, with no ID document anywhere in the workflow, and a service keyed on a tax number, phone number or email can be satisfied from a questionnaire answer, a Phone/Email Verification step, or a Document AI extraction.
Two rules keep this safe:
* **A workflow with no ID Verification must configure exactly one country** on the Database Validation step. Without an `issuing_state` from a document, that is the only unambiguous signal for which national registry to query, so a multi-country configuration with no ID Verification runs nothing rather than guessing.
* **The required-field check is unchanged.** A service whose fields are not all present at run time is skipped before any provider is queried, so you are never billed for a database that was never asked.
#### Mapping Document AI and questionnaire fields
Document AI extraction fields and questionnaire questions use *your* key names, not Didit's. A key that exactly matches a Database Validation input name (`tax_number`, `date_of_birth`, `phone`, …) is mapped automatically and needs no configuration. For anything else, the workflow editor shows a picker on the Database Validation step for the fields that are still unfilled, where you point each one at the Document AI field key or questionnaire answer that supplies it.
In the Console, a service your workflow cannot yet run is shown disabled with the reason — which fields are missing and which step would provide them — and the editor offers to add that step for you. Adding it lights up every service it unblocks immediately.
**Worked example — an identifier the document does not carry.** Nigeria's National ID service queries by the 11-digit NIN, which is frequently not printed on the documents people upload, so relying on the ID document alone leaves the check skipped as not applicable for a large share of users. Put a **Questionnaire** step in front of the Database Validation step asking for the NIN, then map `national_id` to that question on the Database Validation step. The same mapping is what makes an identifier that is never on a document at all — a bank verification number, a company registration number — usable inside a workflow.
For address-based services, Didit splits the ID document's single `address` into structured address elements automatically:
| Didit field | Meaning |
| ------------------- | ----------------------------------------------------- |
| `address_element_1` | Street address, including number and street type |
| `address_element_2` | Optional unit, building, floor, or extra address line |
| `address_element_3` | Suburb, district, locality, or neighborhood |
| `address_element_4` | City, town, state, province, or region |
| `address_element_5` | Postcode or postal code |
When calling the standalone API, you can send either a single `address` or these structured fields directly. Structured fields win when both are present. Services that require address search need `address_element_1` plus at least one other address element; if Didit cannot derive those from the ID document or Proof of Address in the workflow, the service is skipped and not billed.
If the resolved country is not enabled at all, the step is skipped silently with no warning and no charge.
## The match outcome
Each enabled service returns a per-field validation:
| Field | Outcomes |
| ----------------------- | ------------------------------------------- |
| `identification_number` | `full_match` · `partial_match` · `no_match` |
| `first_name` | `full_match` · `partial_match` · `no_match` |
| `last_name` | `full_match` · `partial_match` · `no_match` |
| `date_of_birth` | `full_match` · `no_match` |
| `address` | `full_match` · `partial_match` · `no_match` |
The aggregated `match_type` rolls up to:
* **`full_match`** — every requested field agrees with at least one source.
* **`partial_match`** — some agreement; route to a review queue per your workflow rules.
* **`no_match`** — no source confirmed the data; usually decline.
## How matching is derived (1×1 / 2×2)
We do not ask you to pre-pick "1×1" or "2×2" anymore — those are derived from how many distinct services full-matched the input data:
* **1×1 outcome** — one service returned a full match (a single authoritative source confirmed the data).
* **2×2 outcome** — two or more services returned a full match (independent corroboration; the highest confidence we can express).
A workflow can enable any number of services per country; you are billed per service that actually returned a result. Services whose required fields couldn't be extracted are skipped with no charge.
➡️ See **[Matching Methods](./database-validation-matching-methods)** for the full decision logic and how it interacts with `partialMatchAction` / `noMatchAction`.
## Biometric Database Validation
Some countries' civil registries publish a biometric template (a portrait or a fingerprint). When that's available, we pair the registry lookup with a **biometric face-match** against the user's selfie:
* **Argentina (`arg_renaper`)** — biometric face-match against the official RENAPER photo on file. 100% population coverage; every Argentine citizen with a DNI is in the database.
* **Panama (`pan_cedula_sib`, `pan_cedula_sib_plus`)** — biometric face-match against the Tribunal Electoral SIB service. SIB Plus is the elevated-tier variant with stronger biometric thresholds and richer match metadata.
For these services the `selfie` field is **required**. Inside a session flow we re-use the liveness selfie (highest quality) and fall back to the document portrait. Outside a session, when calling the standalone API (`POST /v3/database-validation/`, Mode B above), you supply the JPEG/PNG yourself.
## Global identity enrichment
`glb_identity_enrichment` is a **global, standalone-only** Database Validation service. From an email and/or phone number it resolves associated persons (names, dates of birth, addresses, phones, emails and national IDs) and returns a name-match score, mapped to the standard [outcome codes](/core-technology/database-validation/database-validation-outcome-codes) `MATCH` / `PARTIAL_MATCH` / `NO_MATCH` / `DOCUMENT_NOT_FOUND` (with the corresponding `match_type` of `full_match` / `partial_match` / `no_match`).
Because it is a global (`GLB`) service keyed on email/phone rather than a document's issuing country, it is **not available in the workflow builder**. Call it directly through the standalone API with `issuing_state` set to `GLB`:
```json theme={null}
POST /v3/database-validation/
{
"issuing_state": "GLB",
"services": ["glb_identity_enrichment"],
"email": "john.doe@example.com",
"phone": "+14155552671"
}
```
It is billed at **\$0.20 per request** (charged only when the provider returns a result).
## Configuration
Database Validation is configured per-workflow from the Console. For each country you select the specific services you want to run; required and optional input fields are derived from the union of those services. The action taken on `partial_match` and `no_match` outcomes is configured separately and can be `approve`, `review`, or `decline`.
## Pricing — pay only for what you use
Database Validation is billed **per successful query, per service**, at the rate published for that service. Rates run from **\$0.05** per check (Dominican Republic via Junta Central Electoral) up to **\$7.00** for the most expensive specialist sources. Many government registry lookups sit in the **USD 0.20 to USD 0.30** band, but a good number cost considerably more because the source itself charges more — Kenya National ID (`ken_national_id`) is **\$3.15** per successful query. Price a country from its own row in the [pricing table](/getting-started/database-validation-pricing), never from a range. There are no monthly minimums, commitments, or platform fees.
You only pay when a service returns a result. Services that skip because of missing input data are not billed, and neither is a service whose source call itself fails - a registry error or an unreachable registry (`REGISTRY_ERROR` / `REGISTRY_UNAVAILABLE`) means the query was never answered, so it's not a chargeable result.
The **500 free checks per month** described on the [pricing page](/getting-started/pricing) apply to ID Verification, Passive Liveness, Face Match 1:1 and Device & IP Analysis only. Database Validation is **not** part of that free tier: every successful registry query is charged from the first one, whether it runs inside a workflow or through the standalone `POST /v3/database-validation/` API.
➡️ [Database Validation Pricing](/getting-started/database-validation-pricing) · [Sign up and get an API key](https://business.didit.me/)
## All services
## Continue reading
* [Matching Methods](./database-validation-matching-methods) — how 1×1 / 2×2 outcomes are derived and how to react to them.
* [Outcome Codes](./database-validation-outcome-codes) — the full list of provider response codes (deceased, minor, document not found, biometric mismatch, …) and how they map to session decisions.
* [Warnings](./database-validation-warnings) — what we surface when a service can't run due to missing data.
* [Reports](./database-validation-report) — monthly per-service billing exports with country, service ID, usage, unit price and total cost.
* [Supported Countries](./database-validation-supported-countries) — a static table view of every country we cover today.
# Document AI Overview
Source: https://docs.didit.me/core-technology/document-ai/overview
Collect up to 3 custom documents and extract the exact fields you define with AI, then cross-check them against other steps and your own rules.
Document AI lets you collect documents that don't fit a fixed template — proof of funds, payslips, source-of-wealth letters, tax statements, and anything else — and extract exactly the fields **you** define. You configure each document's on-screen title and description (automatically translated into every verification language) and the list of data points to read. Didit's AI extracts those fields, optionally cross-references them against other verification steps, and applies your rules to produce a status.
Already have the document on your server? Call the [Document AI API](/standalone-apis/document-ai) directly: send the file plus your field definitions in one `POST /v3/document-ai/` request and get the extracted fields, name match, and status back synchronously, with no hosted flow. \$0.20 per document.
## How it works
In the workflow builder you define up to **3 documents**. For each document you set:
| Setting | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Title & description** | Shown to the user on the upload screen. Entered once and **auto-translated** into every verification language. |
| **Fields to extract** | A list of typed fields — each with a name, an extraction instruction for the AI, a type (`text`, `number`, `date`), a required flag, and an optional **full name** marker. |
| **Name field** | Optionally mark one text field per document as the name to match. On a person (KYC) workflow it is the **full name**, compared against the verified identity; on a business (KYB) workflow it is the **company name**, compared against the verified company. `document_ai_name_mismatch_action` decides what happens on a mismatch. Optional — leave it unset to skip the name check for that document. |
Start from a [template](#starter-templates) or build from scratch.
The user uploads one file per configured document (PDF or image). Each upload is processed independently; the step completes once every required document has been provided.
Didit renders each document and runs a vision-language model with a schema built from your field definitions. Extracted values are returned typed (numbers as numbers, dates as `YYYY-MM-DD`) and stored per document. PDF/EXIF forensics run to detect manipulation.
Extracted fields can be cross-checked against other steps — for example, the document holder name against the verified ID, or any field against questionnaire answers — and against your **basic rules** and **custom status rules**. The resulting per-document statuses combine into the feature status.
## Starter templates
When you add a Document AI node you can start from a ready-made template that pre-fills the title, description, and typed fields. Customize anything afterward.
| Template | Typical fields |
| ----------------------------- | ----------------------------------------------------- |
| **Proof of Funds** | Account holder, balance, currency, statement date |
| **Proof of Income (payslip)** | Employer, employee name, gross/net salary, pay period |
| **Source of Wealth** | Source type, amount, date, institution |
| **Tax Document** | Taxpayer name, tax year, declared income, tax ID |
## Cross-referencing other steps
Document AI fields are addressable as `document_ai.` and can be compared against fields from other completed steps using a custom status rule with `value_type: "field"`. For example:
* `document_ai.account_holder` **equals** `kyc.full_name` — the document holder matches the verified identity.
* `document_ai.balance` **greater\_than** `50000` — flag high balances for review.
Matching rules fold into the decision using the platform precedence **Declined > In Review > Approved**, so a rule can raise severity but never override a stricter risk-driven status. See [Rules & warnings](/core-technology/document-ai/warnings-document-ai).
## Document requirements
| Requirement | Detail |
| ---------------------- | -------------------------------------------------------------------------- |
| **Documents per step** | Up to 3, each configured independently |
| **Supported formats** | PDF, JPG, JPEG, PNG, TIFF, WebP |
| **Pages** | Multi-page PDFs supported |
| **Quality** | Original document or high-quality scan; no digital editing or manipulation |
Document AI performs unstructured-text reading, which introduces a few seconds of latency per document. Webhooks fire only when the entire workflow step completes — do not poll the decision endpoint faster than every 5 s during this window.
# Document AI report
Source: https://docs.didit.me/core-technology/document-ai/report-document-ai
Parse Document AI results: the upload endpoint, per-document extracted fields keyed by your configuration, status, and where the feature appears in the decision.
## Overview
Document AI accepts up to 3 documents per step (PDF or image — PDF, JPG, JPEG, PNG, TIFF, WebP). Each document is classified by its configured `document_key`, read by a vision-language model using a schema built from your field definitions, and run through PDF/EXIF forensics. For every document it stores:
* The **extracted fields** — a map keyed by your configured field `key`, with values typed as you declared them (`text` → string, `number` → number, `date` → `YYYY-MM-DD`). Fields that could not be read are `null`.
* **Detected QR codes and barcodes** - decoded payloads and pixel coordinates for every rendered page. A QR code that can be located but not decoded is still returned with `parsed_payload: null`.
* The **document status** — `Approved`, `In Review`, `Declined`, or `Not Finished`.
* **Document metadata** — file forensics including any overlay/manipulation evidence.
* **Cross-check results** — the outcome of name matching against the verified identity and of any custom field cross-references.
## Uploading documents
Documents are uploaded one at a time to the Document AI endpoint. In a hosted session or SDK flow this is handled for you; the contract is:
```text theme={null}
POST https://verification.didit.me/v3/document-ai/documents/
multipart/form-data — Session-Token:
```
| Field | Required | Description |
| -------------- | -------- | ------------------------------------------------------ |
| `document_key` | yes | The configured document this file fulfills |
| `document` | yes | The file to upload |
| `locale` | no | Locale used to resolve the on-screen title/description |
The response advances the flow:
```json theme={null}
{
"valid": true,
"next_step": "DOCUMENT_AI",
"document_ai_status": "In Progress",
"uploaded_document_keys": ["proof_of_funds"],
"required_document_keys": ["proof_of_funds", "payslip"]
}
```
The step completes (`next_step` moves past `DOCUMENT_AI`) once every entry in `required_document_keys` appears in `uploaded_document_keys` and no document is left unfinished.
## Where it appears in API responses
`GET /v3/session/{sessionId}/decision/` surfaces Document AI in two places, for both User Verification (KYC) and Business Verification (KYB) sessions — the `document_ai_documents[]` array and PDF report are identical regardless of `session_kind`:
* **`features[]`** — a summary entry per Document AI node, used to enumerate which features ran:
```json theme={null}
{ "features": [ { "feature": "DOCUMENT_AI", "node_id": "feature_document_ai_1" } ] }
```
* **`document_ai_documents[]`** — the full result, one group per Document AI node. Each group carries the node's combined `status`, the list of uploaded `items`, and any `warnings`. Every uploaded document is an item with the fields you configured (under `extracted_data`), detected codes (under `detected_codes`), its own `status`, the field definitions, forensic `document_metadata`, and `cross_check_result`.
Each node's per-document statuses combine into the node status using the precedence **Declined > In Review > Approved**.
## Decision example
For a **Proof of Funds** document configured with `account_holder` (text), `balance` (number), `currency` (text), and `statement_date` (date):
```json theme={null}
{
"document_ai_documents": [
{
"node_id": "feature_document_ai_1",
"status": "Approved",
"items": [
{
"uuid": "5c1f0a2e-9b3d-4c7a-8f21-0a4e8e7d6c12",
"document_key": "proof_of_funds",
"status": "Approved",
"original_filename": "statement.pdf",
"extracted_data": {
"account_holder": "Sophia Martinez",
"balance": 18450.75,
"currency": "EUR",
"statement_date": "2026-05-31"
},
"detected_codes": [
{
"type": "QR_CODE",
"page_number": 2,
"position": [[124, 86], [306, 86], [306, 268], [124, 268]],
"x": 124,
"y": 86,
"width": 182,
"height": 182,
"page_width": 1190,
"page_height": 1684,
"raw_payload": "SU5WLTIwMjYtMDA0Mg==",
"parsed_payload": "INV-2026-0042"
}
],
"fields": [
{ "key": "account_holder", "name": "Account holder", "type": "text", "required": true },
{ "key": "balance", "name": "Balance", "type": "number", "required": false },
{ "key": "currency", "name": "Currency", "type": "text", "required": false },
{ "key": "statement_date", "name": "Statement date", "type": "date", "required": false }
],
"document_metadata": { "is_tampered": false },
"cross_check_result": null,
"created_at": "2026-05-31T10:12:00Z"
}
],
"warnings": []
}
]
}
```
`page_number` is 1-based. `position` contains the detected polygon, while `x`, `y`, `width`, and `height` provide its bounding box. All coordinates use the rendered page's pixel space, whose dimensions are `page_width` and `page_height`. `detected_codes` is an empty array when no supported code is found. Treat an item with `parsed_payload: null` as presence and location only, not as a decoded value.
Because every field is addressable as `document_ai.`, you can drive branching and [custom status rules](/core-technology/document-ai/warnings-document-ai) directly from the `extracted_data` values — including cross-references against other steps such as `kyc.full_name` or questionnaire answers.
# Document AI rules & warnings
Source: https://docs.didit.me/core-technology/document-ai/warnings-document-ai
Document AI basic rules and custom status rules: unreadable documents, missing required fields, tampering, name mismatch, and field-level cross-references.
## Overview
Document AI produces a status for each uploaded document from two layers of rules, and the per-document statuses combine into the feature status using the precedence **Declined > In Review > Approved**.
1. **Basic rules** — built-in checks, each mapped to a configurable action: `DECLINE`, `REVIEW`, or `NO_ACTION`.
2. **Custom status rules** — your own conditions on any extracted field, including cross-references against other steps.
## Basic rules
Each rule below is detected automatically during extraction and forensic analysis. Configure the action for each in the Document AI feature's **Rules** tab.
| Rule | Config field | Triggers when |
| --------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Document unreadable** | `document_ai_unreadable_document_action` | The document can't be read or no data can be extracted |
| **Missing required fields** | `document_ai_missing_required_fields_action` | A field marked **required** can't be extracted |
| **Document tampering** | `document_ai_document_tampering_action` | Forensics detect signs of manipulation |
| **Name mismatch** | `document_ai_name_mismatch_action` | The name on the document doesn't match the verified subject — the holder's identity on a person (KYC) workflow, or the company on a business (KYB) workflow (below `document_ai_name_match_score_threshold`) |
| **Unsupported file** | `document_ai_unsupported_file_action` | The uploaded file type is not supported |
| **Exceeded attempts** | `document_ai_max_attempts_exceeded_action` | The user exceeds `document_ai_max_retry_attempts` |
The **name mismatch** check compares the document's marked name field against the verified subject using a 0–100 score: on a person (KYC) workflow against the verified ID (and the expected details supplied at session creation), and on a business (KYB) workflow against the verified company (the registry-matched company, or the expected company name). Set the cut-off with `document_ai_name_match_score_threshold`.
## Custom status rules
Custom status rules let you set a status (`Approved`, `In Review`, `Declined`, or `No Action`) when a condition on an extracted field is met. Each rule is:
```json theme={null}
{
"field": "document_ai.balance",
"operator": "greater_than",
"value": 50000,
"status": "In Review"
}
```
To **cross-reference another step**, set `value_type: "field"` and point `value` at another field path:
```json theme={null}
{
"field": "document_ai.account_holder",
"operator": "fuzzy_match",
"value": "kyc.full_name",
"value_type": "field",
"score": 80,
"status": "Declined"
}
```
Every matching rule contributes its status, and the strictest of those plus the base status wins (Declined > In Review > Approved). A rule can therefore raise severity but never pull a risk-driven `Declined`/`In Review` back up. `No Action` rules are recorded but leave the status unchanged.
Extracted fields are addressable as `document_ai.` using the `key` you configured for each field. Number and date fields support comparison operators (`greater_than`, `less_than`, date ranges); text fields support equality, `contains`, and `fuzzy_match`.
# Email Verification Overview
Source: https://docs.didit.me/core-technology/email-verification/overview
Verify emails with OTP and risk assessment. Detect breached, disposable, and undeliverable addresses. Pay-per-call $0.03, no monthly minimums.
Didit's Email Verification provides a reliable method to verify user email addresses through one-time passcodes (OTP) and advanced risk assessment. This feature helps ensure valid, reachable contact information while protecting against high-risk and compromised emails.
The Academy lesson on reading a verification result reaches the email verification check at 8:34.
## How it works
Our Email Verification solution combines OTP verification with intelligence signals such as breach exposure, deliverability, and provider reputation.
The system securely collects:
| Input | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Email Address** | If not provided during session creation, the user enters their email address. If provided during session creation, the user must verify the pre-filled email. |
| **Preferred Language** | Used for localized communications when applicable. |
Our verification system:
* Generates a secure, time-limited one-time passcode
* Delivers the code via email to the provided address
* Implements deliverability best practices to maximize inbox placement
* Provides resend flows with safeguards to prevent abuse
The user completes verification by:
* Entering the received code into the verification interface
* Submitting within the configured timeframe (5 minutes)
* Requesting a new code if needed (with appropriate rate limiting)
Our system performs advanced checks:
| Check | Description |
| --------------------------------- | ------------------------------------------------------------------------- |
| **Breach Exposure** | Looks up the email across known data breaches and lists exposed services. |
| **Disposable Provider Detection** | Flags emails from temporary/disposable providers. |
| **Deliverability Checks** | Detects undeliverable or syntactically invalid addresses. |
| **Reputation Signals** | Identifies potentially high-risk email patterns. |
| **Duplicate Use** | Cross-references with historical verification data. |
Access verification results through multiple channels:
| Channel | Description |
| ------------- | ------------------------------------------------------------- |
| **Dashboard** | Real-time dashboard updates. |
| **Webhooks** | Webhook notifications. |
| **API** | RESTful API responses. |
| **Reports** | Comprehensive reports with detailed verification information. |
## Verification Features
Our Email Verification service offers several key features to enhance your verification process:
#### OTP Verification
* **Secure Code Generation**: Random, time-bound one-time passcodes
* **Email Delivery**: Reliable and localized email delivery
* **Configurable Timeouts**: Set expiration times based on your security requirements
* **Retry Options**: Allow users to request new codes with appropriate limits
#### Email Analysis
* **Syntax Validation**: Ensure the email address follows RFC-compliant format
* **Provider Checks**: Identify disposable/temporary providers
* **Deliverability Insight**: Detect undeliverable addresses
* **Breach Intelligence**: Surface known breach records with details
#### Risk Assessment
* **Exposure Detection**: Identify if the email appears in known breaches
* **Disposable Detection**: Flag temporary or throwaway emails
* **Activity Monitoring**: Track suspicious patterns across attempts
* **Blocklist Checking**: Check against internal lists of previously misused emails
# Email Verification Report
Source: https://docs.didit.me/core-technology/email-verification/report-email-verification
Parse Email Verification responses: OTP lifecycle, breach exposure, disposable and undeliverable flags, cross-session matches, and risk warnings.
The Email Verification report captures the full outcome of an email OTP challenge: who the message was sent to, whether the address is disposable or undeliverable, whether it appears in known data breaches, how many attempts the user took, and any cross-session matches against your blocklist or other approved users.
This page documents the JSON shape returned by the decision endpoint so you can parse OTP outcomes, breach exposure, and risk flags for each verified address.
## Overview
An email report is produced every time a workflow node runs the Email Verification feature. Each report represents one OTP challenge against one address and contains:
* The verified `email` address.
* Boolean risk flags (`is_breached`, `is_disposable`, `is_undeliverable`) and the supporting `breaches[]` array sourced from a breach-intelligence database.
* The count of OTP send attempts (`verification_attempts`) and the approval timestamp.
* A chronological `lifecycle[]` log of every send, retry and code-check attempt.
* A `matches[]` array surfacing the same address on other approved email verifications or your blocklist.
* A `warnings[]` array — risk events emitted during the verification (see [Email Verification warnings](/core-technology/email-verification/warnings-email-verification)).
* A `node_id` that identifies which workflow graph node produced the report (V3 sessions only).
In hosted workflow sessions the OTP challenge runs inside the Didit verification UI — you only read the result from the decision payload. For server-to-server OTP without a hosted flow, use the standalone Email API: [`POST /v3/email/send/`](/standalone-apis/email-send) delivers the code and [`POST /v3/email/check/`](/standalone-apis/email-check) validates the user's entry; finalized standalone verifications surface through the same report shape.
Hosted sessions cap the user at **2 wrong code entries** (`email_max_check_attempts`) and **2 OTP sends — the initial send plus one resend** (`email_max_retries`) by default, tunable per workflow node. Exceeding either cap finalizes the step as `Declined` with `EMAIL_CODE_ATTEMPTS_EXCEEDED`.
## Where it appears in API responses
The decision endpoint (`GET /v3/session/{sessionId}/decision/`) returns email reports under the plural array key **`email_verifications`**. The array contains one entry per Email Verification node in the workflow graph — typically one, but step-up flows may produce several.
```json theme={null}
{
"session_id": "11111111-1111-1111-1111-111111111111",
"status": "Approved",
"email_verifications": [
{ "node_id": "feature_email_1", "status": "Approved", "...": "..." }
]
}
```
A `null` value means no Email Verification step has run yet. Iterate the array (rather than reading `email_verifications[0]`) when your workflow can collect more than one email address.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#email-verification) page.
```typescript theme={null}
interface EmailVerification {
node_id: string | null;
status: "Not Finished" | "Approved" | "Declined" | "In Review" | "Expired";
email: string;
is_breached: boolean;
breaches: Breach[]; // Up to the 5 most recent known breaches
is_disposable: boolean; // Disposable / throwaway provider
is_undeliverable: boolean; // Failed syntax / DNS (MX) validation or OTP delivery
verification_attempts: number; // OTP send attempts
verified_at: string | null; // ISO 8601, null until a valid code is entered
lifecycle: EmailLifecycleEvent[]; // OTP send/retry/check timeline
warnings: Warning[]; // Risk events (see Data models → Warning object)
matches: EmailMatch[]; // Cross-session / blocklist matches
}
interface Breach {
name: string; // Breached service name
domain: string; // Affected domain
breach_date: string; // YYYY-MM-DD
breach_emails_count: number; // Total affected accounts
description: string; // HTML description of the breach
logo_path: string; // Breached-service logo URL
data_classes: string[]; // Exposed data categories (snake_case)
is_verified: boolean; // true when the breach is a verified incident
}
```
An undeliverable address — invalid syntax, a non-existent domain, missing MX records, or a failed OTP delivery — finalizes the step as **`Declined`** with the auto-decline warning `UNDELIVERABLE_EMAIL_DETECTED`. The only exception: when the user typed the address themselves (it was not pre-filled at session creation), an undeliverable send returns an inline error so they can retry with a different address instead of being declined immediately.
### Status values
| Status | Meaning |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The OTP send has been initiated but the verification has not been finalized yet. |
| `Approved` | The user entered a valid OTP and no declining risk matched. |
| `Declined` | An auto-decline warning fired (blocklist, undeliverable address, attempts exceeded) or a risk action configured to `DECLINE` matched. |
| `In Review` | A risk action configured to `REVIEW` routed the step to manual review. |
| `Expired` | The 5-minute OTP window elapsed with no valid code — applies to verifications created via the standalone email API. |
### Lifecycle event types
`lifecycle[]` is sorted chronologically. Each event has `type`, `timestamp`, `details` and a billable `fee` (when applicable):
| Event type | Emitted when |
| --------------------------------------- | ----------------------------------------------------------------------- |
| `EMAIL_VERIFICATION_MESSAGE_SENT` | First OTP send attempt — including sends that come back undeliverable. |
| `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT` | Subsequent OTP send after the user requested a resend. |
| `VALID_CODE_ENTERED` | The user submitted the correct OTP code. |
| `INVALID_CODE_ENTERED` | The user submitted a wrong or expired OTP code (counts toward the cap). |
| `EMAIL_VERIFICATION_APPROVED` | Feature-level status was set to `Approved`. |
| `EMAIL_VERIFICATION_DECLINED` | Feature-level status was set to `Declined`. |
| `EMAIL_VERIFICATION_IN_REVIEW` | Feature-level status was set to `In Review`. |
| `EMAIL_VERIFICATION_EXPIRED` | The OTP window elapsed without a valid code (standalone email API). |
The `details` payload depends on the event family:
* **Send events** (`EMAIL_VERIFICATION_MESSAGE_SENT`, `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`) — `{ status, reason }`. `status` is `Success`, `Retry` or `Undeliverable`; `reason` is `null` unless the send failed (`email_can_not_be_delivered`, or `unknown` for unrecognized legacy values).
* **Check events** (`VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`) — `{ code_tried, status }` with `status` `Approved`, `Failed`, `Expired or Not Found` or `Declined`.
* **Final status events** — `null`, except `EMAIL_VERIFICATION_DECLINED` / `EMAIL_VERIFICATION_IN_REVIEW`, which carry `{ "reason": "" }` (e.g. `UNDELIVERABLE_EMAIL_DETECTED`).
Sends with `status` `Success` or `Undeliverable` record the \$0.03 Email Verification fee; `Retry` sends, check events and status events always carry `fee: 0`.
### Cross-session matches
`matches[]` records the same address on **previously approved** email verifications in the same application — across KYC, KYB and standalone API sessions — plus blocklist hits configured in the [management-api lists](/management-api/lists/overview). Verifications sharing the current session's `vendor_data` are excluded (the same end-user does not match themselves), the array is capped at **5** entries, ordered oldest-first. When the address is on your blocklist but none of the matched sessions is blocklisted, a synthetic entry with `source: "list_entry"` (all session fields `null`) is prepended to the array.
```typescript theme={null}
interface EmailMatch {
session_id: string | null; // null when source = "list_entry"
session_number: number | null;
vendor_data: string | null;
verification_date: string | null; // ISO 8601, creation date of the matched session
email: string;
status: string | null; // Status of the matched session
is_blocklisted: boolean;
api_service: string | null; // Set when the matched session came from a standalone API
source: "session" | "list_entry"; // "list_entry" = manual blocklist hit
}
```
## Examples
### Approved — clean OTP with one informational breach
```json theme={null}
{
"node_id": "feature_email_1",
"status": "Approved",
"email": "alex.sample@example.com",
"is_breached": true,
"breaches": [
{
"name": "ExampleAir",
"domain": "example-air.com",
"breach_date": "2022-08-25",
"breach_emails_count": 6083479,
"description": "In August 2022, the airline ExampleAir suffered a data breach that exposed customers' personal information.",
"logo_path": "https:///logos/ExampleAir.png",
"data_classes": [
"dates_of_birth", "email_addresses", "genders", "names",
"nationalities", "phone_numbers", "physical_addresses",
"salutations", "spoken_languages"
],
"is_verified": true
}
],
"is_disposable": false,
"is_undeliverable": false,
"verification_attempts": 1,
"verified_at": "2025-09-15T17:36:19.963451Z",
"lifecycle": [
{
"type": "EMAIL_VERIFICATION_MESSAGE_SENT",
"timestamp": "2025-09-15T17:35:50.000000+00:00",
"details": { "status": "Success", "reason": null },
"fee": 0.03
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2025-09-15T17:36:19.000000+00:00",
"details": { "code_tried": "123456", "status": "Approved" },
"fee": 0
},
{
"type": "EMAIL_VERIFICATION_APPROVED",
"timestamp": "2025-09-15T17:36:19.963451+00:00",
"details": null,
"fee": 0
}
],
"warnings": [
{
"feature": "EMAIL",
"risk": "BREACHED_EMAIL_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "Breached email detected",
"long_description": "This email address was found in one or more known data breaches.",
"node_id": "feature_email_1"
}
],
"matches": []
}
```
### Declined — undeliverable address
A pre-filled address that fails syntax or DNS (MX) validation — or whose OTP email cannot be delivered — finalizes immediately as `Declined`. No code-check events appear because no code ever reached the user.
```json theme={null}
{
"node_id": "feature_email_1",
"status": "Declined",
"email": "user@nonexistent-domain.example",
"is_breached": false,
"breaches": [],
"is_disposable": false,
"is_undeliverable": true,
"verification_attempts": 1,
"verified_at": null,
"lifecycle": [
{
"type": "EMAIL_VERIFICATION_MESSAGE_SENT",
"timestamp": "2025-09-15T18:00:00.000000+00:00",
"details": { "status": "Undeliverable", "reason": "email_can_not_be_delivered" },
"fee": 0.03
},
{
"type": "EMAIL_VERIFICATION_DECLINED",
"timestamp": "2025-09-15T18:00:01.500000+00:00",
"details": { "reason": "UNDELIVERABLE_EMAIL_DETECTED" },
"fee": 0
}
],
"warnings": [
{
"feature": "EMAIL",
"risk": "UNDELIVERABLE_EMAIL_DETECTED",
"additional_data": null,
"log_type": "error",
"short_description": "Undeliverable email detected",
"long_description": "The system detected that the email is undeliverable, which is not allowed.",
"node_id": "feature_email_1"
}
],
"matches": []
}
```
### Declined — blocklisted disposable mailbox
The user completed the OTP, but the address matched your email blocklist (auto-decline). The disposable flag is also raised — at `information` level here because `disposable_email_action` is left at its default `NO_ACTION`.
```json theme={null}
{
"node_id": "feature_email_1",
"status": "Declined",
"email": "user@mailinator.com",
"is_breached": false,
"breaches": [],
"is_disposable": true,
"is_undeliverable": false,
"verification_attempts": 1,
"verified_at": "2025-09-15T18:05:42.201734Z",
"lifecycle": [
{
"type": "EMAIL_VERIFICATION_MESSAGE_SENT",
"timestamp": "2025-09-15T18:05:01.000000+00:00",
"details": { "status": "Success", "reason": null },
"fee": 0.03
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2025-09-15T18:05:42.000000+00:00",
"details": { "code_tried": "654321", "status": "Approved" },
"fee": 0
},
{
"type": "EMAIL_VERIFICATION_DECLINED",
"timestamp": "2025-09-15T18:05:42.201734+00:00",
"details": { "reason": "EMAIL_IN_BLOCKLIST" },
"fee": 0
}
],
"warnings": [
{
"feature": "EMAIL",
"risk": "EMAIL_IN_BLOCKLIST",
"additional_data": {
"blocklisted_session_id": null,
"blocklisted_session_number": null,
"api_service": null
},
"log_type": "error",
"short_description": "Email in blocklist",
"long_description": "The system detected that the email is in the blocklist, which is not allowed.",
"node_id": "feature_email_1"
},
{
"feature": "EMAIL",
"risk": "DISPOSABLE_EMAIL_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "Disposable email detected",
"long_description": "The system detected that the email is disposable, which is not allowed.",
"node_id": "feature_email_1"
}
],
"matches": [
{
"session_id": null,
"session_number": null,
"vendor_data": null,
"verification_date": null,
"email": "user@mailinator.com",
"status": null,
"is_blocklisted": true,
"api_service": null,
"source": "list_entry"
}
]
}
```
## Related
* [Email Verification overview](/core-technology/email-verification/overview) — feature behavior and pricing.
* [Email Verification warnings](/core-technology/email-verification/warnings-email-verification) — every risk code, decline triggers and configurable actions.
* [Data models — email verification](/reference/data-models#email-verification) — canonical field-by-field schema.
* [Email API — Send OTP](/standalone-apis/email-send) and [Check OTP](/standalone-apis/email-check) — standalone server-to-server endpoints.
* [Management API — Lists](/management-api/lists/overview) — manage the email blocklist that feeds `matches[]` with `source: list_entry`.
# Email Verification Warnings
Source: https://docs.didit.me/core-technology/email-verification/warnings-email-verification
Reference for every Email Verification risk code: BREACHED_EMAIL_DETECTED, DISPOSABLE_EMAIL_DETECTED, UNDELIVERABLE_EMAIL_DETECTED, blocklist, and OTP caps.
The Email Verification feature emits **warnings** whenever a risk signal fires on the OTP flow or the address itself: a known data breach, a disposable provider, an undeliverable mailbox, an OTP attempt cap, or a hit against your blocklist or another approved verification. This page lists every code, what triggers it, and how to configure the workflow response.
## Overview
Warnings are tagged with feature `EMAIL`. They appear on the report under `email_verifications[].warnings[]` and follow the standard [warning object](/reference/data-models#warning-object) shape (`feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`). Each warning is also routed into the per-session log so it surfaces in the Business Console under the Email section.
Some warnings are **hard auto-decline** triggers — they always set the feature status to `Declined` and carry `log_type: "error"`. The others map to a **configurable action** (`DECLINE`, `REVIEW`, or the default `NO_ACTION`) defined per workflow node or per API call, so the same risk code may decline one workflow while only flagging another. The configured action also sets the severity: `DECLINE` → `error`, `REVIEW` → `warning`, `NO_ACTION` → `information`.
## Auto-decline conditions
The following warnings always decline the Email Verification step regardless of configuration:
* `EMAIL_CODE_ATTEMPTS_EXCEEDED` — the user exhausted the OTP budget: 2 wrong code entries (`email_max_check_attempts`) or 2 OTP sends — the initial send plus one resend (`email_max_retries`) — by default.
* `EMAIL_IN_BLOCKLIST` — the address is in a blocklist managed via the [Lists API](/management-api/lists/overview), or matched a previously approved email verification that was blocklisted.
* `UNDELIVERABLE_EMAIL_DETECTED` — the address cannot receive mail: invalid syntax, a non-existent domain, missing DNS (MX) records, or the OTP email could not be delivered. The step finalizes as `Declined` immediately — no code check ever runs. Exception: when the user typed the address themselves (it was not pre-filled at session creation), an undeliverable send returns an inline error so they can retry with a different address instead of being declined.
## Configurable risks
Each of the following risks maps to a setting (per workflow node, or per call on the standalone [check endpoint](/standalone-apis/email-check)) you can configure to `DECLINE`, `REVIEW`, or `NO_ACTION`:
| Setting | Risk code | Default behavior |
| ------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------- |
| `breached_email_action` | `BREACHED_EMAIL_DETECTED` | `NO_ACTION` — surface breach details at `information` level without changing the status. |
| `disposable_email_action` | `DISPOSABLE_EMAIL_DETECTED` | `NO_ACTION` — flag the throwaway provider at `information` level without changing the status. |
| `duplicated_email_action` | `DUPLICATED_EMAIL` | `NO_ACTION` — record the duplicate at `information` level. Skipped when the address is allowlisted. |
Duplicate detection only considers **previously approved** email verifications in the same application, grouped by `vendor_data` — verifications sharing the same `vendor_data` are treated as one end-user and excluded. Leave `vendor_data` empty and every session is treated as a distinct user. If the address is on your email allowlist, the duplicate action is skipped and `EMAIL_IN_ALLOWLIST` is emitted instead.
## Verification attempt limits
The Email Verification feature applies a hard cap on OTP attempts to prevent abuse:
* **Code-entry attempts** — `email_max_check_attempts`, default **2**: the second wrong (or expired) code finalizes the step.
* **Send attempts** — `email_max_retries`, default **2** sends in total (the initial send plus one resend): a further resend request finalizes the step.
Exceeding either cap raises `EMAIL_CODE_ATTEMPTS_EXCEEDED` and auto-declines. You can tune both caps per workflow node.
## Warnings produced
| Tag | Severity | Description |
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `EMAIL_CODE_ATTEMPTS_EXCEEDED` | `error` | The user exceeded the code-entry or send cap. Auto-declines. |
| `EMAIL_IN_BLOCKLIST` | `error` | The address is present in a blocklist created via the [Lists API](/management-api/lists/overview), or matched a blocklisted approved verification. Auto-declines. `additional_data` carries `blocklisted_session_id`, `blocklisted_session_number` and `api_service` (all `null` for a pure list hit). |
| `EMAIL_IN_ALLOWLIST` | `information` | The address matched an allowlist created via the [Lists API](/management-api/lists/overview), so the duplicate-email action was skipped. |
| `BREACHED_EMAIL_DETECTED` | Per `breached_email_action` (`information` by default) | The address was found in one or more known data breaches. The `breaches[]` array on the report carries the breached service, date and data classes. |
| `DISPOSABLE_EMAIL_DETECTED` | Per `disposable_email_action` (`information` by default) | The domain is a known disposable / throwaway provider often used to evade traceability. |
| `UNDELIVERABLE_EMAIL_DETECTED` | `error` | The address cannot receive mail (invalid syntax, non-existent domain, missing MX records, or failed delivery). Auto-declines. |
| `DUPLICATED_EMAIL` | Per `duplicated_email_action` (`information` by default) | The address matches a previously approved email verification belonging to a different end-user (grouped by `vendor_data`). `additional_data` carries `duplicated_session_id`, `duplicated_session_number` and `api_service`. |
At most one of `EMAIL_IN_BLOCKLIST`, `DUPLICATED_EMAIL` and `EMAIL_IN_ALLOWLIST` fires per attempt — blocklist takes precedence over duplicate, which takes precedence over allowlist.
## Cross-session matches
When an address is detected on previously approved email verifications or on your blocklist, those hits also appear under `email_verifications[].matches[]` (capped at 5 entries). Each match carries `session_id`, `session_number`, `vendor_data`, `verification_date`, `email`, `status`, `is_blocklisted`, `api_service`, and a `source` of `session` (another approved verification) or `list_entry` (a blocklist entry). If the current address is on your email allowlist, Didit keeps the match evidence but emits `EMAIL_IN_ALLOWLIST` instead of applying the duplicate-email action. See the [Email Verification report](/core-technology/email-verification/report-email-verification#cross-session-matches) for the full match schema.
## Example
```json theme={null}
{
"warnings": [
{
"feature": "EMAIL",
"risk": "EMAIL_IN_BLOCKLIST",
"additional_data": {
"blocklisted_session_id": null,
"blocklisted_session_number": null,
"api_service": null
},
"log_type": "error",
"short_description": "Email in blocklist",
"long_description": "The system detected that the email is in the blocklist, which is not allowed.",
"node_id": "feature_email_1"
},
{
"feature": "EMAIL",
"risk": "DISPOSABLE_EMAIL_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "Disposable email detected",
"long_description": "The system detected that the email is disposable, which is not allowed.",
"node_id": "feature_email_1"
}
]
}
```
## Warning types
Each risk is assigned a severity based on your application's configuration. Severities fall into three categories:
## Related
* [Email Verification overview](/core-technology/email-verification/overview) — feature behavior and pricing.
* [Email Verification report](/core-technology/email-verification/report-email-verification) — full response shape including `lifecycle[]` and `matches[]`.
* [Data models — email verification](/reference/data-models#email-verification) — canonical field-by-field schema.
* [Management API — Lists](/management-api/lists/overview) — manage the email blocklist used by `EMAIL_IN_BLOCKLIST`.
# Face Match 1:1
Source: https://docs.didit.me/core-technology/face-match/overview
Compare live selfies against ID document photos with AI facial recognition. Pay-per-call $0.05, 500 free/month, sub-2-second response.
In the Academy lesson on reading a verification result, the 6:16 chapter covers the face match check inside a real session.
Powered by cutting-edge AI, computer vision, and biometric technology, our solution ensures fast, accurate, and secure identity verification at scale. Designed to combat fraud, simplify compliance, and enhance user experience, Didit provides a robust and trustworthy platform that meets the highest industry standards.
## Retries & actionable feedback
In a Didit session, a face match that fails for a **fixable capture reason** (the selfie is too dark, blurry, off-center, or the similarity is borderline) does not immediately decline. The user is asked to **retry with guidance**, up to a configurable number of attempts — the same model used for [Liveness](/core-technology/liveness/overview).
* **Attempt budget** — `face_match_max_attempts` controls the total number of face-match submissions per session. Default **3** (one initial attempt plus two retries), configurable per workflow node (range 1–3).
* **Retry feedback** — while attempts remain, a retryable failure keeps the user on the step and returns `feedback_codes` (the Web and Mobile SDKs surface these as coaching messages automatically):
```json theme={null}
{
"detail": "Make sure your face matches your document photo, improve lighting and try again.",
"error": "LOW_FACE_MATCH_SIMILARITY",
"feedback_codes": ["LOW_FACE_MATCH_SIMILARITY"],
"attempts_used": 1,
"max_attempts": 3
}
```
* **Exhaustion** — once the budget is exhausted the computed status is applied (`Declined` or `In Review`).
Retries apply to the **session flow** only. The standalone `POST /v3/face-match/` endpoint is stateless and single-shot — it returns the similarity score directly with no retry budget.
## How It Works
Effortlessly begin the verification process with our intuitive, AI-driven capture system.
Users upload or photograph their ID documents with real-time assistance:
| Feature | Description |
| ---------------------- | ---------------------------------------------------------------------- |
| **Auto-detection** | Identifies document type and issuing country automatically |
| **Real-time guidance** | Visual cues for optimal positioning, lighting, and focus |
| **Smart capture** | Automatic capture when conditions are ideal — no manual retries needed |
| **Broad support** | Passports, driver's licenses, national ID cards, and residence permits |
*Why it matters*: Our intelligent capture reduces user friction and ensures high-quality submissions on the first attempt, boosting conversion rates and trust.
Extract and validate identity data with unmatched precision.
**Data Extraction** — State-of-the-art technology processes all key fields:
| Capability | Details |
| -------------------- | ------------------------------------------------------------------------------------ |
| **Field extraction** | Full name, date of birth, document number, issue/expiry dates, nationality, and more |
| **OCR** | High-precision optical character recognition for text |
| **MRZ parsing** | Machine-Readable Zone parsing and barcode decoding |
**Data Validation**:
* Cross-references data between visual zones, MRZ, and barcodes for consistency
* Format and pattern matching to detect anomalies
* Real-time queries against government databases (where permitted) for authoritative verification
*Why it matters*: Comprehensive data processing ensures accuracy and eliminates errors, giving you confidence in every verification.
Our AI-powered system performs comprehensive checks:
* Document authenticity verification
* Tamper detection and image integrity analysis
* Security feature validation (holograms, watermarks, etc.)
* Template matching against certified database
**Document liveness detection** prevents fraud from:
| Attack type | Description |
| --------------------- | --------------------------------------------------- |
| **Screen captures** | Digital documents photographed from a screen |
| **Screen replay** | Photos of documents displayed on screens |
| **Printed copies** | Physical reproductions of original documents |
| **Altered portraits** | Manipulated documents with swapped or edited photos |
Get actionable insights instantly with flexible delivery options.
**Real-Time Results**:
| Channel | Description |
| ------------- | ----------------------------------------------------- |
| **Dashboard** | Immediate updates via an intuitive dashboard |
| **Webhooks** | Instant webhook notifications for automated workflows |
| **REST API** | Seamless integration into your existing systems |
**Comprehensive Reporting**:
* Detailed PDF reports with verification outcomes and evidence
* Audit trails for compliance and record-keeping
* Customizable options to align with your operational needs
*Why it matters*: Fast, accessible results empower your team to act quickly while maintaining a secure, auditable process.
***
## Document Requirements
For optimal verification success, documents must meet these standards:
#### General Requirements
* Government-issued and valid within its configured validity period
* Physically intact (no damage, scratches, or stains obscuring details)
* All critical information (full name, date of birth, MRZ, etc.) clearly legible
* Consistent data across all submitted documents
#### Image Requirements
* Original, real-time photo (no screenshots, scans, or digital copies)
* Supported formats: JPG, JPEG, PNG, PDF
* Maximum file size: 5MB
* Full-color image with all document corners visible
* Free from glare, shadows, digital editing, or manipulation
* Physical documents required (digital IDs supported only in select regions where officially recognized)
## Additional Settings by Country & Document Type
Configure fine-grained rules per country and document type directly from the console. These controls let you tailor acceptance criteria and transcription preferences to your compliance needs.
* **Expiration mode**: Choose how to handle documents with past expiration dates.
* **Reject expired**: If the document's expiration date is earlier than today, mark it as expired and reject.
* **Allow expired**: Do not flag or block documents with a past expiration date.
* **Preferred character format**: Select how extracted names and fields should be normalized.
* **Prefer Latin characters (A–Z)**: Example: "Mohammed"
* **Prefer original script (non‑Latin)**: Example: "محمد"
* **Regional support & subtypes**: Enable documents by region and specify acceptable subtypes when a document type contains many variations.
* Example: For United States driver's licenses, select the exact subtypes you accept (e.g., Arizona Commercial Driver License, Indiana Operator License (REAL ID), New York Enhanced Driver License, etc.).
* Use the "Accepted subtypes" control to quickly include/exclude many variants (e.g., "128 selected").
> Tip: These settings apply per country and document type, so you can be strict in some markets while more permissive in others.
# Face match report
Source: https://docs.didit.me/core-technology/face-match/report-face-match
How to read Didit's face match 1:1 report: status, similarity score, source/target image URLs, warnings, and where the data appears in API responses.
## Overview
The **face match report** captures a 1:1 biometric comparison between the live selfie captured during liveness and a reference image — most commonly the portrait extracted from the ID document. It returns a single 0–100 similarity score, signed URLs for the two compared images, the source session for the reference image, and any warnings raised by the match.
The report is produced after the user completes both the liveness step and the document step in a workflow (or, for portrait-based workflows such as biometric authentication, after liveness alone), or directly via the standalone [Face Match API](/standalone-apis/face-match) when you supply two arbitrary face images yourself.
Each report carries its own `status` — independent of the overall session status — that reflects how the face match step alone resolved:
* **Approved** — `score` is above the review threshold and no auto-decline warning fired.
* **In Review** — `score` is between the decline and review thresholds.
* **Declined** — `score` is at or below the decline threshold, or `NO_REFERENCE_IMAGE` fired.
* **Resub Requested** — a reviewer requested the user resubmit this step from the console.
* **Not Finished** — the face match step has not produced a result yet (including while the user is retrying a failed capture).
## Where it appears in API responses
The face match report ships inside the `face_matches[]` array — **always a JSON array**, never a singular `face_match` object. It is `null` until at least one face-match step has produced data, and multiple entries appear when a workflow runs face match more than once.
* **Session decision API** — `GET /v3/session/{sessionId}/decision/` returns `face_matches[]` at the top level. See [Retrieve session decision](/sessions-api/retrieve-session).
* **Webhooks** — `session.status.updated` payloads include the same `face_matches[]` array once the step has produced data. See [Webhooks](/integration/webhooks).
* **Standalone Face Match API** — for direct 1:1 comparisons, see [Face Match standalone API](/standalone-apis/face-match).
Read `response.face_matches[0]` and iterate the array — the singular `face_match` shape some older tutorials referenced does not exist on the v3 decision endpoint.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#face-match) reference page. The fields below mirror that canonical schema.
| Field | Type | Description |
| ------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status` | `"Approved" \| "Declined" \| "In Review" \| "Resub Requested" \| "Not Finished"` | Face match step status (`FeatureStatusChoices`). |
| `node_id` | string \| null | Workflow graph node that produced this report. |
| `score` | number 0–100 \| null | Biometric similarity score, rounded to 2 decimals. `null` when no comparison was possible. |
| `source_image_session_id` | string \| null | UUID of the session whose stored portrait supplied the reference (source) image — set in portrait-based flows (biometric authentication, or a portrait supplied at session creation). `null` when the reference image is the document portrait captured in the same session. |
| `source_image` | string (signed URL) | The reference image (usually the document portrait). Short-lived signed URL — download promptly, never persist the URL. |
| `target_image` | string (signed URL) | The target image (usually the liveness selfie). Short-lived signed URL — download promptly, never persist the URL. |
| `warnings[]` | array | Module-level warnings. Each entry carries `feature` (always `"FACEMATCH"`), `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, and `node_id` — see [Face match warnings](/core-technology/face-match/warnings-face-match). |
## Status values
| Status | Meaning | Downstream effect |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `Approved` | Score above the review threshold and no auto-decline warning fired. | Counts as a successful face match. |
| `In Review` | Score above the decline threshold but at or below the review threshold. | Session also moves to `In Review` until a reviewer acts. |
| `Declined` | Score at or below the decline threshold, or `NO_REFERENCE_IMAGE` fired. | Session is declined unless another approved branch satisfies the workflow. |
| `Resub Requested` | A reviewer requested resubmission of this step from the console. | The user is asked to capture again. |
| `Not Finished` | The face match step has not produced a result (an input was missing, the workflow never reached the compare step, or the user is still inside the capture-retry loop). | The branch did not complete. |
Low-similarity failures are retried before the status sticks: the user gets up to `face_match_max_attempts` captures (3 by default), and only the final attempt's score is applied. Custom status rules configured on the workflow node can further adjust the resulting status, combining with the score-derived status under `Declined` > `In Review` > `Approved` precedence.
## Examples
### Approved — clean match
```json theme={null}
{
"face_matches": [
{
"status": "Approved",
"node_id": "feature_face_match_1",
"score": 96.42,
"source_image_session_id": null,
"source_image": "https:///source-image.jpg",
"target_image": "https:///target-image.jpg",
"warnings": []
}
]
}
```
### In Review — borderline similarity
```json theme={null}
{
"face_matches": [
{
"status": "In Review",
"node_id": "feature_face_match_1",
"score": 65.43,
"source_image_session_id": null,
"source_image": "https:///source-image.jpg",
"target_image": "https:///target-image.jpg",
"warnings": [
{
"feature": "FACEMATCH",
"risk": "LOW_FACE_MATCH_SIMILARITY",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face match similarity",
"long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"node_id": "feature_face_match_1"
}
]
}
]
}
```
### Declined — no reference image to compare against
```json theme={null}
{
"face_matches": [
{
"status": "Declined",
"node_id": "feature_face_match_1",
"score": null,
"source_image_session_id": null,
"source_image": null,
"target_image": "https:///target-image.jpg",
"warnings": [
{
"feature": "FACEMATCH",
"risk": "NO_REFERENCE_IMAGE",
"additional_data": null,
"log_type": "error",
"short_description": "No source image found for performing face match",
"long_description": "A reference image for facial comparison is missing, preventing the system from completing the face matching process.",
"node_id": "feature_face_match_1"
}
]
}
]
}
```
## Security note
The signed URLs for `source_image` and `target_image` are biometric data and expire after a short validity window. Treat them as short-lived: do not cache or surface them publicly. Typically your application only needs `status` and `score` — store as little as possible to minimize biometric data on your servers.
## Related
* [Face match warnings](/core-technology/face-match/warnings-face-match) — full warning enum, causes, and remediation.
* [Liveness report](/core-technology/liveness/report-liveness) — the upstream step that captures the target image.
* [Webhooks](/integration/webhooks) — listen for `session.status.updated` to receive the report.
* [Data models — Face match](/reference/data-models#face-match) — canonical schema with every field.
# Face match warnings
Source: https://docs.didit.me/core-technology/face-match/warnings-face-match
Every warning Didit's face match 1:1 module emits — low similarity, missing reference image, exhausted retries — with cause, severity, and remediation.
## Overview
Warnings on the face match report flag every condition Didit observed while running the 1:1 face comparison. They land in the `warnings[]` array on each item of `face_matches[]` (see [Face match report](/core-technology/face-match/report-face-match)). Each entry also carries `feature: "FACEMATCH"` and the `node_id` of the workflow node that raised it.
Every warning has three layers:
1. **The `risk` code** — a stable identifier you can match on in your code.
2. **The `log_type`** — `information`, `warning`, or `error`, derived from your workflow's similarity-score thresholds.
3. **The decision impact** — set by the workflow's review and decline thresholds for the similarity score.
The face match module is intentionally small — there are exactly five risk codes, listed in full below.
## Auto-decline conditions
The following risk always forces the face match report (and the session) to `Declined`:
| Risk | Trigger |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `NO_REFERENCE_IMAGE` | One of `source_image` (reference) or `target_image` (selfie) is missing, so no comparison was possible. |
When `NO_REFERENCE_IMAGE` fires, `LOW_FACE_MATCH_SIMILARITY` is suppressed (there is nothing to score).
## Configurable verification settings
In the Didit console, the face match similarity score is governed by two thresholds (no Approve/Review/Decline toggle):
| Setting | Default | Behavior |
| ------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------- |
| `face_match_score_review_threshold` | 70 | When `score` ≤ this value, `LOW_FACE_MATCH_SIMILARITY` fires and the status moves to `In Review`. |
| `face_match_score_decline_threshold` | 50 | When `score` ≤ this value, the same warning escalates to `error` and the status moves to `Declined`. |
Tune the review threshold higher if you observe too many false approvals; tune the decline threshold lower if you observe too many false declines.
A separate setting controls what happens when a similarity score **cannot be computed at all** because no face was detected in one of the images:
| Setting | Default | Behavior |
| -------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `face_match_not_computed_action` | `REVIEW` | Action applied once the "could not compute" outcome is terminal. `REVIEW` sends the report to `In Review`; `DECLINE` sets it to `Declined`. (There is no auto-approve option — a score that was never computed is never silently passed.) |
This action governs both `FACE_MATCH_NOT_COMPUTED` (no face in the document portrait) and an exhausted `NO_FACE_IN_SELFIE` (no face in the selfie after the user's retries). When the selfie is the problem, the user is first asked to recapture — see below — and the action only applies if a face still cannot be detected once the attempt budget runs out.
Low-similarity failures also enter a capture-retry loop before the status sticks: the user gets up to `face_match_max_attempts` total attempts (default 3, configurable 2–5 per workflow node) to recapture. A `NO_FACE_IN_SELFIE` failure uses the same retry loop, since recapturing a clearer selfie can fix it. A `FACE_MATCH_NOT_COMPUTED` failure does **not** retry — the document portrait cannot be recaptured at this step. Only when the budget is exhausted does the last attempt's status apply — and `FACE_MATCH_MAX_ATTEMPTS_EXCEEDED` is logged to record it.
On the standalone [Face Match API](/standalone-apis/face-match) only a decline threshold exists (`face_match_score_decline_threshold` request option, default 30); any fired warning declines the request, and `NO_REFERENCE_IMAGE` fires when no face could be compared at all.
## Warnings produced
| Risk | Cause | Severity | Affects status? | Remediation |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `LOW_FACE_MATCH_SIMILARITY` | `score` ≤ `face_match_score_review_threshold`. Suppressed when `NO_REFERENCE_IMAGE` is also present. | `warning` between the thresholds; `error` at or below the decline threshold | `In Review` between the thresholds; `Declined` at or below the decline threshold | Tune thresholds, or accept via manual review. |
| `FACE_MATCH_NOT_COMPUTED` | A similarity score could not be computed because no face was detected in the **document portrait**. Suppressed when `NO_REFERENCE_IMAGE` is also present. | `warning` when `face_match_not_computed_action` is `REVIEW`; `error` when `DECLINE` | `In Review` or `Declined`, per `face_match_not_computed_action` | The document image cannot be recaptured at this step — resolve via manual review, or recapture the document earlier in the flow. |
| `NO_FACE_IN_SELFIE` | A similarity score could not be computed because no face was detected in the user's **selfie**. The user is asked to retake the selfie before this sticks. | `warning` when `face_match_not_computed_action` is `REVIEW`; `error` when `DECLINE` | `In Review` or `Declined`, per `face_match_not_computed_action`, after retries are exhausted | The user recaptures a clearer, well-lit, front-facing selfie. |
| `NO_REFERENCE_IMAGE` | Either the reference image or the target image is missing. | `error` | **Auto-decline** | Ensure both the document portrait and the liveness selfie are captured. For the standalone API, pass two valid face images. |
| `FACE_MATCH_MAX_ATTEMPTS_EXCEEDED` | The user exhausted the capture-retry budget (`face_match_max_attempts`) after repeated low-similarity failures. | `information` | No — records that the last attempt's computed status (decline or review) was applied | Raise `face_match_max_attempts` on the workflow node, or resolve via manual review. |
## Examples
### In Review — borderline similarity
```json theme={null}
"warnings": [
{
"feature": "FACEMATCH",
"risk": "LOW_FACE_MATCH_SIMILARITY",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face match similarity",
"long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"node_id": "feature_face_match_1"
}
]
```
### In Review — face match could not be computed
```json theme={null}
"warnings": [
{
"feature": "FACEMATCH",
"risk": "FACE_MATCH_NOT_COMPUTED",
"additional_data": null,
"log_type": "warning",
"short_description": "Face match could not be computed",
"long_description": "A face match score could not be computed because a face could not be detected in the document portrait. Because the document image cannot be recaptured, the result follows the configured action for this case (manual review by default) rather than being treated as a low-similarity mismatch.",
"node_id": "feature_face_match_1"
}
]
```
### Auto-decline — missing reference
```json theme={null}
"warnings": [
{
"feature": "FACEMATCH",
"risk": "NO_REFERENCE_IMAGE",
"additional_data": null,
"log_type": "error",
"short_description": "No source image found for performing face match",
"long_description": "A reference image for facial comparison is missing, preventing the system from completing the face matching process.",
"node_id": "feature_face_match_1"
}
]
```
### Retries exhausted — last attempt's status applied
```json theme={null}
"warnings": [
{
"feature": "FACEMATCH",
"risk": "LOW_FACE_MATCH_SIMILARITY",
"additional_data": null,
"log_type": "error",
"short_description": "Low face match similarity",
"long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.",
"node_id": "feature_face_match_1"
},
{
"feature": "FACEMATCH",
"risk": "FACE_MATCH_MAX_ATTEMPTS_EXCEEDED",
"additional_data": null,
"log_type": "information",
"short_description": "Maximum face match attempts exceeded",
"long_description": "The maximum number of face match capture attempts has been reached. The last attempt's computed status (decline or review) has been applied.",
"node_id": "feature_face_match_1"
}
]
```
The standalone [Face Match API](/standalone-apis/face-match) returns the same warning shape minus `node_id` (the comparison is not bound to a workflow node).
## Warning types
Each risk is assigned a severity based on your application's configuration. The three severities are:
## Related
* [Face match report](/core-technology/face-match/report-face-match) — full report schema and statuses.
* [Liveness warnings](/core-technology/liveness/warnings-liveness) — upstream warnings on the selfie capture.
* [Webhooks](/integration/webhooks) — `session.status.updated` carries the warnings as soon as the step finishes.
* [Data models — Face match](/reference/data-models#face-match) — canonical schema.
* [Data models — Warning object](/reference/data-models#warning-object) — the shape of every entry in `warnings[]`.
# Face Search 1:N
Source: https://docs.didit.me/core-technology/face-search/overview
Search faces across all verified users to spot duplicates and blocklisted fraudsters. Free with Didit identity verification, sub-2-second response.
Face Search is a powerful feature that allows you to search for a specific face across all your approved identity verification sessions. This capability helps identify duplicate accounts, prevent fraud, and enhance your security measures.
The Academy lesson on reading a verification result reaches face search and block lists at 6:53.
## Automatic Face Search Integration
> Face Search is automatically performed during liveness checks in verification sessions to detect duplicate users and check against blocklisted faces.
### Automatic Duplicate Detection
When a user completes a liveness check during identity verification:
* Their facial biometrics are automatically compared against all previously verified users
* The system identifies potential duplicate accounts based on facial similarity
* Matches are flagged according to your configured similarity thresholds
* You can review and take action on potential duplicate users
### Blocklist Integration
Face Search seamlessly integrates with the [blocklist feature](/management-api/lists/overview):
* During verification, faces are automatically checked against your blocklist
* If a match to a blocklisted face is found, the verification is automatically declined
* This prevents previously identified problematic users from creating new accounts
* Helps maintain the integrity of your verification process
### API Access
Face Search functionality is also available through our [API](/standalone-apis/face-search), allowing you to:
* Programmatically submit face searches
* Integrate face matching capabilities into your own applications
* Build custom fraud detection workflows
* Create automated systems for duplicate detection
## Key Features
* **High Accuracy**: Advanced biometric algorithms provide reliable match results
* **Configurable Thresholds**: Customize match sensitivity based on your risk tolerance
* **Comprehensive Scanning**: Search across all your verified users
* **Rapid Results**: Process searches quickly even with large user databases
* **Privacy-Focused**: Matching uses numeric face templates scoped to your application; the index never leaves your tenant
### Configurable Thresholds
You can customize search sensitivity by setting different thresholds for similarity scores:
These thresholds can be adjusted based on your risk tolerance and security requirements.
## How It Works
When a search is initiated, the system processes the reference image:
| Process | Description |
| ---------------------- | -------------------------------------------------------- |
| **Feature extraction** | Isolates facial features from the reference image |
| **Normalization** | Standardizes facial data for consistent comparison |
| **Quality validation** | Checks image quality and facial clarity |
| **Vector encoding** | Creates a mathematical vector representation of the face |
The system searches across your entire database of verified sessions:
* Compares the reference facial vector against **every face enrolled in your application**: session faces, faces uploaded to User profiles, your face lists, and any retained biometric templates
* Employs advanced neural network architecture optimized for speed and accuracy
* Supports two search modes: **most similar** (ranked list) and **blocklisted or approved** (status-filtered)
* Processes large databases rapidly using optimized indexing
For each comparison, a similarity percentage is generated:
| Score Range | Interpretation |
| :-----------: | ------------------------------------------ |
| **90%+** | Strong match — very likely the same person |
| **70–89%** | Possible match — may require manual review |
| **Below 70%** | Likely different individuals |
Your configured match thresholds determine which results are flagged.
The system returns a comprehensive result set:
* **Ranked list** of potential matches sorted by similarity score
* **Match details** including session ID, verification date, and vendor data
* **Similarity percentage** for each match
* **Match images** available for visual review
* **Blocklist status** indicating if the matched face is blocklisted
## Deleted sessions and retained biometric templates
Deleting a session deletes its face embedding by default, so the person drops out of the index and can verify again without a duplicate flag. Applications that delete sessions soon after approval but still need to catch repeat sign-ups can enable [biometric-template retention](/console/data-retention#biometric-template-retention). Didit then keeps one image-free biometric template anchored to the User after the session is deleted, and that template takes part in Face Search and automatic duplicate detection exactly like a live session face.
A hit on a retained template is reported with `source: "retained_template"`, the owning User's `vendor_user_id` and `vendor_data`, and a `biometric_template_id`. It carries no session id, match image, identity details, or session status, because the session that produced the face no longer exists. Retained templates are never blocklisted; face blocklist entries keep their own independent biometric entry.
The template stays until its scheduled expiry, User deletion, a privacy-erasure request, or an explicit purge through the [Biometric Templates API](/management-api/biometric-templates/overview) or **Lists → Biometric templates** in the Console.
## Similarity Percentage
The similarity percentage is the core metric used to determine potential matches:
* **High percentage (typically 90% and above)**: Indicates a strong likelihood that the faces belong to the same person.
* **Medium percentage (70-89%)**: Suggests possible matches that may require further review.
* **Low percentage (below 70%)**: Likely indicates different individuals.
The exact threshold for what constitutes a "match" can be configured based on your security requirements. Increasing the threshold reduces false positives but may increase false negatives.
## Use Cases
* **Fraud Prevention**: Identify users attempting to create multiple accounts
* **Enhanced KYC**: Add an additional layer of verification to your KYC process
* **Regulatory Compliance**: Meet requirements for detecting duplicate accounts
* **Access Control**: Verify user authenticity for high-security areas
* **Law Enforcement**: Assist authorized agencies in identifying persons of interest
# Face search report
Source: https://docs.didit.me/core-technology/face-search/report-face-search
Read the Face Search 1:N response: matched sessions, similarity percentages, blocklist and allowlist flags, and user details for fraud investigation and duplicate detection.
## Overview
Face Search is a **1:N** biometric search: Didit takes the largest detected face in your `user_image` and searches it against your application's face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists. It is distinct from Face Match, which is **1:1** — Face Match compares a selfie against the portrait on a specific document, while Face Search asks "have we seen this person anywhere before?".
Two behaviors are worth understanding before you parse the report:
* **Only blocklist matches decline.** `face_search.status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires. Duplicate matches alone return `Approved` — inspect `matches` and `warnings`, not just `status`.
* **Optional persistence and enrollment.** When `save_api_request=true` (the default), the call is persisted as an API-type session and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are **excluded from future Face Search results**, so repeated searches never match each other. Pass `save_api_request=false` for a one-shot query: nothing is stored and the face is not enrolled.
## Where the results appear
Face Search is a standalone API. The search results are returned **synchronously, in the `POST /v3/face-search/` response only**, as a singular `face_search` object at the top level:
```text theme={null}
POST /v3/face-search/ ──▶ { "request_id": …, "face_search": { … }, "vendor_data": …, "metadata": …, "created_at": … }
```
There is no plural `face_searches[]` array anywhere — the plural-array convention used by `id_verifications[]`, `aml_screenings[]`, and `liveness_checks[]` on the session decision endpoint does not apply.
When `save_api_request=true` (the default), the call is also persisted as an API-type session:
* **Business Console** — the session appears in your sessions list with the search image, matches, and warnings.
* **`GET /v3/session/{sessionId}/decision/`** — retrievable using the returned `request_id` as the session id. The persisted data surfaces as a `FACE_SEARCH` entry in `features[]` and as a `liveness_checks[]` item carrying the stored `matches` (with freshly signed `match_image_url` values) and `warnings` — **not** as a `face_search` object.
When `save_api_request=false`, `request_id` is a transient correlation UUID: the session is never stored, so it cannot be fetched from the Console or the decision endpoint.
## Schema
See [Face Search in the Data Models reference](/reference/data-models#face-search) for the canonical field tables. The shape below reflects what the endpoint actually returns at runtime.
```typescript theme={null}
interface FaceSearchResponse {
request_id: string; // UUID — the persisted session id when save_api_request=true
face_search: {
status: 'Approved' | 'Declined';
total_matches: number; // 0-5, length of matches[]
matches: FaceSearchMatch[]; // capped at 5, ordered by similarity
user_image: { // always present
entities: {
bbox: [number, number, number, number];
confidence: number; // 0-1, one entry per detected face
}[];
best_angle: 0 | 90 | 180 | 270;
};
warnings: Warning[];
};
vendor_data: string | null; // echoed from the request
metadata: object | null; // echoed from the request
created_at: string; // ISO 8601, e.g. "2026-06-12T01:04:42.763237+00:00"
}
interface FaceSearchMatch {
session_id: string | null; // UUID of the matched session; null for non-session matches
session_number: number | null; // Console-friendly number; null for non-session matches
similarity_percentage: number; // 0-100
source: 'session' | 'imported' | 'list_entry' | 'retained_template';
vendor_data: string | null; // the matched session's, imported profile's, or retained template's User vendor_data
vendor_user_id: string | null; // UUID of the linked User; null for list_entry matches and sessions without a User
biometric_template_id: string | null; // set only for retained_template matches
verification_date: string | null; // "YYYY-MM-DDThh:mm:ssZ"; null for list_entry matches; the retention date for retained_template matches
user_details: {
full_name: string | null;
document_type: string | null;
document_number: string | null;
} | null; // null when the matched session has no document data
match_image_url: string | null; // signed URL when save_api_request=true; raw storage path otherwise; null for retained_template matches
status: 'Approved' | 'Declined' | 'In Review' | null; // the matched session's status
is_blocklisted: boolean;
is_allowlisted: boolean;
api_service: 'ID_VERIFICATION' | 'FACE_MATCH' | 'AGE_ESTIMATION' | 'POA' | 'AML'
| 'PASSIVE_LIVENESS' | 'DATABASE_VALIDATION' | 'PHONE_VERIFICATION'
| 'EMAIL_VERIFICATION' | null; // null when the match comes from a workflow session
}
interface Warning {
risk: string;
feature: 'LIVENESS'; // always LIVENESS on this endpoint
additional_data: object | null;
log_type: 'error' | 'warning' | 'information';
short_description: string;
long_description: string;
}
```
Field notes:
* **`matches[]` is capped at 5** entries above the similarity floor, ordered by descending similarity (`search_type=blocklisted_or_approved` ranks blocklisted entries first, then allowlisted, then similarity).
* **`source`** tells you where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a user profile; `list_entry` — a face attached to one of your lists; `retained_template` — an image-free [biometric template](/management-api/biometric-templates/overview) the application retained after deleting the source session. `session_id`, `session_number`, `status`, and `api_service` are `null` for `imported`, `list_entry`, and `retained_template` matches.
* **`retained_template` matches** carry `vendor_user_id`, `vendor_data`, and `biometric_template_id`, and always `null` for `match_image_url`, `user_details`, and everything session-related: the session that produced the face no longer exists and the template holds no image or identity data. `is_blocklisted` is always `false` for a retained template; a blocklisted face is reported separately as a `list_entry` match. Purge the template with `DELETE /v3/biometric-templates/{biometric_template_id}/`.
* **`api_service`** is the matched session's standalone API type (uppercase, e.g. `PASSIVE_LIVENESS`) and `null` when the matched face came from a regular workflow verification session.
* **`user_details`** comes from the matched session's document verification. For `source: imported` matches it is populated from the user profile instead (`full_name` set, `document_type`/`document_number` null); it is `null` when no identity data exists (for example liveness-only sessions).
* **Warnings use a reduced shape** compared with workflow warnings: there is no `node_id`. The documented response contract (`FaceSearchResponseSerializer`) declares the five fields `risk`, `additional_data`, `log_type`, `short_description`, `long_description`; the runtime payload additionally carries `feature`, which is always `"LIVENESS"` here. See [Face Search warnings](/core-technology/face-search/warnings-face-search).
* **Same-user matches are not excluded** — unlike workflow duplicate detection, the standalone endpoint does not filter out prior sessions with the same `vendor_data`, so a returning legitimate user matches their own earlier sessions.
## Status values
The top-level `face_search.status` is derived from `warnings[]`. The match-level `status` reflects the **matched session's** verification outcome and is independent.
| Value | When it appears |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `Approved` | No blocklist warning fired. Includes the case where duplicate matches were found — duplicates are informational signals, not declines. |
| `Declined` | A `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired — the search image matched a face in your face blocklist. |
Note: when the search image contains no detectable face the API returns HTTP `400` with `{"error": "No face detected in the image"}` **before** a `face_search` object is built — there is no `Declined` status for that case. Insufficient credits return HTTP `403` before any image processing.
## Examples
### Approved — duplicate match, no blocklist
```json theme={null}
{
"request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f",
"face_search": {
"status": "Approved",
"total_matches": 1,
"matches": [
{
"session_id": "1f2e3d4c-5b6a-7980-1122-334455667788",
"session_number": 1024,
"similarity_percentage": 87.42,
"source": "session",
"vendor_data": "user-9",
"verification_date": "2025-11-20T09:15:00Z",
"user_details": {
"full_name": "Jane Marie Doe",
"document_type": "Passport",
"document_number": "X1234567"
},
"match_image_url": "https:///face/9c8b7a6d/reference.jpg?signature=...",
"status": "Approved",
"is_blocklisted": false,
"is_allowlisted": false,
"api_service": null
}
],
"user_image": {
"entities": [
{
"bbox": [40, 40, 120, 120],
"confidence": 0.732973
}
],
"best_angle": 0
},
"warnings": [
{
"risk": "DUPLICATED_FACE",
"feature": "LIVENESS",
"additional_data": {
"duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788",
"duplicated_session_number": 1024,
"api_service": null
},
"log_type": "information",
"short_description": "Duplicated face from other approved session",
"long_description": "The system identified a duplicated face from another approved session, requiring further investigation."
}
]
},
"vendor_data": "user-123",
"metadata": null,
"created_at": "2026-06-12T01:04:42.763237+00:00"
}
```
### Declined — blocklist match
```json theme={null}
{
"request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"face_search": {
"status": "Declined",
"total_matches": 1,
"matches": [
{
"session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"session_number": 323442,
"similarity_percentage": 99.99,
"source": "session",
"vendor_data": "user-1",
"verification_date": "2025-01-01T00:00:00Z",
"user_details": {
"full_name": "Jane Marie Doe",
"document_type": "ID",
"document_number": "X1234567"
},
"match_image_url": "https:///face/3f6a1c2e/reference.jpg?signature=...",
"status": "Approved",
"is_blocklisted": true,
"is_allowlisted": false,
"api_service": "PASSIVE_LIVENESS"
}
],
"user_image": {
"entities": [
{
"bbox": [40, 40, 120, 120],
"confidence": 0.732973
}
],
"best_angle": 0
},
"warnings": [
{
"risk": "FACE_IN_BLOCKLIST",
"feature": "LIVENESS",
"additional_data": {
"blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"blocklisted_session_number": 323442,
"api_service": "PASSIVE_LIVENESS"
},
"log_type": "error",
"short_description": "Face in blocklist",
"long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified."
}
]
},
"vendor_data": "user-123",
"metadata": null,
"created_at": "2026-06-12T01:04:42.763237+00:00"
}
```
## Related
* [Face Search warnings](/core-technology/face-search/warnings-face-search) — every warning code the search can emit
* [Face Search API reference](/standalone-apis/face-search) — request parameters and `search_type` modes
* [Data models — Face Search](/reference/data-models#face-search) — canonical schema
* [Webhooks](/integration/webhooks) — `status.updated` payloads for persisted sessions
When `save_api_request=true`, `match_image_url` values are signed, short-lived URLs — re-fetch the session decision endpoint with the returned `request_id` to get fresh ones. When `save_api_request=false`, they are internal storage paths and are not directly downloadable. Store only the matched session id, status, and `similarity_percentage` on your side — do not retain the underlying biometric image.
# Face search warnings
Source: https://docs.didit.me/core-technology/face-search/warnings-face-search
Reference for Face Search 1:N warning codes: blocklist matches, duplicate faces, multiple faces, and the search-image gate that rejects images with no detectable face.
## Overview
Face Search emits a small set of warnings so your team can tell **why** a search returned `Approved` or `Declined`. Warnings are returned in the `face_search.warnings[]` array of the `POST /v3/face-search/` response (see [Face Search report](/core-technology/face-search/report-face-search)).
Warnings here use a **reduced shape** compared with workflow warnings: there is no `node_id`. The documented response contract declares five fields — `risk`, `additional_data`, `log_type`, `short_description`, `long_description` — and the runtime payload additionally carries `feature`, which is always `"LIVENESS"` on this endpoint.
## Warnings produced
| Risk | Cause | Severity (`log_type`) | Affects status | Recommended remediation |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `FACE_IN_BLOCKLIST` | The search image matched a face in your application's face blocklist above the high-confidence similarity band. | `error` | Forces `Declined`. | Reject the user. Investigate the linked session via `additional_data.blocklisted_session_id`. |
| `POSSIBLE_FACE_IN_BLOCKLIST` | A blocklisted face matched within the lower "possible" similarity band, below the high-confidence band. | `error` | Forces `Declined` on the standalone Face Search API (stricter than the workflow liveness check, which sends this risk to review). | Manual review. Confirm the hit against the original blocklist evidence before acting. |
| `MULTIPLE_FACES_DETECTED` | More than one face was found in the submitted search image. The search still runs, using the largest detected face. | `warning` | None — never declines on its own. | Re-capture with a single subject in frame if the largest face is not the intended one. |
| `DUPLICATED_FACE` | The face matched, above the high-confidence band, a face from an **approved** session or an imported user-profile face. The standalone endpoint does not exclude same-`vendor_data` sessions, so a returning user matches their own earlier sessions. | `information` | None — duplicates are surfaced as signal, not declines. | Investigate the linked session via `additional_data.duplicated_session_id`. Decide whether to block or merge accounts on your side. |
| `POSSIBLE_DUPLICATED_FACE` | Same as `DUPLICATED_FACE` but the similarity sits in the lower "possible" band rather than the high-confidence band. | `information` | None. | Manual review before treating the two records as the same person. |
Producer: `FaceSearchAPIView.add_warnings` (`apis/views/face_search.py:250-330`); status derivation: `get_face_search_status` (`apis/views/face_search.py:242-248`), which declines **only** for the two blocklist risks.
Emission rules worth knowing:
* **Blocklist takes precedence over duplicates.** A confirmed blocklist hit suppresses `DUPLICATED_FACE`, and a possible blocklist hit suppresses `POSSIBLE_DUPLICATED_FACE` — you never get both members of a pair for the same confidence band.
* **`additional_data` references the first matching face only**, even when `matches[]` contains several. Blocklist warnings carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate warnings carry `{duplicated_session_id, duplicated_session_number, api_service}`; `MULTIPLE_FACES_DETECTED` carries `null`. The `api_service` value is uppercase (e.g. `PASSIVE_LIVENESS`) or `null` for workflow sessions.
* **Allowlisted faces suppress duplicate warnings** — a high-confidence allowlist match clears `DUPLICATED_FACE`/`POSSIBLE_DUPLICATED_FACE` (it never clears blocklist warnings).
### Exact warning strings
| Risk | `short_description` | `long_description` |
| ---------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FACE_IN_BLOCKLIST` | Face in blocklist | The system identified a face in the blocklist, which means the face is not allowed to be verified. |
| `POSSIBLE_FACE_IN_BLOCKLIST` | Possible face in blocklist | The system identified a possible face in the blocklist, which means the face is not allowed to be verified. |
| `DUPLICATED_FACE` | Duplicated face from other approved session | The system identified a duplicated face from another approved session, requiring further investigation. |
| `POSSIBLE_DUPLICATED_FACE` | Possible duplicated face from other approved session | The system identified a possible duplicate face from another approved session, requiring further investigation. |
| `MULTIPLE_FACES_DETECTED` | Multiple faces detected | Multiple faces were detected in the liveness image. The system uses the largest face for liveness verification and face comparison, but the presence of multiple faces may require additional review. |
### Search-image gate
When no face is detected in `user_image`, the API rejects the request with HTTP `400` and `{"error": "No face detected in the image"}` **before** a `face_search` object is built — so a `NO_FACE_DETECTED` warning never appears in a successful Face Search response. This is returned as a top-level `error` string, not in `warnings[]`.
## Configurable settings
The similarity bands that split confirmed hits (`FACE_IN_BLOCKLIST`, `DUPLICATED_FACE`) from possible hits (`POSSIBLE_FACE_IN_BLOCKLIST`, `POSSIBLE_DUPLICATED_FACE`) are fixed internally — per-application threshold tuning applies to the workflow liveness check, not this endpoint. The switches you control per request are:
* **`search_type`** (`most_similar`, default | `blocklisted_or_approved`) — `most_similar` ranks every enrolled face by similarity; `blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first.
* **`save_api_request`** (default `true`) — persists the call as an API-type session and enrolls the searched face into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results. Set to `false` to query without storing or enrolling.
* **`rotate_image`** (default `false`) — tries 90-degree rotations and keeps the orientation with the best face detection; useful when EXIF orientation is missing.
## Examples
### Blocklist hit (forces `Declined`)
```json theme={null}
{
"warnings": [
{
"risk": "FACE_IN_BLOCKLIST",
"feature": "LIVENESS",
"additional_data": {
"blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86",
"blocklisted_session_number": 323442,
"api_service": "PASSIVE_LIVENESS"
},
"log_type": "error",
"short_description": "Face in blocklist",
"long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified."
}
]
}
```
### Duplicate signal (status stays `Approved`)
```json theme={null}
{
"warnings": [
{
"risk": "MULTIPLE_FACES_DETECTED",
"feature": "LIVENESS",
"additional_data": null,
"log_type": "warning",
"short_description": "Multiple faces detected",
"long_description": "Multiple faces were detected in the liveness image. The system uses the largest face for liveness verification and face comparison, but the presence of multiple faces may require additional review."
},
{
"risk": "DUPLICATED_FACE",
"feature": "LIVENESS",
"additional_data": {
"duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788",
"duplicated_session_number": 1024,
"api_service": null
},
"log_type": "information",
"short_description": "Duplicated face from other approved session",
"long_description": "The system identified a duplicated face from another approved session, requiring further investigation."
}
]
}
```
## Related
* [Face Search report](/core-technology/face-search/report-face-search) — full response shape
* [Face Search API reference](/standalone-apis/face-search) — request parameters
* [Face Match warnings](/core-technology/id-verification/warnings-id-verification) — 1:1 selfie-to-document warnings (separate from Face Search)
* [Data models — Face Search](/reference/data-models#face-search) — canonical schema
### Warning types
# Digital ID Wallets
Source: https://docs.didit.me/core-technology/id-verification/digital-id-wallets
Let people verify with the government or bank digital identity they already hold - MitID, BankID, itsme, UAE PASS, gov.br and the EUDI Wallet.
Wallet sign-in is one of the three [ID Verification methods](/core-technology/id-verification/verification-methods). The user signs in with a digital identity they already hold — a national eID or a bank identity — and that provider shares a signed set of attributes with Didit.
The result carries the assurance tier `cryptographic`: an identity provider authenticated the person and signed the attributes it returned. That is all the tier claims. It is not a ranking against the other two tiers — they describe [different evidence](/core-technology/id-verification/verification-methods#assurance-tiers), not more or less of the same thing — and how long a sign-in takes is the wallet's own flow, which Didit neither controls nor promises.
**No wallet is live in production yet.** The launch switch is off there, so every wallet below reads **Coming soon**, cannot be enabled on a workflow, and is not offered to end users — whatever a wallet's own row would otherwise say. The table is the roadmap, not today's capability. Talk to your account team about being in the first cohort.
## Prerequisites and pricing
Each wallet requires an active identity with the issuer named below and access to its authentication method. The account must be eligible for the requested attributes, and the user must consent to sharing them. A signature verifies the issuer and returned claims; it does not guarantee that every requested attribute is present, current or sufficient for your compliance policy. A wallet without a portrait does not include a selfie comparison.
All 22 catalog wallet entries are listed below, including both BankID countries. Every wallet retail rate is still marked as a placeholder and is therefore **pending publication**, in USD per completed verified sign-in. Failed, cancelled, expired or abandoned sign-ins are not charged. A configured document fallback is a separate document check, subject to the document allowance. The 500 free document checks do not include wallet sign-in. No launch date is promised.
These are **identity wallets**. [Crypto wallet screening](/core-technology/wallet-screening/overview) checks blockchain addresses for financial risk and is a separate product with separate pricing.
## What the user does
1. Picks their country in the ID step and chooses a wallet from the ones you accept.
2. Signs in with that wallet, the way they sign in to their bank or their government portal.
3. Approves sharing their identity attributes with you.
4. Comes back verified.
**Same-device** sign-in hands the user to the wallet app and returns them to the verification flow when they are done. **Cross-device** sign-in shows a QR code on the desktop screen that they scan with the phone holding the wallet. Both are offered where the wallet supports them; the user picks.
A started sign-in stays valid for a limited window (10 minutes by default). After that the user has to start it again.
## Wallets are an accept-list
`wallet.providers` is the list of wallets a country may offer. It is not a ranking: the order you send carries no meaning, is normalised into catalog order on save, and the end user chooses from whatever you accepted. Enable one wallet or every wallet in a country — the choice is theirs.
The API rejects a workflow that enables a wallet the catalog does not offer in that country, or one that is still **Coming soon**.
Every wallet's attribute list below includes a **signed assertion**. Didit verifies that signature and reports the verdict as `wallet_verification.signature_valid` on the session; the assertion itself is not handed back, so there is no reference to fetch or store. The **On the session** column says which of the two you get for each attribute.
## Supported wallets
| Wallet | Wallet id | Countries | Issuing authority | Level of assurance | Availability | Price |
| --------------------- | ----------------- | ----------------------------------- | ----------------------------------------------------------- | ------------------ | ------------ | ---------- |
| MitID | `mitid` | Denmark | Danish Agency for Digital Government | Substantial | Coming soon | On request |
| BankID | `bankid_se` | Sweden | Finansiell ID-Teknik / bank consortium | Substantial | Coming soon | On request |
| BankID | `bankid_no` | Norway | BankID BankAxept AS | High | Coming soon | On request |
| Vipps | `vipps` | Norway | Vipps MobilePay / BankID NO | Substantial | Coming soon | On request |
| Buypass ID | `buypass` | Norway | Buypass AS | High | Coming soon | On request |
| itsme | `itsme` | Belgium, Luxembourg, Netherlands | Belgian Mobile ID | Substantial | Coming soon | On request |
| iDIN | `idin` | Netherlands | Dutch banks (Currence iDIN) | Substantial | Coming soon | On request |
| Finnish Trust Network | `ftn` | Finland | Finnish banks and mobile operators (FTN) | Substantial | Coming soon | On request |
| Personalausweis | `personalausweis` | Germany | Bundesministerium des Innern (eID) | High | Coming soon | On request |
| Freja eID | `frejaid` | Sweden | Freja eID Group | Substantial | Coming soon | On request |
| UAE PASS | `uae_pass` | United Arab Emirates | UAE Digital Government Authority | High | Coming soon | On request |
| gov.br | `govbr` | Brazil | Governo Federal do Brasil | Substantial | Coming soon | On request |
| OneID | `oneid` | United Kingdom | OneID (UK bank-verified identity) | Substantial | Coming soon | On request |
| GOV.UK Wallet | `govuk_wallet` | United Kingdom | UK Government Digital Service | High | Coming soon | On request |
| Smart-ID | `smart_id` | Estonia, Latvia, Lithuania, Belgium | SK ID Solutions | High | Coming soon | On request |
| Mobile-ID | `mobile_id` | Estonia, Latvia, Lithuania | SK ID Solutions with the national mobile operators | High | Coming soon | On request |
| Bank iD | `bankid_cz` | Czechia | Bankovní identita, a.s. | Substantial | Coming soon | On request |
| MojeID | `mojeid` | Czechia | CZ.NIC | Substantial | Coming soon | On request |
| Diia | `diia` | Ukraine | Ministry of Digital Transformation of Ukraine | High | Coming soon | On request |
| FranceConnect | `franceconnect` | France | DINUM (French state) | Substantial | Coming soon | On request |
| Auðkenni | `audkenni` | Iceland | Auðkenni (Icelandic electronic ID) | High | Coming soon | On request |
| EUDI Wallet | `eudi` | 30 EU/EEA member states | The user's own member state; the issuer differs per country | High | Coming soon | On request |
**Availability** is served by the capability catalog, not hand-maintained here. **Coming soon** means the wallet is visible in the console and cannot be switched on yet; it carries no committed date. Prices marked **On request** are not published yet — wallets are priced per wallet and quoted by your account team.
## Level of assurance
Every wallet asserts a [eIDAS](https://en.wikipedia.org/wiki/EIDAS) level of assurance — `low`, `substantial` or `high` — and Didit records the level the wallet actually asserted on the result, not the level the catalog hoped for. If the wallet returns a weaker level than the one that was requested, the sign-in fails rather than quietly downgrading.
Check `wallet_verification.level_of_assurance` on the session when your compliance rules depend on it.
## Per-wallet reference
### MitID
`mitid` · Denmark · **Coming soon**
| | |
| ------------------------------- | --------------------------------------------------------- |
| Issuing authority | Danish Agency for Digital Government |
| Coverage | Covers \~95% of adults |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active MitID identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · CPR alias (pseudonymised)
| Attribute returned | Key | On the session |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| CPR alias (pseudonymised) | `cpr_alias` | Returned in `wallet_verification.attributes` |
| Level of assurance reached | `level_of_assurance` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### BankID
`bankid_se` · Sweden · **Coming soon**
| | |
| ------------------------------- | ---------------------------------------------------------- |
| Issuing authority | Finansiell ID-Teknik / bank consortium |
| Coverage | Covers \~99% of adults |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active BankID identity and access to its sign-in method |
**Attributes requested:** Name · Personal number
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Personal number | `personal_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### BankID
`bankid_no` · Norway · **Coming soon**
| | |
| ------------------------------- | ---------------------------------------------------------- |
| Issuing authority | BankID BankAxept AS |
| Coverage | National eID |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active BankID identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · National identity number
| Attribute returned | Key | On the session |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| National identity number | `national_identity_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Vipps
`vipps` · Norway · **Coming soon**
| | |
| ------------------------------- | --------------------------------------------------------- |
| Issuing authority | Vipps MobilePay / BankID NO |
| Coverage | Covers \~90% of adults |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Vipps identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · National identity number
| Attribute returned | Key | On the session |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| National identity number | `national_identity_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Buypass ID
`buypass` · Norway · **Coming soon**
| | |
| ------------------------------- | -------------------------------------------------------------- |
| Issuing authority | Buypass AS |
| Coverage | Covers \~35% of adults |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Buypass ID identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · National identity number
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### itsme
`itsme` · Belgium, Luxembourg, Netherlands · **Coming soon**
| | |
| ------------------------------- | --------------------------------------------------------- |
| Issuing authority | Belgian Mobile ID |
| Coverage | Covers \~78% of adults |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active itsme identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · National register number
| Attribute returned | Key | On the session |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| National register number | `national_register_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### iDIN
`idin` · Netherlands · **Coming soon**
| | |
| ------------------------------- | -------------------------------------------------------- |
| Issuing authority | Dutch banks (Currence iDIN) |
| Coverage | Every Dutch bank customer |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active iDIN identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · Address
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Gender | `gender` | Returned in `wallet_verification.attributes` |
| Address | `address` | Returned in `wallet_verification.attributes` |
| Bank identifier | `bank_identifier` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Finnish Trust Network
`ftn` · Finland · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------------------- |
| Issuing authority | Finnish banks and mobile operators (FTN) |
| Coverage | Every Finnish bank customer |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Finnish Trust Network identity and access to its sign-in method |
**Attributes requested:** Name · Personal identity code
| Attribute returned | Key | On the session |
| ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Personal identity code | `personal_identity_code` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Personalausweis
`personalausweis` · Germany · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------------- |
| Issuing authority | Bundesministerium des Innern (eID) |
| Coverage | Every German ID card with the online function activated |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Personalausweis identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Address | `address` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Freja eID
`frejaid` · Sweden · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------- |
| Issuing authority | Freja eID Group |
| Coverage | Government-approved Swedish eID |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Freja eID identity and access to its sign-in method |
**Attributes requested:** Name · Personal number
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Personal number | `personal_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### UAE PASS
`uae_pass` · United Arab Emirates · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------ |
| Issuing authority | UAE Digital Government Authority |
| Coverage | Government sign-in |
| Level of assurance | High |
| Shares a portrait | Yes |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active UAE PASS identity and access to its sign-in method |
**Attributes requested:** Name · Emirates ID number · Nationality · Portrait
| Attribute returned | Key | On the session |
| --------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
| Full name (EN and AR) | `full_name` | Returned in `wallet_verification.attributes` |
| Emirates ID number | `emirates_id_number` | Returned in `wallet_verification.attributes` |
| Nationality | `nationality` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Portrait | `portrait` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### gov.br
`govbr` · Brazil · **Coming soon**
| | |
| ------------------------------- | ---------------------------------------------------------- |
| Issuing authority | Governo Federal do Brasil |
| Coverage | Government sign-in |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active gov.br identity and access to its sign-in method |
**Attributes requested:** Name · CPF
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| CPF | `tax_number` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### OneID
`oneid` · United Kingdom · **Coming soon**
| | |
| ------------------------------- | --------------------------------------------------------- |
| Issuing authority | OneID (UK bank-verified identity) |
| Coverage | Bank-verified identity for UK current-account holders |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active OneID identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Address | `address` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### GOV.UK Wallet
`govuk_wallet` · United Kingdom · **Coming soon**
| | |
| ------------------------------- | ----------------------------------------------------------------- |
| Issuing authority | UK Government Digital Service |
| Coverage | Government wallet; driving licence credential rolling out |
| Level of assurance | High |
| Shares a portrait | Yes |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active GOV.UK Wallet identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · Portrait
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Portrait | `portrait` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Smart-ID
`smart_id` · Estonia, Latvia, Lithuania, Belgium · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------ |
| Issuing authority | SK ID Solutions |
| Coverage | Covers most adults in the Baltics |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Smart-ID identity and access to its sign-in method |
**Attributes requested:** Name · Personal identification code
| Attribute returned | Key | On the session |
| ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Personal identification code | `personal_code` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Mobile-ID
`mobile_id` · Estonia, Latvia, Lithuania · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------- |
| Issuing authority | SK ID Solutions with the national mobile operators |
| Coverage | SIM-based national eID |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Mobile-ID identity and access to its sign-in method |
**Attributes requested:** Name · Personal identification code
| Attribute returned | Key | On the session |
| ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Personal identification code | `personal_code` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Bank iD
`bankid_cz` · Czechia · **Coming soon**
| | |
| ------------------------------- | ----------------------------------------------------------- |
| Issuing authority | Bankovní identita, a.s. |
| Coverage | Federated bank identity; the most used Czech sign-in |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Bank iD identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Address | `address` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### MojeID
`mojeid` · Czechia · **Coming soon**
| | |
| ------------------------------- | ---------------------------------------------------------- |
| Issuing authority | CZ.NIC |
| Coverage | About 500k active accounts; substantial LoA on a subset |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active MojeID identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Diia
`diia` · Ukraine · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------- |
| Issuing authority | Ministry of Digital Transformation of Ukraine |
| Coverage | Government app with national ID, passport and driving licence |
| Level of assurance | High |
| Shares a portrait | Yes |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Diia identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth · Portrait
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Document number | `document_number` | Returned in `wallet_verification.attributes` |
| Portrait | `portrait` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### FranceConnect
`franceconnect` · France · **Coming soon**
| | |
| ------------------------------- | ----------------------------------------------------------------- |
| Issuing authority | DINUM (French state) |
| Coverage | Federated state identity; FranceConnect+ for substantial LoA |
| Level of assurance | Substantial |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active FranceConnect identity and access to its sign-in method |
**Attributes requested:** Name · Date of birth
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Place of birth | `birthplace` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### Auðkenni
`audkenni` · Iceland · **Coming soon**
| | |
| ------------------------------- | ------------------------------------------------------------ |
| Issuing authority | Auðkenni (Icelandic electronic ID) |
| Coverage | National electronic ID on SIM and app |
| Level of assurance | High |
| Shares a portrait | No |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active Auðkenni identity and access to its sign-in method |
**Attributes requested:** Name · Kennitala
| Attribute returned | Key | On the session |
| ------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| Full name | `full_name` | Returned in `wallet_verification.attributes` |
| Date of birth | `date_of_birth` | Returned in `wallet_verification.attributes` |
| Kennitala | `national_id` | Returned in `wallet_verification.attributes` |
| Signed assertion | `signed_assertion` | Verified inside Didit; the session returns the `signature_valid` verdict, not the assertion |
### EUDI Wallet
`eudi` · 30 EU/EEA member states · **Coming soon**
| | |
| ------------------------------- | --------------------------------------------------------------- |
| Issuing authority | The user's own member state; the issuer differs per country |
| Coverage | Coming soon - EU rollout |
| Level of assurance | High |
| Shares a portrait | Yes |
| Price (USD / completed sign-in) | On request |
| Prerequisite | An active EUDI Wallet identity and access to its sign-in method |
**Attributes requested:** PID - person identification data
| Attribute returned | Key | On the session |
| ------------------------------------ | ----- | -------------------------------------------- |
| Schema follows the ARF PID rule book | `pid` | Returned in `wallet_verification.attributes` |
## Failure and fallback
| Outcome | Means | Billed |
| ----------- | ---------------------------------------------------------------------- | ------ |
| `verified` | The user signed in and the signed attributes checked out. | Yes |
| `cancelled` | The user backed out of the wallet sign-in. | No |
| `timeout` | The sign-in window expired before it completed. | No |
| `failed` | The sign-in could not be completed or the assertion did not check out. | No |
Anything other than `verified` follows the country's `wallet.on_failure` switch: `fallback_to_document` sends the user to document capture, `decline` ends the session with a declined ID Verification. `fallback_to_document` with document capture disabled for that country declines instead, because there is nowhere to fall back to.
Only a completed sign-in is billed. Abandoned, cancelled, timed-out and failed sign-ins cost nothing.
## What lands on the session
```json theme={null}
{
"node_id": "feature_ocr_1",
"status": "Approved",
"verification_method": "wallet",
"assurance": "cryptographic",
"wallet_provider": "mitid",
"fallback_from": null,
"id_lookup": null,
"wallet_verification": {
"provider": "mitid",
"provider_name": "MitID",
"issuing_authority": "Danish Agency for Digital Government",
"issuing_country": "DNK",
"credential_type": "person_identification",
"level_of_assurance": "substantial",
"verified_at": "2026-09-04T09:12:44Z",
"signature_valid": true,
"attributes": {
"full_name": "Freja Nielsen",
"date_of_birth": "1988-03-02",
"cpr_alias": "b0f1c2d3-e4f5-4678-9abc-def012345678"
},
"portrait": null,
"face_match_score": null
}
}
```
`attributes` holds exactly what that wallet shared, keyed the way the per-wallet table above lists it. `portrait` is present only for the wallets that share one. `signature_valid` is the result of validating the signed assertion the wallet returned. It is `true` on a completed sign-in: an assertion that does not validate fails the sign-in and follows `wallet.on_failure` instead of producing an approved result.
Full field types are in the [ID verification data model](/reference/data-models#id-verification).
## Configuring wallets
```json theme={null}
{
"feature": "OCR",
"config": {
"methods": {
"DNK": {
"document": { "enabled": true },
"wallet": { "enabled": true, "providers": ["mitid"], "on_failure": "fallback_to_document" }
},
"NOR": {
"document": { "enabled": true },
"wallet": { "enabled": true, "providers": ["bankid_no", "vipps"], "on_failure": "decline" }
}
}
}
}
```
Enable at least one wallet when `wallet.enabled` is `true`, or the save is rejected. The full reference is in [Workflow feature configs](/management-api/workflows/feature-configs#ocr--id-verification).
## Related
* [ID Verification methods](/core-technology/id-verification/verification-methods) — the three methods, fallback and billing
* [Non-document lookup](/core-technology/id-verification/non-doc-lookup) — the data-match method
* [ID Verification methods in the console](/console/id-verification-methods) — enabling wallets per country
# Document Geolocation
Source: https://docs.didit.me/core-technology/id-verification/document-geolocation-id-verification
Validate addresses with Didit's Document Geolocation: AI address extraction, Google Maps cross-checks, and fictitious-address detection in real time.
Didit's Document Address Geolocation verification offers a comprehensive solution to validate user addresses efficiently and accurately. Our system leverages advanced AI technology and external data sources to ensure the authenticity and validity of address information.
Users upload an image of their document containing address information. Our advanced AI handles the rest.
| Capability | Description |
| -------------------- | ---------------------------------------------------- |
| **Document types** | Passports, IDs, residence permits, and more |
| **Language support** | Multi-language and multi-format recognition |
| **AI extraction** | Automatic data extraction from the uploaded document |
The extracted address is validated against multiple sources and geolocated by IP.
| Capability | Description |
| ------------------------ | ----------------------------------------------------------- |
| **Map integration** | Google Maps and OpenStreetMap cross-referencing |
| **Component validation** | Street, city, region, and postal code verified individually |
| **Fraud detection** | Fictitious address detection to prevent possible fraud |
A full report is generated on the validation status with standardized outputs.
| Capability | Description |
| ------------------------- | ------------------------------------------------ |
| **Verification results** | Detailed results with standardized output format |
| **Comprehensive reports** | Full verification reports for every submission |
| **Delivery options** | Results via dashboard, webhooks, or API |
Our system supports global address formats and provides standardized outputs regardless of the input document type or region.
### Report Structure
The Document Geolocation report returns a JSON object with a root-level `address` field containing all verification results.
#### Core Response Fields
```typescript theme={null}
interface DocumentGeolocationResponse {
address: string;
parsed_address: {
id: string;
label: string;
street_1: string;
street_2: string | null;
city: string;
region: string;
postal_code: string;
raw_results: {
geometry: {
location: {
lat: number;
lng: number;
};
location_type: string;
viewport: {
northeast: {
lat: number;
lng: number;
};
southwest: {
lat: number;
lng: number;
};
};
};
formatted_address: string;
};
};
}
```
### Response Fields
#### Address Information
* `address`: Complete address as extracted from document
* `parsed_address.street_1`: Primary street information
* `parsed_address.street_2`: Secondary street information
* `parsed_address.city`: City name
* `parsed_address.region`: State or region
* `parsed_address.postal_code`: ZIP or postal code
#### Geolocation Data
* `raw_results.geometry.location`: Precise coordinates
* `raw_results.location_type`: Accuracy level of geolocation
* `raw_results.viewport`: Coordinate boundaries
* `raw_results.formatted_address`: Standardized address format
### Sample JSON Response
```json theme={null}
{
"address": "123 Sample Street",
"parsed_address": {
"id": "7c6280a2-fb6a-4258-93d5-2ac987cbc6ba",
"city": "Madrid",
"label": "Spain ID Card Address",
"region": "Madrid",
"street_1": "Avda de Madrid 34",
"street_2": null,
"postal_code": "28822",
"raw_results": {
"geometry": {
"location": {
"lat": 37.4222804,
"lng": -122.0843428
},
"location_type": "ROOFTOP",
"viewport": {
"northeast": {
"lat": 37.4237349802915,
"lng": -122.083183169709
},
"southwest": {
"lat": 37.4210370197085,
"lng": -122.085881130292
}
}
},
"formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain"
}
}
}
```
For a complete list of possible properties and their values, please refer to our [*API Reference*](/sessions-api/retrieve-session).
### Security and Privacy Considerations
Address information extracted from documents should be handled with appropriate security measures and in compliance with relevant data protection regulations. Implement proper access controls and data retention policies for this sensitive information.
# Document Monitoring
Source: https://docs.didit.me/core-technology/id-verification/document-monitoring-id-verification
Track document expiration automatically with Didit's Document Monitoring. Real-time alerts, ongoing KYC compliance, reduced fraud risk across your user base.
Didit's Document Monitoring feature automatically tracks and validates user documents throughout their lifecycle. This proactive system helps maintain compliance, reduce fraud risks, and ensures your user base always has valid identification documents.
1. **Initial Verification**: User documents are verified during the onboarding process.
2. **Expiration Date Extraction**: The system extracts and records the expiration date from verified documents.
3. **Continuous Monitoring**: Our system regularly checks the expiration status of all stored documents.
4. **Status Update**: When a document expires, the user's status is automatically changed from "Approved" to "Kyc Expired".
5. **Notification**: You receive alerts about expired documents through our dashboard, webhooks, or API.
## Key Features
#### 1. Intelligent Document Monitoring
* Real-time tracking of document expiration dates
* Automatic status updates based on document validity
* Support for multiple document types and jurisdictions
* Continuously monitor expiration dates of all verified documents
* Automatically update user statuses based on document validity
#### 2. Proactive Notifications
* Receive webhooks when a document status changes to "Kyc Expired"
#### 3. Comprehensive Reporting
* Access detailed reports on document statuses across your user base
Need more details? Check our [*API Reference*](/sessions-api/retrieve-session) for a complete list of properties and values.
## Use Cases
#### Regulatory Compliance
* Maintain continuous compliance with KYC/AML regulations
* Automate document validity checks
* Generate compliance reports on demand
#### Risk Management
* Prevent fraud from expired documents
* Reduce operational risks through automated monitoring
* Maintain up-to-date user verification status
#### Operational Efficiency
* Automate manual document checking processes
* Reduce administrative overhead
* Streamline user re-verification workflows
# Document subtypes
Source: https://docs.didit.me/core-technology/id-verification/document-subtypes-id-verification
Every document_subtype code Didit's ID verification report can return, how the naming pattern works, and how to tell a commercial driver's license apart from a standard one.
`document_subtype` on the [ID verification report](/core-technology/id-verification/report-id-verification) identifies the specific document variant Didit recognized — for example a Queensland driver's licence versus a generic driver's licence, or a US Commercial Driver's License versus a standard one. This page enumerates every value the field can return.
Your own workflow only accepts a subset of these — whatever you selected in the workflow's document-settings step. Retrieve the exact accepted list for your workflow with the Management API's workflow-retrieve endpoint (`documents_allowed` on the OCR node) rather than assuming every value below is enabled for you.
## Naming pattern
Every `document_subtype` value is one of two shapes:
* **Bare category** — `{CATEGORY}`, for documents Didit does not need to distinguish by region (most passports, most national IDs), for example `PASSPORT_GENERIC`, `ID_CARD_GENERIC`.
* **Region-prefixed** — `{REGION}_{CATEGORY}`, for documents whose format or classification genuinely differs by state, province, or territory, for example `QUEENSLAND_DRIVER_LICENSE_GENERIC` (Australia) or `CALIFORNIA_COMMERCIAL_DL` (United States). The region prefix is the issuing state/province name, upper-cased with spaces replaced by underscores.
A document only gets a region-prefixed subtype when Didit's document registry carries a distinct, separately classified variant for that region. The absence of a region-prefixed variant does not mean the document type doesn't exist in that region — it means Didit currently reports it under the country-level bare category instead (see the CDL caveat below for a concrete example).
## All subtype values
The table below is the full enumeration of `document_subtype` values the report can return, grouped by the `document_type` family. It already includes the region-prefixed variants — for example both `DRIVER_LICENSE_GENERIC` (most countries) and `CALIFORNIA_DRIVER_LICENSE_GENERIC` appear as distinct rows under Driver's License.
A bare-category code is not unique to one country — it's shared by every country whose registry entry for that document has no region-specific variant. `ID_CARD_GENERIC`, for instance, covers national ID cards for roughly 170 countries. Only look up a code by its `document_type` grouping and (when present) its region prefix, never by assuming a code maps to a single country.
## US driver's licenses: standard vs. commercial (CDL)
`document_type` is `"Driver's License"` for both a standard driver's license and a Commercial Driver's License (CDL) — it never distinguishes them. The distinction lives entirely in `document_subtype`.
**In 48 of the 51 US states and territories Didit classifies, a CDL gets its own `{STATE}_COMMERCIAL_DL` subtype, distinct from that state's `{STATE}_DRIVER_LICENSE_GENERIC`.** Treating `document_subtype` ending in `_COMMERCIAL_DL` as "this is a CDL" is a reliable signal in those states.
**Alaska, the District of Columbia, and Hawaii do not currently have a distinct CDL subtype.** A CDL from one of these three jurisdictions is reported under that jurisdiction's standard `_DRIVER_LICENSE_GENERIC` subtype — indistinguishable from a non-commercial license by `document_subtype` alone. If you need airtight CDL detection for these three jurisdictions specifically, [contact us](mailto:support@didit.me) to register the missing document variants, or apply an out-of-band control (for example, requiring the driver to separately supply their CDL/DOT number for verification).
### `extra_fields.dl_categories` is not a reliable CDL signal
Some US licenses also produce `extra_fields.dl_categories` — a list of the class letters (`A`, `B`, `C`, …) printed on the card, read via OCR. **Do not use this as your CDL signal.** Today this field is populated for only a small minority of US driver's license configurations, class-letter meanings vary by state and are not normalized against a commercial/non-commercial semantic, and endorsement or restriction codes (which is where "commercial" is actually marked on many licenses) are not currently exposed on the report at all. `document_subtype` is the only structured, reliable signal for the standard-vs-commercial distinction today.
## Staying in sync
New `document_type` families are announced in the monthly changelog when they ship. `document_subtype` additions — a new region variant of an existing document type, for example — are not currently announced individually; Didit's document registry grows continuously as new document formats are onboarded. There is no version number on the webhook payload for this list.
If your integration needs to detect a subtype it has never seen before rather than assuming the set is fixed, treat any `document_subtype` value your code doesn't recognize as "unclassified, review manually" rather than erroring, and periodically re-check this page or your workflow's accepted-subtype list via the Management API.
## Related
* [ID verification report](/core-technology/id-verification/report-id-verification) — the `document_type` / `document_subtype` fields in context.
* [Supported ID documents](/core-technology/id-verification/supported-documents-id-verification) — countries and document types Didit supports.
* [Data models — ID verification](/reference/data-models#id-verification) — canonical field schema.
# Non-Document Lookup
Source: https://docs.didit.me/core-technology/id-verification/non-doc-lookup
Verify without a document: the user types their national ID number and a few details, checked against the authoritative source named for that country.
Non-doc lookup is one of the three [ID Verification methods](/core-technology/id-verification/verification-methods). Instead of photographing a document, the user types their national ID number plus a few personal details, and Didit checks them against the authoritative source for that country.
The result carries the assurance tier `data_match`: the details the user gave matched a record that source holds. No document was inspected, so nothing about a document's authenticity is being claimed.
## Which source is queried
Most countries are a government register — the Department of Home Affairs in South Africa, RENAPER in Argentina, UIDAI in India, the CPR register in Denmark. **Some are not.** In Canada, France, Indonesia, the Netherlands, Norway, Singapore, the United Kingdom and the United States the authoritative record Didit can reach is a credit-bureau, financial-services, utility or residential-records file rather than a state register.
That distinction is not cosmetic. It changes what the match proves, which consent regime the query runs under, and what you can tell a regulator about provenance, so this page never generalises: the **Source queried** column below names the exact source for every country, and it is the only statement of provenance to rely on. Read it before you enable a country.
The end user never sees the source name. It is on the session payload (`id_lookup.source`), in the console and in exports, for you and your auditors.
## What the user does
1. Picks their country in the ID step.
2. Types the fields that country asks for — always the identifier, usually a name, sometimes a date of birth or an address.
3. Where the source returns a portrait, takes a selfie.
4. Gets a result: verified and moved on, asked to correct the number, retried, sent to document capture, or declined.
The whole step is typing and, for four countries, one selfie. There is no upload, no camera framing and no document at all.
## Sources that return a portrait
Four sources return the person's photograph: **South Africa**, **Nigeria**, **Argentina** and **Panama** — all four government registers. For those countries the user is asked for a selfie, [passive liveness](/core-technology/liveness/overview) runs on it, and it is face-matched to the registry portrait. All three happen inside the lookup and inside the lookup price — there is no separate Liveness or Face Match charge.
Because the lookup already proved a live person matching the register's photograph (all four are government registers), the country's `skip_liveness_and_face_match` switch (on by default) lets the workflow skip its own Liveness and Face Match steps after a match. Turn it off to run them anyway.
Every other source returns data only. There is no portrait to compare against, so no selfie is taken and the switch is normalised off on save. Add [Liveness](/core-technology/liveness/overview) and [Face Match](/core-technology/face-match/overview) as their own workflow steps if a biometric check matters in those countries — Face Match then needs a portrait from somewhere else in the workflow.
## Coverage and pricing
Prices below are public retail USD per answered attempt, checked against the anonymous Database Validation pricing feed on September 7, 2026. They use the services configured for the country. A price is not a production-launch announcement: enable only the methods your application catalog offers. The existing availability snapshot is retained separately from this price refresh.
For a country using multiple sources, the table shows their sum when all answer. An answered attempt charges only its billable source responses; a source that did not answer is excluded. Nigeria uses **NIN or BVN**, not their sum. Custom contract rates can differ and are shown in your console. These are ID-step lookup prices, not a quote for every other Database Validation service offered in that country.
| Country | USD / attempt | Code | Source queried | Availability | Selfie, liveness and face match | Consent regime |
| ------------------ | --------------------- | ----- | ------------------------------------------------------ | ------------ | ------------------------------- | ------------------------- |
| Argentina | \$0.20 | `ARG` | RENAPER | Available | Included in the lookup | Ley 25.326 |
| Bolivia | \$0.20 | `BOL` | SEGIP | Available | Not applicable — data only | — |
| Brazil | \$0.20 | `BRA` | Receita Federal | Available | Not applicable — data only | LGPD |
| Cambodia | \$0.35 | `KHM` | Ministry of Interior voter register | Available | Not applicable — data only | Explicit consent required |
| Canada | \$3.95 | `CAN` | Canadian credit bureau records (FINTRAC dual-process) | Available | Not applicable — data only | PIPEDA |
| Chile | \$0.20 | `CHL` | Servicio de Registro Civil e Identificación | Available | Not applicable — data only | — |
| China | \$0.30 | `CHN` | NCIIC (National Citizen Identity Information Center) | Available | Not applicable — data only | Explicit consent required |
| Colombia | \$0.20 | `COL` | Registraduría General de la Nación / ANI | Available | Not applicable — data only | — |
| Costa Rica | \$0.20 | `CRI` | Tribunal Supremo de Elecciones | Available | Not applicable — data only | — |
| Denmark | \$1.39 | `DNK` | CPR register | Available | Not applicable — data only | — |
| Dominican Republic | \$0.05 | `DOM` | Junta Central Electoral | Available | Not applicable — data only | — |
| Ecuador | \$0.20 | `ECU` | Registro Civil del Ecuador | Available | Not applicable — data only | — |
| El Salvador | \$0.20 | `SLV` | RNPN | Available | Not applicable — data only | — |
| Finland | \$2.10 | `FIN` | DVV population register | Available | Not applicable — data only | Explicit consent required |
| France | \$1.54 | `FRA` | French residential and utility records | Available | Not applicable — data only | GDPR |
| Guatemala | \$0.20 | `GTM` | SAT | Available | Not applicable — data only | — |
| Honduras | \$0.20 | `HND` | CNE | Available | Not applicable — data only | — |
| India | \$0.25 | `IND` | UIDAI (Aadhaar) | Available | Not applicable — data only | Aadhaar consent |
| Indonesia | \$0.35 | `IDN` | Indonesian population register via residential records | Available | Not applicable — data only | UU PDP |
| Kenya | \$3.15 | `KEN` | IPRS (Integrated Population Registration System) | Available | Not applicable — data only | — |
| Malaysia | \$0.35 | `MYS` | JPN (National Registration Department) | Available | Not applicable — data only | Explicit consent required |
| Mexico | \$0.20 | `MEX` | RENAPO | Available | Not applicable — data only | — |
| Netherlands | \$0.90 | `NLD` | Dutch residential records | Available | Not applicable — data only | GDPR |
| Nigeria | NIN $0.20 / BVN $0.35 | `NGA` | NIMC / NIBSS | Available | Included in the lookup | NDPR |
| Norway | \$2.42 | `NOR` | Norwegian residential records | Available | Not applicable — data only | GDPR |
| Panama | \$0.75 | `PAN` | Tribunal Electoral / SIB | Available | Included in the lookup | — |
| Paraguay | \$0.20 | `PRY` | Registro del Estado Civil | Available | Not applicable — data only | — |
| Peru | \$0.20 | `PER` | RENIEC | Available | Not applicable — data only | — |
| Singapore | \$4.30 | `SGP` | Singapore credit bureau and utility records | Available | Not applicable — data only | PDPA |
| South Africa | \$2.20 | `ZAF` | Department of Home Affairs | Available | Included in the lookup | POPIA |
| Sweden | \$0.35 | `SWE` | Skatteverket population register | Available | Not applicable — data only | — |
| Thailand | \$0.35 | `THA` | DOPA civil registration | Available | Not applicable — data only | Explicit consent required |
| United Kingdom | \$1.85 | `GBR` | UK credit bureau and financial services records | Available | Not applicable — data only | UK GDPR |
| United States | \$0.27 | `USA` | US credit bureau and financial services records | Available | Not applicable — data only | GLBA permissible purpose |
| Uruguay | \$0.20 | `URY` | Dirección Nacional del Registro de Estado Civil | Available | Not applicable — data only | — |
| Venezuela | \$0.20 | `VEN` | CNE | Available | Not applicable — data only | — |
Availability comes from the capability catalog the backend serves; the API rejects a workflow that enables a lookup the catalog does not mark `Available` for that country. Prices marked **On request** are not published yet — non-document methods are priced per country and quoted by your account team.
## Format rules
Identifier fields carry a client-side format check before anything is sent — a length, a digit-only rule, a checksum or a pattern, depending on the country. A number that fails it never reaches the source, which means it is **never billed and never counted against `max_attempts`**: the user is shown a country-specific message and asked to correct it.
The per-country tables below list the rule for each field. A field with no rule is free text — a name, a date the interface collects, or an image capture.
Get the format right in your own pre-fill and you spend nothing on typos.
## Per-country fields
#### Argentina (`ARG`)
Source: **RENAPER**. The source returns a portrait, so a selfie is taken, passive liveness runs on it and it is face-matched to that portrait inside the lookup.
| Asked for | Field key | Required | Format check |
| ------------- | ----------------- | -------- | --------------------------- |
| Argentine DNI | `document_number` | Yes | 7-8 characters; digits only |
| Selfie | `selfie` | Yes | — |
| Gender | `gender` | Yes | One of `M`, `F` |
| Returned | Field key | Required |
| ----------------- | ------------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Registry portrait | `registry_portrait` | No |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Bolivia (`BOL`)
Source: **SEGIP**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | ----------------- | -------- | ---------------------------- |
| Bolivian CI | `document_number` | Yes | 6-10 characters; digits only |
| Date of birth | `date_of_birth` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Brazil (`BRA`)
Source: **Receita Federal**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ---------------------------------- |
| Brazilian CPF | `tax_number` | Yes | Exactly 11 characters; digits only |
| Date of birth | `date_of_birth` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Cambodia (`KHM`)
Source: **Ministry of Interior voter register**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check | |
| --------------- | --------------- | -------- | --------------- | ----------- |
| First name | `first_name` | Yes | — | |
| Last name | `last_name` | Yes | — | |
| Date of birth | `date_of_birth` | Yes | — | |
| Voter ID number | `voter_id` | Yes | Pattern \`(?:\d | \d-\d-\d)\` |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Canada (`CAN`)
Source: **Canadian credit bureau records (FINTRAC dual-process)**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ------------ |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Chile (`CHL`)
Source: **Servicio de Registro Civil e Identificación**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ----------- | ----------------- | -------- | --------------- |
| Chilean RUT | `personal_number` | Yes | 8-12 characters |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### China (`CHN`)
Source: **NCIIC (National Citizen Identity Information Center)**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check | |
| ------------------ | --------------- | -------- | ------------- | ------------ |
| Full name | `full_name` | Yes | — | |
| Date of birth | `date_of_birth` | Yes | — | |
| National ID number | `national_id` | Yes | Pattern \`(\d | \d\[\dXx])\` |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Colombia (`COL`)
Source: **Registraduría General de la Nación / ANI**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ---------------- | ----------------- | -------- | ---------------------------- |
| Colombian Cédula | `personal_number` | Yes | 6-11 characters; digits only |
| Date of issue | `date_of_issue` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Costa Rica (`CRI`)
Source: **Tribunal Supremo de Elecciones**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------------ | ----------------- | -------- | ---------------------------- |
| Costa Rican Cédula | `personal_number` | Yes | 9-12 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Denmark (`DNK`)
Source: **CPR register**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------------ | --------------- | -------- | ---------------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| National ID number | `national_id` | Yes | Pattern `\d{6}-?\d{4}` |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Dominican Republic (`DOM`)
Source: **Junta Central Electoral**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ---------------- | ----------------- | -------- | ---------------------------------- |
| Dominican Cédula | `personal_number` | Yes | Exactly 11 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Ecuador (`ECU`)
Source: **Registro Civil del Ecuador**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ----------------- | ----------------- | -------- | ---------------------------------- |
| Ecuadorian Cédula | `personal_number` | Yes | Exactly 10 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### El Salvador (`SLV`)
Source: **RNPN**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| -------------- | ----------------- | -------- | --------------------------------- |
| Salvadoran DUI | `document_number` | Yes | Exactly 9 characters; digits only |
| Date of birth | `date_of_birth` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Finland (`FIN`)
Source: **DVV population register**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check | |
| ------------------ | --------------- | -------- | -------------------------------- | ---------------- |
| First name | `first_name` | Yes | — | |
| Last name | `last_name` | Yes | — | |
| Date of birth | `date_of_birth` | Yes | — | |
| National ID number | `national_id` | Yes | Pattern \`(?:\d\[-+A]\d\[0-9A-Z] | \d\d\[0-9A-Z])\` |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### France (`FRA`)
Source: **French residential and utility records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ------------ |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Guatemala (`GTM`)
Source: **SAT**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| -------------- | ----------------- | -------- | ---------------------------------- |
| Guatemalan DPI | `document_number` | Yes | Exactly 13 characters; digits only |
| Date of birth | `date_of_birth` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Honduras (`HND`)
Source: **CNE**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------ | ----------------- | -------- | ---------------------------------- |
| Honduran DNI | `document_number` | Yes | Exactly 13 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### India (`IND`)
Source: **UIDAI (Aadhaar)**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------------ | ----------------- | -------- | ---------------------------------------- |
| Full name | `full_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| National ID number | `personal_number` | Yes | Exactly 12 characters; digits only |
| PAN | `pan` | Yes | Pattern `[A-Z]{5}\d{4}[A-Z]`; uppercased |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Indonesia (`IDN`)
Source: **Indonesian population register via residential records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| --------------- | --------------- | -------- | ---------------------------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| NIK (16 digits) | `national_id` | Yes | Exactly 16 characters; digits only |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Kenya (`KEN`)
Source: **IPRS (Integrated Population Registration System)**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------------ | --------------- | -------- | ----------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| National ID number | `national_id` | Yes | Pattern `\d{8,9}` |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Malaysia (`MYS`)
Source: **JPN (National Registration Department)**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------------ | --------------- | -------- | ---------------------------------- |
| Full name | `full_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| National ID number | `national_id` | Yes | Exactly 12 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Mexico (`MEX`)
Source: **RENAPO**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------ | ----------------- | -------- | --------------------- |
| Mexican CURP | `personal_number` | Yes | Exactly 18 characters |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Netherlands (`NLD`)
Source: **Dutch residential records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ------------ |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Nigeria (`NGA`)
Source: **NIMC / NIBSS**. The source returns a portrait, so a selfie is taken, passive liveness runs on it and it is face-matched to that portrait inside the lookup.
| Asked for | Field key | Required | Format check |
| ----------------- | ----------------- | -------- | ---------------------------------- |
| ID type | `identifier_type` | Yes | One of `NIN`, `BVN` |
| The number itself | `identifier` | Yes | Exactly 11 characters; digits only |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Selfie | `selfie` | Yes | — |
| Returned | Field key | Required |
| --------------------- | --------------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| State of origin | `state_of_origin` | No |
| Phone number (masked) | `phone_number_masked` | No |
| Registry portrait | `registry_portrait` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Norway (`NOR`)
Source: **Norwegian residential records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ------------ |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Panama (`PAN`)
Source: **Tribunal Electoral / SIB**. The source returns a portrait, so a selfie is taken, passive liveness runs on it and it is face-matched to that portrait inside the lookup.
| Asked for | Field key | Required | Format check | | | | |
| ----------------- | ----------------- | -------- | ------------------------- | ----- | -- | - | ----------------------------------------------- |
| Panamanian Cédula | `personal_number` | Yes | Pattern \`(?:\d(?:-?(?:AV | PI))? | PE | E | N)\[\d-]\*\`; at least 5 characters; uppercased |
| Selfie | `selfie` | Yes | — | | | | |
| Returned | Field key | Required |
| ----------------- | ------------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Registry portrait | `registry_portrait` | No |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Paraguay (`PRY`)
Source: **Registro del Estado Civil**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | ----------------- | -------- | ---------------------------- |
| Paraguayan CI | `document_number` | Yes | 6-10 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Peru (`PER`)
Source: **RENIEC**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------ | ----------------- | -------- | --------------------------------- |
| Peruvian DNI | `personal_number` | Yes | Exactly 8 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Singapore (`SGP`)
Source: **Singapore credit bureau and utility records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | --------------------------------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| NRIC / FIN | `national_id` | Yes | Pattern `[STFGM]\d{7}[A-Z]`; uppercased |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### South Africa (`ZAF`)
Source: **Department of Home Affairs**. The source returns a portrait, so a selfie is taken, passive liveness runs on it and it is face-matched to that portrait inside the lookup.
| Asked for | Field key | Required | Format check |
| ------------------ | ------------- | -------- | ------------------------------------------------- |
| 13-digit ID number | `national_id` | Yes | Exactly 13 characters; digits only; LUHN checksum |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Selfie | `selfie` | Yes | — |
| Returned | Field key | Required |
| -------------------------------------- | ------------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Citizenship | `citizenship` | No |
| ID status (valid, deceased or blocked) | `id_status` | Yes |
| Registry portrait | `registry_portrait` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Sweden (`SWE`)
Source: **Skatteverket population register**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check | |
| ------------------ | --------------- | -------- | ------------- | ------------------ |
| First name | `first_name` | Yes | — | |
| Last name | `last_name` | Yes | — | |
| Date of birth | `date_of_birth` | Yes | — | |
| National ID number | `national_id` | Yes | Pattern \`(\d | \d)\`; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Thailand (`THA`)
Source: **DOPA civil registration**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ----------------------- | --------------- | -------- | ---------------------------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Thai national ID number | `national_id` | Yes | Exactly 13 characters; digits only |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### United Kingdom (`GBR`)
Source: **UK credit bureau and financial services records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | --------------- | -------- | ------------ |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### United States (`USA`)
Source: **US credit bureau and financial services records**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| --------------------------------------- | --------------- | -------- | --------------------------- |
| First name | `first_name` | Yes | — |
| Last name | `last_name` | Yes | — |
| Date of birth | `date_of_birth` | Yes | — |
| Home address | `address` | Yes | — |
| Social Security number (last 4 or full) | `ssn` | No | 4-9 characters; digits only |
| Returned | Field key | Required |
| --------------------------------------------- | --------------------- | -------- |
| Name match (exact, partial or none) | `name_match` | Yes |
| Date-of-birth match | `date_of_birth_match` | Yes |
| Address match | `address_match` | Yes |
| Number of sources that confirmed the identity | `sources_confirming` | Yes |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Uruguay (`URY`)
Source: **Dirección Nacional del Registro de Estado Civil**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ------------- | ----------------- | -------- | --------------------------- |
| Uruguayan CI | `personal_number` | Yes | 7-9 characters; digits only |
| Date of birth | `date_of_birth` | Yes | — |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
#### Venezuela (`VEN`)
Source: **CNE**. This source returns data only, so there is no portrait to face-match against.
| Asked for | Field key | Required | Format check |
| ----------------- | ----------------- | -------- | --------------- |
| Venezuelan Cédula | `document_number` | Yes | 7-12 characters |
| Returned | Field key | Required |
| ------------- | --------------- | -------- |
| Full name | `full_name` | Yes |
| Date of birth | `date_of_birth` | Yes |
| Gender | `gender` | No |
| Address | `address` | No |
| Record status | `record_status` | No |
| Source name | `source_name` | No |
| Queried at | `queried_at` | No |
## Consent
Several sources may only be queried with the person's explicit consent, and the consent regime differs by country — POPIA in South Africa, NDPR in Nigeria, LGPD in Brazil, UK GDPR in the United Kingdom, GLBA permissible purpose in the United States, and so on. The regime that applies to each source is in the coverage table above.
You are the controller for that consent. Collect it in your own flow, or add a [questionnaire](/core-technology/questionnaires/overview) step before the ID step, and keep the record — Didit runs the query you asked for and does not judge whether you had the right to ask for it. Where a country's source requires consent wording of its own, the verification UI shows it before the lookup runs.
## Attempts, outcomes and billing
Each answered lookup counts as one attempt. `max_attempts` is 1 to 5, default 1.
| Outcome | Counts as an attempt | Billed | What happens next |
| ----------------- | -------------------- | ------ | ----------------------------------------------------------------------------------- |
| `match` | Yes | Yes | The register's record becomes the ID Verification result and the workflow moves on. |
| `partial_match` | Yes | Yes | Retry while attempts remain, then `on_partial_match`. |
| `no_match` | Yes | Yes | Retry while attempts remain, then `on_no_match`. |
| `provider_error` | No | No | Retry, then `on_provider_error`. |
| `format_rejected` | No | No | The user is asked to correct the field. It never reached the register. |
Each billable answered attempt sums the retail rates of the services that answered with a match, partial match or no match. Retries are new attempts and can add another charge. An attempt that ends in a provider error is not billed. Format rejection is not billed.
The first 500 document-capture checks per organization each month do not cover lookup attempts. If lookup falls back to document capture, the completed document check is billed separately, subject to its remaining document allowance. Disabling fallback declines the session; it does not refund a completed lookup. Sandbox scenarios simulate outcomes without paid registry queries or usage charges.
## What lands on the session
A lookup that matched populates the ID Verification result the same way a document read would — name, date of birth and the national identifier — and adds an `id_lookup` evidence block:
```json theme={null}
{
"node_id": "feature_ocr_1",
"status": "Approved",
"verification_method": "id_lookup",
"assurance": "data_match",
"wallet_provider": null,
"fallback_from": null,
"id_lookup": {
"source": "Department of Home Affairs",
"checked_at": "2026-09-04T09:12:44Z",
"attempts": 1,
"max_attempts": 2,
"outcome": "match",
"comparison": [
{ "field": "identification_number", "label": "ID number", "provided": "9106155043088", "record": "9106155043088", "result": "match" },
{ "field": "full_name", "label": "Full name", "provided": "Thabo Mokoena", "record": "Thabo Mokoena", "result": "match" },
{ "field": "date_of_birth", "label": "Date of birth", "provided": "1991-06-15", "record": "1991-06-15", "result": "match" }
],
"registry_portrait": "https:///registry_portrait.jpg",
"face_match_score": 96.4,
"source_errors": []
},
"wallet_verification": null
}
```
`comparison` carries one row per comparable field, with `result` of `match`, `partial` or `no_match`, so a reviewer can see exactly which field disagreed. The row names are normalised — `identification_number`, `full_name`, `date_of_birth`, `address` — not the country's request field keys, because a first name and a last name are compared as one name and every identifier alias is compared as one number.
The current staging response keeps source failures in a separate `source_errors` array. Its entries can contain `service_id`, `code`, `message`, `details` and `outcome_detail` when supplied. Treat these as query diagnostics, not identity mismatches. Earlier releases may represent source failures as `comparison` rows with `result: "error"`; confirm the contract deployed in your target environment.
`registry_portrait` and `face_match_score` are present only for the four sources that return a portrait and when the selected response fields retain the portrait.
Full field types are in the [ID verification data model](/reference/data-models#id-verification).
## Retention
The source record that produced a match is stored as the ID Verification result and follows your organization's [data retention](/console/data-retention) settings, exactly like a document read. Deleting a session deletes it; see [Delete session](/sessions-api/delete-session).
The current staging implementation applies `id_lookup.response_fields` to comparison output, optional gender and retained registry portraits. Required identity fields remain part of the verification record. This is not a guarantee that every intermediate provider payload is excluded from processing. Production rollout has not been verified for this change; confirm the behavior in your target environment before relying on field selection for retention requirements.
## Related
* [ID Verification methods](/core-technology/id-verification/verification-methods) — the three methods, fallback and billing
* [Digital ID wallets](/core-technology/id-verification/digital-id-wallets) — the cryptographic method
* [Database Validation](/core-technology/database-validation/overview) — the standalone feature for checking a document you already captured against a source
* [Workflow feature configs](/management-api/workflows/feature-configs#ocr--id-verification) — the `methods` key reference
# ID Verification Overview
Source: https://docs.didit.me/core-technology/id-verification/overview
Verify identity documents across 220+ countries and 14,000+ types with AI OCR. Pay-per-call $0.15, 500 free/month, sub-2-second inference.
The Academy lesson on reading a verification result reaches ID verification at 3:47 and shows why authenticity checks come before the extracted data.
Powered by cutting-edge AI, computer vision, and biometric technology, our solution ensures fast, accurate, and secure identity verification at scale. Designed to combat fraud, simplify compliance, and enhance user experience, Didit provides a robust and trustworthy platform that meets the highest industry standards.
Effortlessly begin the verification process with our intuitive, AI-driven capture system. Users upload or photograph their ID documents with real-time assistance:
* **Auto-detection** of document type and issuing country
* **Real-time visual cues** for optimal positioning, lighting, and focus
* **Automatic capture** when conditions are ideal — no manual retries needed
* Supports **passports**, **driver's licenses**, **national ID cards**, and **residence permits**
*Why it matters*: Our intelligent capture reduces user friction and ensures high-quality submissions on the first attempt, boosting conversion rates and trust.
Extract and validate identity data with unmatched precision.
**Data Extraction** — State-of-the-art technology processes all key fields:
| Capability | Details |
| -------------------- | ------------------------------------------------------------------------------------ |
| **Field extraction** | Full name, date of birth, document number, issue/expiry dates, nationality, and more |
| **OCR** | High-precision text recognition |
| **MRZ parsing** | Machine-Readable Zone decoding |
| **Barcode decoding** | Automatic barcode data extraction |
**Data Validation**:
* Cross-references data between visual zones, MRZ, and barcodes for consistency
* Cryptographic verification of issuer-signed barcodes, where the issuing authority publishes the certificates needed to check them. French national ID cards are covered today: their ANTS 2D-Doc barcode is verified against a signer certificate that chains to the ANTS trust list, so a barcode the authority did not sign is caught even when its contents look consistent
* Format and pattern matching to detect anomalies
* Real-time queries against government databases (where permitted) for authoritative verification
*Why it matters*: Comprehensive data processing ensures accuracy and eliminates errors, giving you confidence in every verification.
Our AI-powered system performs comprehensive checks to ensure document integrity and authenticity:
* **Document authenticity** verification
* **Tamper detection** and image integrity analysis
* **Document liveness detection** to prevent fraud from:
* Screen captures of digital documents
* Photos of documents displayed on screens
* Printed document copies
* Manipulated documents with altered portraits
* **Security feature validation** (holograms, watermarks, etc.)
* **Template matching** against certified database
Get actionable insights instantly with flexible delivery options.
**Real-Time Results**:
* Immediate updates via an intuitive dashboard
* Instant webhook notifications for automated workflows
* RESTful API for seamless integration into your existing systems
**Comprehensive Reporting**:
* Detailed PDF reports with verification outcomes and evidence
* Audit trails for compliance and record-keeping
* Customizable options to align with your operational needs
*Why it matters*: Fast, accessible results empower your team to act quickly while maintaining a secure, auditable process.
***
## Three ways to verify
A photographed document is one of three [ID Verification methods](/core-technology/id-verification/verification-methods). Per country you can also let people prove who they are without a document at all:
* **[Non-document lookup](/core-technology/id-verification/non-doc-lookup)** — the user types their national ID number plus a few personal details and we check them against a government register or another authoritative source, named per country. Where that source returns a portrait, a selfie, passive liveness and a face match are included.
* **[Digital ID wallets](/core-technology/id-verification/digital-id-wallets)** — the user signs in with a government or bank digital identity such as MitID, BankID or itsme, and the wallet shares a signed set of attributes.
Everything on this page describes document capture, which is unchanged: a workflow that says nothing about methods stays document capture only.
***
## Document Requirements
For optimal verification success, documents must meet these standards:
#### General Requirements
* Government-issued and valid within its configured validity period
* Physically intact (no damage, scratches, or stains obscuring details)
* All critical information (full name, date of birth, MRZ, etc.) clearly legible
* Consistent data across all submitted documents
#### Image Requirements
* Original, real-time photo (no screenshots, scans, or digital copies)
* Supported formats: JPG, JPEG, PNG, PDF
* Maximum file size: 5MB
* Full-color image with all document corners visible
* Free from glare, shadows, digital editing, or manipulation
* Physical documents required (digital IDs supported only in select regions where officially recognized)
## Additional Settings by Country & Document Type
Configure fine-grained rules per country and document type directly from the console. These controls let you tailor acceptance criteria and transcription preferences to your compliance needs.
* **Expiration mode**: Choose how to handle documents with past expiration dates.
* **Reject expired**: If the document's expiration date is earlier than today, mark it as expired and reject.
* **Allow expired**: Do not flag or block documents with a past expiration date.
* **Preferred character format**: Select how extracted names and fields should be normalized.
* **Prefer Latin characters (A–Z)**: Example: "Mohammed"
* **Prefer original script (non‑Latin)**: Example: "محمد"
* **Regional support & subtypes**: Enable documents by region and specify acceptable subtypes when a document type contains many variations.
* Example: For United States driver's licenses, select the exact subtypes you accept (e.g., Arizona Commercial Driver License, Indiana Operator License (REAL ID), New York Enhanced Driver License, etc.).
* Use the "Accepted subtypes" control to quickly include/exclude many variants (e.g., "128 selected").
> Tip: These settings apply per country and document type, so you can be strict in some markets while more permissive in others.
## Per-Country Age Restrictions
Enforce minimum and maximum age requirements on a per-country basis during ID verification. The system extracts the user's date of birth from the submitted document and checks it against the age limits configured for the document's issuing country.
### Configuration
Age restrictions are configured as a per-country table directly from the console:
| Column | Description |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Country** | The issuing country of the document (ISO 3166-1 alpha-3). Only countries enabled in your documents allowed list are shown. |
| **Min Age** | Minimum age required. Users younger than this value trigger the configured action (Decline or Review). |
| **Max Age** | Maximum age allowed. Users older than this value trigger the configured action. Leave empty for no upper limit. |
| **State/Region overrides** | For countries with sub-national age variation (e.g., United States, Mexico), open the settings icon to configure per-state minimum and maximum ages that override the country-level defaults. |
### Age of Majority Defaults
The console includes an **"Apply age of majority"** button that auto-fills each country's minimum age with the known legal age of majority (18 in most countries, with exceptions such as 19 in South Korea, 21 in the UAE, 20 in New Zealand, etc.). You can then adjust individual countries or add state-level overrides as needed.
### State/Region Overrides
Some countries have different legal age thresholds depending on the state or region. When a document is scanned, the OCR system extracts the region (e.g., "Mississippi", "Alabama", "Jalisco") from the document. If a state override is configured for that region, it takes precedence over the country-level age setting.
For example, if the United States has a minimum age of 18, but Mississippi has a state override of 21, a user presenting a Mississippi driver's license will be checked against the minimum age of 21.
### Configurable Actions
When a user's age falls outside the configured range for their document's country (or state), you can choose the action:
* **Decline**: The session is automatically declined.
* **Review**: The session is flagged for manual review.
Per-country age restrictions also work with **Adaptive Age Verification** workflows. When a borderline case triggers ID verification fallback, the age check uses these per-country settings to make the final decision based on the document's issuing country and region.
# ID verification report
Source: https://docs.didit.me/core-technology/id-verification/report-id-verification
Read Didit's ID Verification report: status, extracted document fields, image-quality scores, cross-session matches, and where data appears in responses.
## Overview
The **ID verification report** captures everything Didit extracted and validated from a government-issued identity document — passports, ID cards, driver's licenses, and residence permits. It bundles the OCR'd biographical and document fields, signed URLs to the captured media, parsed and geolocated address data, per-side image quality scores, MRZ contents, multi-script (Latin / non-Latin) breakdowns, and cross-session document matches.
The report is produced after the user completes the document-capture step in a workflow (or hits the standalone OCR endpoint). Didit identifies the document type, runs OCR, validates the MRZ / barcode / QR code, parses the address, screens for tampering (screen capture, printed copy, portrait manipulation), and compares the document against every other document captured in your application.
Each report carries its own `status` — independent of the overall session status — that reflects how the ID step alone resolved:
* **Approved** — document recognized, all required fields extracted, no decline-routed warning fired.
* **In Review** — one or more warnings fired and your workflow routes them to review.
* **Declined** — an auto-decline condition fired (unsupported document, expired document, portrait missing, blocklist hit, adaptive-age failure) or a warning whose configured action is Decline fired (minimum/maximum age default to Decline).
* **Expired** — the document's `expiration_date` passed **after** the verification. Didit's ongoing expiration monitoring flips the report from `Approved` to `Expired`, appends a `DOCUMENT_EXPIRED` warning, and moves an approved session to the `Kyc Expired` session status.
* **Not Finished** — the user never completed the capture step.
## Where it appears in API responses
The ID report ships inside the `id_verifications[]` array — **always a JSON array**, never a singular `id_verification` object. Multiple entries appear when a workflow runs more than one document step (for example a primary ID plus a step-up).
* **Session decision API** — `GET /v3/session/{sessionId}/decision/` returns `id_verifications[]` at the top level. See [Retrieve session decision](/sessions-api/retrieve-session).
* **Webhooks** — `session.status.updated` payloads include the same `id_verifications[]` array once the step has produced data. See [Webhooks](/integration/webhooks).
* **Standalone OCR API** — direct document submission returns a singular `id_verification` object with the same fields. See [OCR standalone API](/standalone-apis/id-verification).
Read `response.id_verifications[0]` and iterate the array. The singular `id_verification` shape only exists on the standalone OCR API — it does not exist on the v3 decision endpoint.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#id-verification) reference page. The fields below come straight from `IDVerificationV3Serializer`, which extends `IDVerificationV2Serializer` and adds `node_id` plus the cross-session `matches[]` array — 40 fields in total.
### Top-level fields
| Field | Type | Description |
| ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status` | `"Approved" \| "Declined" \| "In Review" \| "Expired" \| "Not Finished"` | ID-step status. |
| `node_id` | string \| null | Workflow graph node that produced this report. |
| `document_type` | `"Passport" \| "Identity Card" \| "Driver's License" \| "Residence Permit" \| "Health Insurance Card" \| "Tax Card" \| "Social Security Card" \| "Work Permit" \| "Visa" \| "Public Service Card" \| "Birth Certificate" \| "Firearm License" \| "Other" \| null` | Recognized top-level document family (human-readable). |
| `document_subtype` | string \| null | Document subtype code from Didit's document registry, filtered to what the workflow's document settings accept. Region-specific documents include the region, for example `QUEENSLAND_DRIVER_LICENSE_GENERIC`. See [Document subtypes](/core-technology/id-verification/document-subtypes-id-verification) for the full code list, including how to tell a US commercial driver's license (CDL) apart from a standard one. |
| `document_number` | string | OCR'd document number. On driving licences that print both a licence number and a card number, see the note below the table. |
| `personal_number` | string | OCR'd personal / national identification number, when distinct from the document number. |
| `portrait_image`, `front_image`, `front_video`, `back_image`, `back_video`, `full_front_image`, `full_back_image` | string (signed URL) | Captured media. URLs are signed and time-limited — download promptly, never persist the URL. |
| `front_image_camera_front`, `back_image_camera_front` | string (signed URL) \| null | Frames captured by the user's front-facing camera during document capture (used for the cross-camera face check). |
| `front_image_camera_front_face_match_score`, `back_image_camera_front_face_match_score` | float (0–100) \| null | Similarity score between the front-camera frame and the document portrait. |
| `front_image_quality_score`, `back_image_quality_score` | object \| null | Quality assessment per side (see Image quality scores). |
| `date_of_birth`, `expiration_date`, `date_of_issue` | string (YYYY-MM-DD) | Document dates. |
| `age` | number | Holder age in years computed from `date_of_birth`. |
| `issuing_state` | string (ISO 3166-1 alpha-3) | Issuing country code. |
| `issuing_state_name` | string | Localized country name. |
| `first_name`, `last_name`, `full_name` | string | Holder name in the script chosen by `preferred_characters` (see below). |
| `gender` | `"M" \| "F" \| "U"` | Gender, when present on the document (`U` = unknown). |
| `address`, `formatted_address` | string | Raw and geocoded/formatted address strings. |
| `place_of_birth` | string | OCR'd place of birth. |
| `marital_status` | `"SINGLE" \| "MARRIED" \| "DIVORCED" \| "WIDOWED" \| "UNKNOWN"` | Marital status, when present on the document. |
| `nationality` | string (ISO 3166-1 alpha-3) | Holder nationality, when distinct from issuing state. |
| `extra_fields` | object | Document-specific extras (DL categories, blood group, alternate-script names, …). |
| `mrz` | object \| null | Parsed MRZ key/value fields. |
| `parsed_address` | object \| null | Structured, geocoded address (`street_1`, `street_2`, `city`, `region`, `country`, `postal_code`, `address_type`, `formatted_address`, `document_location`, `raw_results`, `is_best_match`). |
| `extra_files[]` | array of signed URLs | Additional images attached to this document. |
| `matches[]` | array | Cross-session document matches (see Cross-session matches). |
| `warnings[]` | array | Module-level warnings — see [ID verification warnings](/core-technology/id-verification/warnings-id-verification). |
**`issuing_state` is always country-level, never sub-national.** For every
document type, including driving licences issued by a state or province
(Australia, the United States, Canada), `issuing_state` and
`issuing_state_name` report the **country** (for example `AUS` /
`Australia`), not the state. The sub-national value, when the document
carries one, is available separately in `parsed_address.region` (falling
back to `extra_fields.state` on some document types). The **Business
Console** session overview shows this as a **Region** row underneath
**Issuing state** when present.
**Driver's licences that carry two numbers (Australia).** Australian licences
print both a licence number, which identifies the holder's driving
entitlement and stays the same when a card is replaced, and a card number,
which identifies the physical card and changes with every reissue. For these
licences Didit always maps the numbers the same way:
* `personal_number` — the **licence number**
* `document_number` — the **card number**
This applies to every Australian state and territory (NSW, VIC, QLD, WA, SA,
TAS, ACT, NT). On some variants the card number is printed only on the back,
so it is read only when the back of the licence is captured. When no card
number can be read, the licence number is reported as **both**
`document_number` and `personal_number`, so `document_number` always carries
an identifier once the verification finishes. `personal_number` is the
licence number either way. On other document types `document_number` remains
the primary OCR'd number and may itself be a holder-level identifier.
### Image quality scores
`front_image_quality_score` and `back_image_quality_score` each return:
| Field | Type | What it measures |
| --------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `focus_score` | number 0–100 | Sharpness — top-10% local Laplacian variance, robust to uniform backgrounds. Weight 45%. |
| `brightness_score` | number 0–100 | Penalizes too-dark or overexposed images. Weight 30%. |
| `brightness_issue` | `"ok" \| "too_dark" \| "too_bright"` | Direction of the brightness problem, when `brightness_score` is low. |
| `resolution_score` | number 0–100 | Based on total pixel count of the capture. Weight 25%. |
| `is_document_fully_visible` | boolean \| null | `true` when all four document corners are visible, `false` when any corner is cut off, `null` when corner detection was unavailable. |
| `overall_score` | number 0–100 | Weighted composite (focus 45%, brightness 30%, resolution 25%). |
Use `overall_score` as a quick suitability check — scores above **70** generally indicate a capture good enough for reliable verification.
### Cross-session matches
`matches[]` lists up to **5** other documents in your application that match this one — same **date of birth**, same **issuing country**, and a highly similar **full name**. Blocklisted documents additionally require an exact document-number match. Sessions belonging to the same user (same `vendor_data`) are excluded.
Each entry includes `session_id`, `session_number`, `vendor_data`, `verification_date`, `user_details` (`name`, `document_type`, `document_number`), `status`, `is_blocklisted` (`true` when the document was added to your blocklist), `api_service`, and a signed `front_image_url`.
## Status values
| Status | Meaning | Downstream effect |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `Approved` | Document recognized, required fields extracted, all checks passed at the configured thresholds. | Counts as a successful ID check. |
| `In Review` | One or more warnings fired and the workflow routes them to review. | Session also moves to `In Review` until a reviewer acts. |
| `Declined` | Auto-decline condition fired (unsupported document, expired document, portrait missing, blocklist hit, adaptive-age failure) or a warning configured to Decline fired. | Session is declined unless another approved branch satisfies the workflow. |
| `Expired` | The document's expiration date passed **after** approval. Set by Didit's ongoing expiration monitoring (respecting per-country `expiration_check_mode` exemptions), together with a `DOCUMENT_EXPIRED` warning. | An approved session moves to `Kyc Expired`. |
| `Not Finished` | User abandoned the capture step. | The ID branch did not produce a result. |
## Multi-script documents (Latin / non-Latin)
Some documents — Kyrgyz, Kazakh, Russian, Japanese, Korean, Chinese, Arabic, Thai, and others — print the holder's name, address, or place of birth in **both** a Latin transliteration **and** the local script. You choose which script powers the top-level response with the workflow (or standalone-API) setting `preferred_characters`:
| Value | Populates top-level fields with | Opposite script goes to |
| ------------------- | ------------------------------------------------- | -------------------------- |
| `latin` *(default)* | The Latin transliteration printed on the document | `extra_fields.*_non_latin` |
| `non_latin` | The local-script values printed on the document | `extra_fields.*_latin` |
The alternate-script values live inside `extra_fields` under suffixed keys: `first_name_non_latin`, `last_name_non_latin`, `middle_name_non_latin`, `full_name_non_latin`, `address_non_latin`, `place_of_birth_non_latin` (or the matching `_latin` variants).
### When alternate-script fields appear
* **Only when both scripts are actually present on the document.** If an OCR field nominally labelled "Surname-Kyrgyz (Cyrillic)" is extracted as Latin-script text (homoglyphs or a Latin transliteration), it does **not** qualify as a non-Latin alternate and that key is omitted.
* `full_name_non_latin` (and `full_name_latin`) are always **reconstructed** from the individual first-, middle-, and last-name components when both are available — the raw "Full Name" field from OCR is ignored for consistency.
* When `preferred_characters=latin` but the document carries **only** non-Latin text (so the top-level fields end up non-Latin), Didit runs a transliteration pass and exposes `first_name_latin`, `last_name_latin`, `middle_name_latin`, `full_name_latin`, `place_of_birth_latin`, and `address_latin` as a Latin fallback.
```json theme={null}
{
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"extra_fields": {
"first_name_non_latin": "Айна"
}
}
```
`last_name_non_latin` is absent because the document's "Surname (Cyrillic)" field came back as Latin characters on this sample — there was no genuine non-Latin surname to extract.
## Examples
### Approved — clean Spanish identity card (all fields)
```json theme={null}
{
"id_verifications": [
{
"status": "Approved",
"document_type": "Identity Card",
"document_subtype": "ID_CARD_GENERIC",
"document_number": "SAMPLE-DOC-12345",
"personal_number": "SAMPLE-PER-12345",
"portrait_image": "https:///.../portrait.jpg?signature=...",
"front_image": "https:///.../front.jpg?signature=...",
"front_video": "https:///.../front.mp4?signature=...",
"back_image": "https:///.../back.jpg?signature=...",
"back_video": "https:///.../back.mp4?signature=...",
"full_front_image": "https:///.../full_front.jpg?signature=...",
"full_back_image": "https:///.../full_back.jpg?signature=...",
"front_image_camera_front": null,
"back_image_camera_front": null,
"front_image_camera_front_face_match_score": null,
"back_image_camera_front_face_match_score": null,
"front_image_quality_score": {
"focus_score": 85.3,
"brightness_score": 92.1,
"brightness_issue": "ok",
"is_document_fully_visible": true,
"resolution_score": 72.4,
"overall_score": 84.6
},
"back_image_quality_score": {
"focus_score": 79.1,
"brightness_score": 90.5,
"brightness_issue": "ok",
"is_document_fully_visible": true,
"resolution_score": 72.4,
"overall_score": 81.5
},
"date_of_birth": "1990-01-01",
"age": 35,
"expiration_date": "2031-06-02",
"date_of_issue": "2021-06-02",
"issuing_state": "ESP",
"issuing_state_name": "Spain",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"gender": "M",
"address": "Avda de Madrid 34",
"formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain",
"place_of_birth": "Madrid",
"marital_status": "SINGLE",
"nationality": "ESP",
"extra_fields": {},
"mrz": {
"document_number": "SAMPLE-DOC-12345",
"surname": "DOE",
"name": "JOHN",
"birth_date": "900101",
"expiry_date": "310602"
},
"parsed_address": {
"street_1": "Avda de Madrid 34",
"street_2": null,
"city": "Madrid",
"region": "Madrid",
"country": "ES",
"postal_code": "28822",
"address_type": "Avda",
"formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain",
"document_location": { "latitude": 40.4168, "longitude": -3.7038 },
"raw_results": {
"geometry": {
"location": { "lat": 40.4168, "lng": -3.7038 },
"location_type": "ROOFTOP"
}
},
"is_best_match": true
},
"extra_files": [],
"warnings": [],
"node_id": "id_primary",
"matches": []
}
]
}
```
### In Review — QR not detected on a document that should have one
Abridged — a real response always carries the full field set shown above.
```json theme={null}
{
"id_verifications": [
{
"status": "In Review",
"node_id": "id_primary",
"document_type": "Identity Card",
"document_subtype": "ID_CARD_GENERIC",
"document_number": "SAMPLE-DOC-12345",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"warnings": [
{
"feature": "ID_VERIFICATION",
"risk": "QR_NOT_DETECTED",
"additional_data": null,
"log_type": "warning",
"short_description": "QR not detected",
"long_description": "The system couldn't find or read the QR code on the document. This could be due to poor image quality or an unsupported document type.",
"node_id": "id_primary"
}
]
}
]
}
```
## Security note
The signed URLs returned for document images and videos are temporary — they expire after a limited validity window. Treat them as short-lived: download what you need promptly and do not cache or surface them publicly. Typically your application only needs `status`, a handful of biographical fields, and the warnings — store as little as possible to minimize the surface for any future breach.
## Related
* [ID verification warnings](/core-technology/id-verification/warnings-id-verification) — full warning enum, causes, and remediation.
* [Document monitoring](/core-technology/id-verification/document-monitoring-id-verification) — how Didit screens for tampering and cross-session duplicates.
* [Supported documents](/core-technology/id-verification/supported-documents-id-verification) — which document types and countries are covered.
* [Document subtypes](/core-technology/id-verification/document-subtypes-id-verification) — full `document_subtype` code list, including US commercial vs. standard driver's license detection.
* [Webhooks](/integration/webhooks) — listen for `session.status.updated` to receive the report.
* [Data models — ID verification](/reference/data-models#id-verification) — canonical schema with every field.
# Supported ID Documents
Source: https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification
Browse Didit's full list of supported ID documents by country: passports, national IDs, driver's licenses, and residence permits across 220+ countries.
Our state-of-the-art ID Verification system provides comprehensive global coverage, supporting identity documents from over 230 countries and territories in more than 130 languages. This extensive support ensures reliable and secure identity verification for your users worldwide.
Customize the accepted document types and countries through your [custom workflows](/console/workflows) to align with your business requirements and compliance needs.
### Security and Accuracy Standards
We maintain industry-leading standards in ID Verification through:
* **Advanced Template Matching**: Our document database is continuously updated with the latest document formats and security features
* **AI-Powered Verification**: State-of-the-art machine learning algorithms ensure precise document analysis and fraud detection
* **Data Security**: Enterprise-grade encryption and security protocols protect all processed documents
* **Regulatory Compliance**: Full alignment with international KYC/AML regulations and identity verification standards
* **Real-time Updates**: Immediate adaptation to new document formats and security features worldwide
***
## Supported Documents by Country
The table below shows all supported identity documents by country. The **Code** column contains the [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) country code used across Didit APIs (e.g. in `expected_details`, `issuing_state`, workflow conditions, and list entries).
***
## Document Types Explained
| Type | Code | Description |
| ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Passport** | `P` | National passports, e-passports, emergency passports, diplomatic passports |
| **ID Card** | `ID` | National identity cards, citizen cards, voter cards, digital IDs |
| **Driver's License** | `DL` | Standard driver's licenses, commercial licenses (CDL), learner permits — see [Document subtypes](/core-technology/id-verification/document-subtypes-id-verification) for how to tell a commercial license apart from a standard one |
| **Residence Permit** | `RP` | Permanent resident cards, alien/foreigner IDs, border crossing cards |
| **Health Insurance Card** | `HIC` | National health insurance cards |
| **Tax Card** | `TC` | National tax identification cards |
| **Social Security Card** | `SSC` | National social security cards |
| **Work Permit** | `WP` | Work and employment authorization documents |
| **Visa** | `VISA` | Visa stickers and visa documents |
| **Public Service Card** | `PSC` | Public service identity cards |
| **Birth Certificate** | `BC` | Birth certificates |
| **Firearm License** | `FIREARM` | Firearm ownership and carry licenses |
| **Other** | `OTHER` | Document types that don't fall into any category above |
Need a document type that isn't listed? [Contact us](mailto:support@didit.me) — we regularly add new document types based on customer needs. The [document subtype reference](/core-technology/id-verification/document-subtypes-id-verification) has the full breakdown of variants within each type.
# ID Verification Methods
Source: https://docs.didit.me/core-technology/id-verification/verification-methods
Verify identity three ways in one feature: document capture, a non-document lookup against an authoritative source, or a digital ID wallet sign-in.
ID Verification is one feature with three methods. You choose which methods a country may use; the end user gets whichever of them apply to the country they pick, and the session records which method actually produced the result.
| Method | What the user does | Assurance tier |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------- |
| **Document capture** | Photographs their passport, ID card, driver's licence or residence permit | Documentary |
| **Non-doc lookup** | Types their national ID number plus a few personal details, checked against a government or other authoritative source | Data match |
| **Wallet** | Signs in with a government or bank digital identity such as MitID, BankID or itsme | Cryptographic |
Document capture is unchanged. Everything you already configure through [`documents_allowed`](/management-api/workflows/feature-configs) — accepted types, subtypes, capture rules, thresholds, warnings — keeps working exactly as before, and a workflow that says nothing about methods stays document capture only.
## How each method works
### Document capture
What the rest of this section documents: the user photographs the document, Didit reads it, checks it for authenticity and returns the extracted fields. Available in every country Didit supports, at \$0.15 per check with the first 500 checks each month free. Start at the [ID Verification overview](/core-technology/id-verification/overview).
### Non-doc lookup
The user types their national ID number and a few personal details, and Didit checks them against the authoritative source for that country. No photograph of a document is involved.
That source is a government register in most countries, but not in all of them: in Canada, France, Indonesia, the Netherlands, Norway, Singapore, the United Kingdom and the United States it is a credit-bureau, financial-services, utility or residential-records file instead. Because provenance and the applicable consent regime differ with it, the exact source is named per country in the table below and on [Non-document lookup](/core-technology/id-verification/non-doc-lookup) — never generalised.
Where the source returns a portrait — South Africa, Nigeria, Argentina and Panama today, all four government registers — the user is also asked for a selfie. Passive liveness runs on that selfie and it is face-matched to the registry portrait, all inside the lookup and all inside the lookup price. Where the source returns data only there is nothing to face-match against, so no selfie is taken; run [Liveness](/core-technology/liveness/overview) and [Face Match](/core-technology/face-match/overview) as their own workflow steps if you need them.
Full per-country request fields, format rules and consent regimes are on [Non-document lookup](/core-technology/id-verification/non-doc-lookup).
### Wallet
The user signs in with a digital identity they already hold — a national eID or a bank identity. The wallet authenticates them, then shares a signed set of attributes with Didit. Nothing is photographed and nothing is typed.
Same-device sign-in hands the user to the wallet app and back; cross-device sign-in shows a QR code they scan with their phone. Per-wallet attributes, assurance levels and failure semantics are on [Digital ID wallets](/core-technology/id-verification/digital-id-wallets).
## Assurance tiers
Every ID Verification result carries an assurance tier derived from the method that produced it. The tier is an honest description of what was actually proven, not a ranking:
| Tier | Value on the session | What it means |
| ------------- | -------------------- | ---------------------------------------------------------------------------------------------------------- |
| Documentary | `documentary` | A physical document was captured, read and checked for authenticity. |
| Data match | `data_match` | The details the user gave matched a record held by the source that was queried. No document was inspected. |
| Cryptographic | `cryptographic` | A digital identity provider authenticated the user and signed the attributes it shared. |
Assurance tiers are an admin-facing label. They appear on the session payload, in the console and in exports. End users never see the tier, the price or the name of the source that was queried.
## Configuring methods per country
Methods live under the `methods` key on the ID Verification (`OCR`) feature config, keyed by [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) country code. Omit the key, omit a country, or omit a method inside a country, and that country is document capture only.
```json theme={null}
{
"feature": "OCR",
"config": {
"methods": {
"ZAF": {
"document": { "enabled": true },
"id_lookup": {
"enabled": true,
"max_attempts": 2,
"skip_liveness_and_face_match": true,
"on_partial_match": "fallback_to_document",
"on_no_match": "fallback_to_document",
"on_provider_error": "fallback_to_document"
},
"wallet": { "enabled": false, "providers": [], "on_failure": "fallback_to_document" }
},
"DNK": {
"document": { "enabled": true },
"wallet": { "enabled": true, "providers": ["mitid"], "on_failure": "fallback_to_document" }
}
}
}
}
```
Two rules the API enforces on save:
* **Availability is server-driven.** A method or a wallet that the capability catalog does not mark `available` for that country is rejected. Read the catalog first — through the console's **Countries** tab, or with the [`didit_workflow_get_id_verification_methods_catalog`](/integration/mcp/tools) MCP tool.
* **Wallets are an accept-list, never a ranking.** `providers` says which wallets a country may offer. The order you send has no meaning and is normalised away; the end user picks.
The full field-by-field reference, including defaults and validation, is in [Workflow feature configs](/management-api/workflows/feature-configs#ocr--id-verification).
## Fallback: what happens when a non-document method does not land
Four switches decide what happens next. Each one is either `fallback_to_document` (send the user to document capture) or `decline` (end the session with a declined ID Verification). All four default to `fallback_to_document`.
| Switch | Fires when |
| ----------------------------- | ------------------------------------------------------ |
| `id_lookup.on_partial_match` | The register answered, but some fields did not match. |
| `id_lookup.on_no_match` | The register answered and found no matching record. |
| `id_lookup.on_provider_error` | The register could not be reached or did not answer. |
| `wallet.on_failure` | The wallet sign-in was cancelled, timed out or failed. |
`id_lookup.max_attempts` (1 to 5, default 1) is how many answered lookups the user gets before the switch fires. A number that fails the client-side format check never reaches the register, so it does not consume an attempt — the user is simply asked to correct it.
If a switch says `fallback_to_document` but document capture is disabled for that country, the session is declined instead: there is nowhere to fall back to. A decline records `fallback_from` on the declined ID Verification, naming the method that failed and why. The current staging implementation also carries that evidence onto the document verification created after a fallback. Confirm production rollout before depending on that additional evidence.
## What each method costs
| Method | Price | Free tier |
| ---------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------- |
| Document capture | \$0.15 per check | First 500 checks each month |
| Non-doc lookup | [Public USD rates by country and identifier](/core-technology/id-verification/non-doc-lookup#coverage-and-pricing) | Not included |
| Wallet | [Price pending publication, by wallet](/core-technology/id-verification/digital-id-wallets) | Not included |
The 500 free ID Verification checks a month are scoped to document capture. Non-document methods are not part of that allowance. A lookup uses the public Database Validation retail rates for the services it runs; several services can contribute to one attempt. Nigeria selects either NIN or BVN. The [country table](/core-technology/id-verification/non-doc-lookup#coverage-and-pricing) shows the full configured attempt price, with each answered retry billed separately. Wallet prices remain unpublished while the catalog marks them as placeholders.
### Billing rules
* **A register that answered bills the lookup.** Match, partial match and no match all count — the query was run and the answer is the product.
* **A register that never answered is not billed.** A provider error costs nothing.
* **A number that fails the client-side format check is never billed** and never counted against `max_attempts`, because it never reached the register.
* **Document capture bills on top when the user falls back.** A lookup that answered and then fell back to a document is two charges, because two checks ran.
* **An abandoned, cancelled, timed-out or failed wallet sign-in is not billed.** Only a completed sign-in is.
## Coverage
Document capture is available in every country Didit supports and is not repeated below: \$0.15 per check, first 500 a month free. This table lists only the countries where a **non-document** method exists. A country that is not listed, or a cell that reads Not planned, is document capture only.
| Country | Code | Non-doc lookup | Digital ID wallets |
| -------------------- | ----- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| Argentina | `ARG` | Available — RENAPER | Not planned |
| Austria | `AUT` | Not planned | EUDI Wallet (coming soon) |
| Belgium | `BEL` | Not planned | itsme (coming soon), Smart-ID (coming soon), EUDI Wallet (coming soon) |
| Bolivia | `BOL` | Available — SEGIP | Not planned |
| Brazil | `BRA` | Available — Receita Federal | gov.br (coming soon) |
| Bulgaria | `BGR` | Not planned | EUDI Wallet (coming soon) |
| Cambodia | `KHM` | Available — Ministry of Interior voter register | Not planned |
| Canada | `CAN` | Available — Canadian credit bureau records (FINTRAC dual-process) | Not planned |
| Chile | `CHL` | Available — Servicio de Registro Civil e Identificación | Not planned |
| China | `CHN` | Available — NCIIC (National Citizen Identity Information Center) | Not planned |
| Colombia | `COL` | Available — Registraduría General de la Nación / ANI | Not planned |
| Costa Rica | `CRI` | Available — Tribunal Supremo de Elecciones | Not planned |
| Croatia | `HRV` | Not planned | EUDI Wallet (coming soon) |
| Cyprus | `CYP` | Not planned | EUDI Wallet (coming soon) |
| Czechia | `CZE` | Not planned | Bank iD (coming soon), MojeID (coming soon), EUDI Wallet (coming soon) |
| Denmark | `DNK` | Available — CPR register | MitID (coming soon), EUDI Wallet (coming soon) |
| Dominican Republic | `DOM` | Available — Junta Central Electoral | Not planned |
| Ecuador | `ECU` | Available — Registro Civil del Ecuador | Not planned |
| El Salvador | `SLV` | Available — RNPN | Not planned |
| Estonia | `EST` | Not planned | Smart-ID (coming soon), Mobile-ID (coming soon), EUDI Wallet (coming soon) |
| Finland | `FIN` | Available — DVV population register | Finnish Trust Network (coming soon), EUDI Wallet (coming soon) |
| France | `FRA` | Available — French residential and utility records | FranceConnect (coming soon), EUDI Wallet (coming soon) |
| Germany | `DEU` | Not planned | Personalausweis (coming soon), EUDI Wallet (coming soon) |
| Greece | `GRC` | Not planned | EUDI Wallet (coming soon) |
| Guatemala | `GTM` | Available — SAT | Not planned |
| Honduras | `HND` | Available — CNE | Not planned |
| Hungary | `HUN` | Not planned | EUDI Wallet (coming soon) |
| Iceland | `ISL` | Not planned | Auðkenni (coming soon), EUDI Wallet (coming soon) |
| India | `IND` | Available — UIDAI (Aadhaar) | Not planned |
| Indonesia | `IDN` | Available — Indonesian population register via residential records | Not planned |
| Ireland | `IRL` | Not planned | EUDI Wallet (coming soon) |
| Italy | `ITA` | Not planned | EUDI Wallet (coming soon) |
| Kenya | `KEN` | Available — IPRS (Integrated Population Registration System) | Not planned |
| Latvia | `LVA` | Not planned | Smart-ID (coming soon), Mobile-ID (coming soon), EUDI Wallet (coming soon) |
| Liechtenstein | `LIE` | Not planned | EUDI Wallet (coming soon) |
| Lithuania | `LTU` | Not planned | Smart-ID (coming soon), Mobile-ID (coming soon), EUDI Wallet (coming soon) |
| Luxembourg | `LUX` | Not planned | itsme (coming soon), EUDI Wallet (coming soon) |
| Malaysia | `MYS` | Available — JPN (National Registration Department) | Not planned |
| Malta | `MLT` | Not planned | EUDI Wallet (coming soon) |
| Mexico | `MEX` | Available — RENAPO | Not planned |
| Netherlands | `NLD` | Available — Dutch residential records | itsme (coming soon), iDIN (coming soon), EUDI Wallet (coming soon) |
| Nigeria | `NGA` | Available — NIMC / NIBSS | Not planned |
| Norway | `NOR` | Available — Norwegian residential records | BankID (coming soon), Vipps (coming soon), Buypass ID (coming soon), EUDI Wallet (coming soon) |
| Panama | `PAN` | Available — Tribunal Electoral / SIB | Not planned |
| Paraguay | `PRY` | Available — Registro del Estado Civil | Not planned |
| Peru | `PER` | Available — RENIEC | Not planned |
| Poland | `POL` | Not planned | EUDI Wallet (coming soon) |
| Portugal | `PRT` | Not planned | EUDI Wallet (coming soon) |
| Romania | `ROU` | Not planned | EUDI Wallet (coming soon) |
| Singapore | `SGP` | Available — Singapore credit bureau and utility records | Not planned |
| Slovakia | `SVK` | Not planned | EUDI Wallet (coming soon) |
| Slovenia | `SVN` | Not planned | EUDI Wallet (coming soon) |
| South Africa | `ZAF` | Available — Department of Home Affairs | Not planned |
| Spain | `ESP` | Not planned | EUDI Wallet (coming soon) |
| Sweden | `SWE` | Available — Skatteverket population register | BankID (coming soon), Freja eID (coming soon), EUDI Wallet (coming soon) |
| Thailand | `THA` | Available — DOPA civil registration | Not planned |
| Ukraine | `UKR` | Not planned | Diia (coming soon) |
| United Arab Emirates | `ARE` | Not planned | UAE PASS (coming soon) |
| United Kingdom | `GBR` | Available — UK credit bureau and financial services records | OneID (coming soon), GOV.UK Wallet (coming soon) |
| United States | `USA` | Available — US credit bureau and financial services records | Not planned |
| Uruguay | `URY` | Available — Dirección Nacional del Registro de Estado Civil | Not planned |
| Venezuela | `VEN` | Available — CNE | Not planned |
Coverage is generated from the capability catalog the backend serves, so this table is the same source the console and the API validate against. **Coming soon** means the entry is visible in the console and cannot be switched on yet; it is not a live capability and carries no committed date.
## What lands on the session
Every ID Verification result carries the method that produced it, its assurance tier, and — when a non-document method ran — a per-method evidence block. Document sessions read exactly as they did before, with the new keys `null`.
| Field | Type | Description |
| --------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verification_method` | string | `document`, `id_lookup` or `wallet`. |
| `assurance` | string | `documentary`, `data_match` or `cryptographic`. |
| `wallet_provider` | string \| null | Catalog wallet id when a wallet produced the result. |
| `id_lookup` | object \| null | Registry evidence: source, checked-at, attempts, per-field comparison, registry portrait. |
| `wallet_verification` | object \| null | Credential evidence: provider, issuing authority, level of assurance, signature validity, shared attributes. |
| `fallback_from` | object \| null | `{ "method": ..., "reason": ..., "action": ... }` when a non-document method failed and the country's switch declined the session; staging also preserves it after document fallback. |
Field-by-field types and examples for all three methods are in the [ID verification data model](/reference/data-models#id-verification), and the same keys arrive on the [`status.updated` webhook](/integration/webhooks).
## Related
* [Non-document lookup](/core-technology/id-verification/non-doc-lookup) — per-country fields, formats, consent and retention
* [Digital ID wallets](/core-technology/id-verification/digital-id-wallets) — per-wallet attributes, assurance and failure semantics
* [ID Verification methods in the console](/console/id-verification-methods) — the Countries tab and the session method chip
* [Workflow feature configs](/management-api/workflows/feature-configs#ocr--id-verification) — the `methods` key reference
# ID verification warnings
Source: https://docs.didit.me/core-technology/id-verification/warnings-id-verification
Every warning Didit's ID verification module emits — auto-decline triggers, configurable risk groups, mismatch and tampering codes — with cause and remediation.
## Overview
Warnings on the ID verification report flag every condition Didit observed while reading and validating the document. They land in the `warnings[]` array on each item of `id_verifications[]` (see [ID verification report](/core-technology/id-verification/report-id-verification)), with the shape described in [Data models — Warning object](/reference/data-models#warning-object): `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`.
Every warning has three layers:
1. **The `risk` code** — a stable identifier you can match on in your code.
2. **The `log_type`** — derived from the action your configuration assigns to the risk at evaluation time: **Decline → `error`**, **Review → `warning`**, anything else → `information`.
3. **The decision impact** — set by the workflow's configurable risk groups. The same `risk` code can be routed to **Approve**, **Review**, or **Decline** depending on how your application is configured.
## Auto-decline conditions
In workflows, the following risks always force the ID report (and the session) to `Declined`. They cannot be configured:
| Risk | Trigger |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | The submitted document type is not on your application's allowed-documents list. `additional_data` includes `issuing_state`, `document_type`, `document_subtype`, `document_region`, and `document_region_with_subtype` so you can enable the exact document in your workflow. |
| `DOCUMENT_EXPIRED` | The document's expiration date has passed at capture time. |
| `PORTRAIT_IMAGE_NOT_DETECTED` | No portrait image could be located on the document. |
| `ID_DOCUMENT_IN_BLOCKLIST` | The document matches an entry on your application's blocklist. |
| `AGE_BELOW_MINIMUM` | Adaptive age-verification workflows only: extracted age is below the minimum. |
| `AGE_NOT_DETECTED` | Adaptive age-verification workflows only: age could not be extracted from the document. |
For the **standalone OCR API**, the unconditional-decline set is `DOCUMENT_EXPIRED`, `MINIMUM_AGE_NOT_MET`, `PORTRAIT_IMAGE_NOT_DETECTED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, and `PUBLIC_DOCUMENT_IMAGE_DETECTED`. In workflows, minimum age is configurable (default Decline) and the three document-liveness risks are threshold-driven (see below).
## Configurable verification settings
In the Didit console, the remaining ID risks are grouped into settings you can route to **Approve**, **Review**, or **Decline** (per workflow, and per node in graph workflows):
| Group | Risks | Default action |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| **Minimum Age** | `MINIMUM_AGE_NOT_MET` | Decline |
| **Maximum Age** | `MAXIMUM_AGE_EXCEEDED` | Decline |
| **Duplicated User** | `POSSIBLE_DUPLICATED_USER` (skipped when the document number is in your document allowlist) | Approve (information) |
| **Invalid Code** | `QR_NOT_DETECTED`, `BARCODE_NOT_DETECTED`, `QR_VALIDATION_FAILED`, `BARCODE_VALIDATION_FAILED` | Approve (information) |
| **Invalid MRZ** | `MRZ_VALIDATION_FAILED`, `MRZ_NOT_DETECTED` | Approve (information) |
| **Data Inconsistency** | `DATA_INCONSISTENT`, `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME`, `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS`, `DOCUMENT_SIDES_MISMATCH`, `ID_VERIFICATION_DATA_MISMATCH_BETWEEN_DOCUMENTS` | Approve (information) |
| **Invalid Validation** | `INVALID_DATE`, `COULD_NOT_RECOGNIZE_DOCUMENT`, `DOCUMENT_NUMBER_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED` | Review |
| **Expected Details Mismatch** | `FULL_NAME_MISMATCH_WITH_PROVIDED`, `GENDER_MISMATCH_WITH_PROVIDED`, `DOB_MISMATCH_WITH_PROVIDED`, `COUNTRY_MISMATCH_WITH_PROVIDED`, `NATIONALITY_MISMATCH_WITH_PROVIDED`, `IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED` | Review |
| **Expiration Date Not Detected** | `EXPIRATION_DATE_NOT_DETECTED` | Approve (information) |
| **Document or Personal Number Format Mismatch** | `DOCUMENT_NUMBER_FORMAT_MISMATCH`, `PERSONAL_NUMBER_FORMAT_MISMATCH` (one shared action) | Approve (information) |
| **Unparsed Address** | `UNPARSED_ADDRESS` | Approve (information) |
| **Image Quality** | `IMAGE_TOO_BLURRY`, `IMAGE_TOO_DARK`, `IMAGE_TOO_BRIGHT` (one action per risk) | Approve (information) |
| **Document Without Portrait** | `DOCUMENT_HAS_NO_PORTRAIT` | Review |
Two ID risk families are **threshold-driven** instead of Approve/Review/Decline toggles:
* **Document liveness** — `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`. Each fraud type has its own decline and review thresholds: score below the decline threshold → `error` + Declined; below the review threshold → `warning` + In Review; otherwise `information`.
* **Cross-camera face match** — `LOW_FRONT_CAMERA_FACE_MATCH_SIMILARITY`, governed by `document_selfie_portrait_match_decline_threshold` / `document_selfie_portrait_match_review_threshold`.
One ID risk is neither configurable nor threshold-driven: `BARCODE_SIGNATURE_INVALID` always routes to **Review**. It fires only when a barcode carries an issuing-authority signature that fails cryptographic verification, so there is no configuration in which a proven forgery is ignored.
## Warnings produced
Verified against `collect_ocr_logs`, the document-capture flow, and `process_ocr_logs_and_get_status`. Short descriptions below are the exact `short_description` strings returned by the API.
### Document recognition and quality
| Risk | Short description | Cause | Severity |
| ---------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `COULD_NOT_RECOGNIZE_DOCUMENT` | Could not validate Document | The system could not confirm the authenticity or validity of the submitted document — often poor image quality or an unsupported document type. Also used as the fallback for unrecognized processing failures. | Configurable — Invalid Validation, default Review |
| `COULD_NOT_DETECT_DOCUMENT_TYPE` | Could not detect document type | The OCR engine could not classify the document family (passport vs ID vs DL). When this fires, `DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` is suppressed (the type is unknown, so "not supported" would be contradictory). | Configurable — Invalid Validation, default Review |
| `DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | Document not supported for your application | The recognized document type is not on the application's allowed list. `additional_data` carries the country, document type, subtype, and region-aware subtype. | `error` — **auto-decline** |
| `DOCUMENT_EXPIRED` | Document expired | At capture time: the document's expiration date is in the past (`error`, auto-decline). Post-approval: ongoing expiration monitoring appends this warning with `log_type: "warning"` when an approved document's date passes, and the report status becomes `Expired`. | `error` at capture; `warning` from monitoring |
| `DOCUMENT_SIDES_MISMATCH` | Document sides mismatch | Front and back images do not belong to the same document. | Configurable — Data Inconsistency, default information |
| `PORTRAIT_IMAGE_NOT_DETECTED` | Portrait image not detected | No portrait could be located on a document that should have one (deferred when the user uploads the non-portrait side first). | `error` — **auto-decline** |
| `DOCUMENT_HAS_NO_PORTRAIT` | Document has no portrait | The recognized document family never carries a portrait photo on any side (some residence permits, health cards, registration certificates, tax cards, and a subset of DLs/IDs). This is a catalog-level fact about the document type, not an image-quality issue — re-scanning the document cannot change the outcome. | Configurable — Document Without Portrait, default Review |
| `IMAGE_TOO_BLURRY` / `IMAGE_TOO_DARK` / `IMAGE_TOO_BRIGHT` | Document image is too blurry / too dark / too bright | The image-quality gate rejected an upload (the user is asked to retake). `additional_data` carries the failing `side`. A successful retake clears the stale warning; the gate is skipped on the user's final allowed attempt so they are never hard-blocked. | Configurable — per-risk Image Quality action, default information |
The capture flow can also reject an upload with the feedback codes `IMAGE_RESOLUTION_TOO_LOW`, `DOCUMENT_NOT_FULLY_VISIBLE`, `IMAGE_QUALITY_TOO_LOW`, and `DOCUMENT_OCCLUSION_DETECTED` (a finger or object is judged to be physically covering printed text). These prompt a retake in the SDK but are not persisted to the report's `warnings[]` — only the blurry/dark/bright risks are.
If your workflow anchors identity on Face Match rather than the document portrait, consider routing **Document Without Portrait** to Decline — otherwise a portrait-less document family reaches Face Match with nothing to compare against.
### Field extraction
| Risk | Short description | Cause | Severity |
| --------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `NAME_NOT_DETECTED` | First name and/or last name not detected | First name and last name could not be extracted. Exempt when the holder legitimately has a single name (one-name countries, or the MRZ itself carries a single name) and at least one name value was extracted. | Configurable — Invalid Validation, default Review |
| `DATE_OF_BIRTH_NOT_DETECTED` | Date of birth not detected | Date of birth could not be extracted from a document that should have one. | Configurable — Invalid Validation, default Review |
| `DOCUMENT_NUMBER_NOT_DETECTED` | Document number not detected | Neither the document number nor a personal number could be extracted. | Configurable — Invalid Validation, default Review |
| `EXPIRATION_DATE_NOT_DETECTED` | Expiration date not detected | Expiration date could not be extracted from a document that should have one. | Configurable — Expiration Date Not Detected, default information |
| `INVALID_DATE` | Invalid Date | A date on the document (date of birth or expiry) doesn't parse to a valid date. In the current capture flow this surfaces as a retake prompt during upload rather than a stored warning; the code remains in the Invalid Validation routing group and may appear on older sessions. | Configurable — Invalid Validation, default Review |
| `DOCUMENT_NUMBER_FORMAT_MISMATCH` | Document number format mismatch | Document number does not match the expected format configured for this document type. `additional_data` carries `expected_format` and `extracted_value`. | Configurable — Format Mismatch, default information |
| `PERSONAL_NUMBER_FORMAT_MISMATCH` | Personal number format mismatch | Personal number does not match the expected format configured for this document type. | Configurable — Format Mismatch, default information |
### MRZ, QR, barcode
All three MRZ risks are skipped for document types with known-unreliable MRZs.
| Risk | Short description | Cause | Severity |
| ------------------------------------------ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `MRZ_NOT_DETECTED` | MRZ not detected | MRZ could not be located on a document that should have one. | Configurable — Invalid MRZ, default information |
| `MRZ_VALIDATION_FAILED` | MRZ is not valid | MRZ failed checksum / format validation. Often a low-quality capture; can indicate tampering. | Configurable — Invalid MRZ, default information |
| `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` | MRZ and data extracted from OCR have some discrepancies | MRZ contents disagree with the VIZ (visible-zone) OCR. Possible alteration. | Configurable — Data Inconsistency, default information |
| `QR_NOT_DETECTED` | QR not detected | QR code could not be located on a document that should have one. | Configurable — Invalid Code, default information |
| `QR_VALIDATION_FAILED` | QR validation failed | QR contents could not be validated against the rest of the document. | Configurable — Invalid Code, default information |
| `BARCODE_NOT_DETECTED` | Barcode not detected | Barcode could not be located on a document that should have one. | Configurable — Invalid Code, default information |
| `BARCODE_VALIDATION_FAILED` | Barcode validation failed | Barcode contents could not be validated against the rest of the document. | Configurable — Invalid Code, default information |
| `BARCODE_SIGNATURE_INVALID` | Barcode signature invalid | The barcode carries a digital signature from the issuing authority, and that signature did not verify - the data in the barcode is not the data the authority signed, which points to a forged or altered document. Currently checked for **French 2D-Doc (ANTS)** barcodes, where a verified signature lets Didit promote fields without needing MRZ corroboration; AAMVA (US/Canada driving licences and IDs) signatures are detected and logged only, not yet enforced. | Fixed - Review (own action group `invalid_barcode_signature_action`; not yet console/API-configurable) |
| `DATA_INCONSISTENT` | OCR data in the document is not consistent | Data extracted from different parts of the document disagrees — typically the front and back sides of the same document. | Configurable — Data Inconsistency, default information |
Barcode signature verification currently covers French national ID cards, whose ANTS 2D-Doc barcode is signed by the issuing authority. `BARCODE_SIGNATURE_INVALID` is raised only when the signature check itself rejects the payload. A barcode with no signature, one signed by an authority not yet in Didit's trust store, or one whose signature cannot be decoded is treated as unauthenticated data and validated against the MRZ and printed text as before, without raising this risk.
US and Canadian driving licence (AAMVA) barcodes are not cryptographically verified: digital signing there is optional and jurisdiction-specific, and no jurisdiction publishes verification keys, so Didit only detects and logs whether a payload carries a signature; the signature itself is never checked. AAMVA signature failures are not a risk signal today. A tampered AAMVA payload only surfaces as `BARCODE_VALIDATION_FAILED`, and only if the tampering also makes the barcode disagree with the MRZ or printed/OCR data - a forged signature on an otherwise internally consistent payload passes with no signal at all.
The user is never asked to re-capture on `BARCODE_SIGNATURE_INVALID`: a signature that does not match the payload cannot be fixed by taking a better photo.
### Identity matching
| Risk | Short description | Cause | Severity |
| ------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `ID_DOCUMENT_IN_BLOCKLIST` | ID document in blocklist | The document matches a blocklisted document (same date of birth, issuing country, and exact document number) or a manually blocklisted document number. `additional_data` carries the blocklisted session reference. | `error` — **auto-decline** |
| `ID_DOCUMENT_IN_ALLOWLIST` | ID document in allowlist | The document number matches your document allowlist, so the duplicate-user action was skipped. `additional_data` carries the `document_number`. | `information` |
| `POSSIBLE_DUPLICATED_USER` | Possible duplicated user from other session | Another document in your application has the same date of birth, the same issuing country, and a highly similar full name — under a different user (`vendor_data`), regardless of that session's status. `additional_data` carries the duplicate session reference. | Configurable — Duplicated User, default information |
| `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS` | Document name differs from other approved documents | Name on this document differs from previously approved documents for the same user. Only produced for Didit-protocol (UserKYC) accounts, not standard API sessions. | Configurable — Data Inconsistency, default information |
| `ID_VERIFICATION_DATA_MISMATCH_BETWEEN_DOCUMENTS` | Data mismatch between ID verification documents | Two ID documents in the same session disagree on name / date of birth. | Configurable — Data Inconsistency, default information |
| `LOW_FRONT_CAMERA_FACE_MATCH_SIMILARITY` | Selfie during document capture doesn't match document portrait | The face captured from the user's front camera during document capture does not match the portrait on the document well. | Threshold-driven: `error` below the decline threshold, `warning` at or below the review threshold |
### Document liveness (tampering)
Threshold-driven per fraud type in workflows; unconditionally declining on the standalone OCR API.
| Risk | Short description | Cause | Severity |
| -------------------------------- | ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `SCREEN_CAPTURE_DETECTED` | Screen capture of document detected | Document was captured from a screen rather than a physical document. | `error` below the decline threshold, `warning` below the review threshold, else `information` |
| `PRINTED_COPY_DETECTED` | Printed copy of document detected | Document appears to be a printed copy rather than the original. | Same tri-bucket thresholds |
| `PORTRAIT_MANIPULATION_DETECTED` | Portrait manipulation detected | The portrait region appears to have been replaced or altered. | Same tri-bucket thresholds |
### Known public document images
| Risk | Short description | Cause | Severity |
| -------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `PUBLIC_DOCUMENT_IMAGE_DETECTED` | Known public internet document image detected | The submitted document image matches a known publicly available internet image (for example a specimen, press, or stock photo of a document), so it cannot be a photo of the user's own physical document. Matching uses exact and perceptual (pHash/dHash) fingerprinting against Didit's corpus of known public document images; `additional_data` carries `match_type` (`exact` or `perceptual`), `source`, `source_url`, and a `similarity_score` (0-100). | Fixed - Review in workflows; **auto-decline** on the standalone `/id-verification` API |
### Expected-details mismatches
When you pass `expected_details` to a session, Didit raises a mismatch warning per field that does not match. One grouping (`expected_details_mismatch_action`, default Review) controls routing for all of these:
| Risk | Short description | Cause |
| ---------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `FULL_NAME_MISMATCH_WITH_PROVIDED` | Full name mismatch with provided information | Provided name scores below the name-match threshold against the document. |
| `DOB_MISMATCH_WITH_PROVIDED` | Date of birth mismatch with provided information | Provided date of birth does not match. |
| `GENDER_MISMATCH_WITH_PROVIDED` | Gender mismatch with provided information | Provided gender does not match. |
| `COUNTRY_MISMATCH_WITH_PROVIDED` | Country mismatch with provided information | Provided country does not match `issuing_state`. |
| `NATIONALITY_MISMATCH_WITH_PROVIDED` | Nationality mismatch with provided information | Provided nationality does not match. |
| `IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED` | Identification number mismatch with provided information | Provided ID number does not match the document number, personal number, or tax number. |
Each mismatch warning carries the expected and extracted values in `additional_data`.
### Age
| Risk | Short description | Cause | Severity |
| ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `MINIMUM_AGE_NOT_MET` | Minimum age not met | Holder is below the configured minimum age (global or per-country/state). `additional_data` carries `min_age_allowed` and `user_age`. | Configurable — Minimum Age, default Decline (`error`); unconditional decline on the standalone OCR API |
| `MAXIMUM_AGE_EXCEEDED` | Maximum age exceeded | Holder is above the configured maximum age. `additional_data` carries `max_age_allowed` and `user_age`. | Configurable — Maximum Age, default Decline (`error`) |
| `AGE_NOT_DETECTED` | Age not detected | Adaptive age-verification workflows: age could not be extracted from the document. | `error` — **auto-decline** |
| `AGE_BELOW_MINIMUM` | Age below minimum | Adaptive age-verification workflows: extracted age is below the minimum. | `error` — **auto-decline** |
### Address
| Risk | Short description | Cause | Severity |
| ------------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `UNPARSED_ADDRESS` | Unable to determine address location | The document defines an address field, but the extracted address could not be parsed or geolocated. | Configurable — Unparsed Address, default information |
## Cross-session document matches
In addition to the warnings above, the V3 ID report ships a `matches[]` array — up to 5 other documents in your application with the same date of birth, the same issuing country, and a highly similar full name (blocklisted documents additionally require an exact document-number match), excluding sessions that belong to the same user. Documents added to your blocklist appear with `is_blocklisted: true`. Documents on your allowlist still appear in matches, but their duplicate-user action is skipped.
Each match includes `session_id`, `session_number`, `vendor_data`, `verification_date`, `user_details` (`name`, `document_type`, `document_number`), `status`, `is_blocklisted`, `api_service`, and a signed `front_image_url`.
## Examples
### Configurable warning at its default routing (information)
```json theme={null}
"warnings": [
{
"feature": "ID_VERIFICATION",
"risk": "QR_NOT_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "QR not detected",
"long_description": "The system couldn't find or read the QR code on the document. This could be due to poor image quality or an unsupported document type.",
"node_id": "id_primary"
}
]
```
### Auto-decline on expired document
```json theme={null}
"warnings": [
{
"feature": "ID_VERIFICATION",
"risk": "DOCUMENT_EXPIRED",
"additional_data": null,
"log_type": "error",
"short_description": "Document expired",
"long_description": "The document's expiration date has passed, rendering it no longer valid for use.",
"node_id": "id_primary"
}
]
```
### Expected-details mismatch with payload
```json theme={null}
"warnings": [
{
"feature": "ID_VERIFICATION",
"risk": "DOB_MISMATCH_WITH_PROVIDED",
"additional_data": {
"expected_dob": "1990-01-01",
"extracted_dob": "1991-03-15"
},
"log_type": "warning",
"short_description": "Date of birth mismatch with provided information",
"long_description": "The provided date of birth in `expected_details` doesn't match the date extracted from the document.",
"node_id": "id_primary"
}
]
```
## Warning types
Each risk is assigned a severity based on your application's configuration. The three severities are:
## Related
* [ID verification report](/core-technology/id-verification/report-id-verification) — full report schema and statuses.
* [Document monitoring](/core-technology/id-verification/document-monitoring-id-verification) — how Didit detects tampering and cross-session duplicates.
* [Webhooks](/integration/webhooks) — `session.status.updated` carries the warnings as soon as the ID step finishes.
* [Data models — ID verification](/reference/data-models#id-verification) — canonical field-by-field schema.
* [Data models — Warning object](/reference/data-models#warning-object) — the shape of every entry in `warnings[]`.
# Device & IP Analysis
Source: https://docs.didit.me/core-technology/ip-analysis/overview
Detect duplicate devices, fraud rings, VPNs, proxies, Tor, location mismatches, and suspicious device fingerprints during KYC verification.
Didit's Device & IP Analysis combines device fingerprinting, duplicate-device recovery, IP intelligence, and geolocation checks in the verification flow. It helps detect when the same physical device is trying to access multiple accounts, even when the user changes sessions, clears browser storage, opens an incognito window, or rotates network information.
The feature is designed for fraud-prevention decisions where false positives are expensive. Exact duplicate-device matches use stable persistent identifiers. Recovered-device matches use the v2 fingerprint signal vector and high-confidence gates, so Didit can surface suspicious reuse without merging unrelated users too aggressively.
In the Academy lesson on reading a verification result, the 9:49 chapter walks the device and IP analysis signals.
## How it works
Device & IP Analysis runs automatically during the verification session and links the device, network, and identity context into one risk surface.
The verification client sends a privacy-safe v2 fingerprint payload with web or mobile device signals:
| Data Point | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| **Persistent device ID** | First-party identifier used for exact same-device detection when storage persists. |
| **Composite hash** | Stable grouped signal hash used for deterministic duplicate checks with collision safeguards. |
| **Signal vector** | Device and browser/app attributes vectorized for high-confidence recovery. |
| **Platform context** | Browser, OS, app, hardware, WebGL/canvas, media, locale, timezone, and mobile integrity signals where available. |
Didit enriches the observed connection data with network risk information:
* IP geolocation by country, region, city, and coordinates
* VPN, proxy, Tor, data center, and private-network detection
* Expected IP checks when you provide an allowed IP for the session
* IP blocklist checks configured in your application
Didit checks whether the current session matches previous sessions from another `vendor_data`:
| Check | Purpose |
| --------------------------------- | --------------------------------------------------------------------------------- |
| **Duplicated IP** | Detects the same IP address across different users. |
| **Duplicated device fingerprint** | Detects exact reuse of the same persistent device identity. |
| **Recovered device** | Detects a high-confidence v2 fingerprint recovery when the persistent ID changed. |
| **Collision guard** | Suppresses low-quality pooled hashes instead of merging unrelated devices. |
Device & IP Analysis compares location context against trusted reference points:
* Document country and address coordinates
* Expected session IP address
* IP country and city
* Distance and direction between address and IP location
You can configure each risk category independently and consume the result in every Didit output:
| Channel | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Workflow actions** | Approve, review, or decline for VPN/proxy, location mismatch, duplicate IP, exact duplicate device, and recovered device. |
| **Dashboard** | Review warnings, matching sessions, device information, and network details in the Didit console. |
| **Webhooks and APIs** | Receive structured warnings and matches in decision payloads. |
| **Reports** | Export Device & IP Analysis details in verification PDFs. |
## Matching Model
Device & IP Analysis separates exact matches from recovered matches so you can tune fraud response safely:
| Layer | What it detects | False-positive posture |
| ----------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| **Exact persistent ID** | The same first-party device identity appears in another user's session. | Strongest signal. Used for `DUPLICATED_DEVICE_FINGERPRINT`. |
| **Composite hash** | The same deterministic device hash appears in another user's session. | Guarded by collision detection so common WebView/browser pools are suppressed. |
| **v2 recovered device** | The persistent ID changed, but rich device signals match a previous device with high confidence. | Conservative. Used for `DEVICE_RECOVERED_HIGH_CONFIDENCE` only after hard gates pass. |
| **IP reuse** | The same IP address appears across users. | Contextual. Useful for fraud rings, but shared offices, households, and mobile carriers can be legitimate. |
Recovered-device matching uses a dedicated vector recovery index. If that index is not available, Didit continues exact duplicate checks and does not emit fuzzy recovery candidates.
In **Networks**, Didit also highlights residential proxy reuse when the same non-datacenter IP intelligence appears across multiple distinct users or businesses. The graph shows the aggregate risk signal, subject count, connection type, ISP and ASN context; it does not expose the raw IP address or another organization's profile data.
## Fraud Patterns Detected
Device & IP Analysis helps identify and reduce:
* Multi-accounting and duplicate-account creation
* KYC bypass attempts using the same device across different identities
* Fraud rings coordinating many accounts from shared devices or infrastructure
* Bonus abuse, referral abuse, promo abuse, and free-trial abuse
* Synthetic identity onboarding from repeated devices
* Money mule onboarding patterns
* Account takeover risk from unfamiliar or high-risk devices
* Credential stuffing and automated signup attempts
* Card testing, chargeback abuse, and refund abuse supported by repeated device/network patterns
* VPN, proxy, Tor, data center, and residential proxy evasion
* Device tampering, emulator usage, jailbreak/root risk, and app cloning where mobile signals are available
* Location spoofing and mismatches between document, IP, timezone, carrier, and device context
* Bot-driven verification attempts using headless browsers or scripted clients
## Key Capabilities
#### Device fingerprinting and recovery
* **Exact duplicate-device detection**: Detect the same device identity across sessions from different `vendor_data` values.
* **High-confidence recovery**: Recover likely same-device relationships when storage changes or incognito/private browsing changes the persistent ID.
* **Collision protection**: Avoid merging unrelated users when a device hash looks too common across many distinct persistent IDs.
* **Mobile and web coverage**: Use web browser signals and native mobile signals, including integrity-related fields when available.
#### IP and network intelligence
* **VPN and proxy detection**: Identify masked or anonymized connections.
* **Tor and data-center detection**: Flag high-risk infrastructure.
* **Residential proxy reuse**: Detect repeated use of the same residential-looking network across distinct subjects without exposing the raw IP value in the Networks graph.
* **IP blocklists**: Automatically decline when the IP appears in your application blocklist.
* **Expected IP enforcement**: Compare the observed IP to an expected IP supplied during session creation.
#### Geolocation and document comparison
* **Country mismatch detection**: Compare document country and IP country.
* **Geofencing**: Only accept sessions from allowed countries — with per-state/region overrides for countries like the United States — based on the IP geolocation.
* **Address distance checks**: Compare document address coordinates and IP geolocation.
* **Session match context**: Return matching sessions with device and location details for staff review.
## Configure Actions
Use workflow settings to choose the action for each risk. Conservative customers often set `recovered_device_action` to `REVIEW` first, inspect recovered-device warnings for a few weeks, and only move to `DECLINE` after confirming the local false-positive profile.
| Setting | Recommended starting action | Why |
| -------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `vpn_detection_action` | `REVIEW` or `DECLINE` | Depends on whether VPN usage is allowed in your product. |
| `ip_mismatch_action` | `REVIEW` | Location mismatch is strong context but can be legitimate. |
| `duplicated_ip_action` | `REVIEW` | Shared networks can create false positives. |
| `duplicated_device_action` | `REVIEW` or `DECLINE` | Exact same-device reuse across users is a strong fraud signal. |
| `recovered_device_action` | `REVIEW` | High-confidence recovery is intentionally conservative, but should be monitored before automatic decline. |
| `ip_geofencing_action` | `DECLINE` | Geofencing usually expresses a hard jurisdictional rule; use `REVIEW` if you prefer manual triage of restricted locations. |
See [Device & IP Analysis warnings](/core-technology/ip-analysis/warnings-ip-analysis) for all risk tags and [Workflow Feature Configs](/management-api/workflows/feature-configs#device--ip-analysis) for API fields.
# Device & IP analysis report
Source: https://docs.didit.me/core-technology/ip-analysis/report-ip-analysis
Parse the Device & IP analysis report: geolocation, device fingerprint, VPN/Tor/proxy flags, document-vs-IP distance, and duplicate device matches.
## Overview
Device & IP analysis profiles every session by IP geolocation, device fingerprint, browser/OS, and network type.
Geolocation, network-risk enrichment, device matching, and remote device integrity checks run asynchronously while the user completes verification.
Device and IP observations remain available while analysis is pending; geolocation fields can be empty until results arrive, or when the address is not public.
If your workflow uses these checks to determine the outcome or choose a subsequent step, the session remains `In Progress` until the required results are available.
If required analysis remains unavailable after bounded retries, the session moves to `In Review` for a manual decision.
Optional enrichment failures do not prevent completion, and manual decisions remain authoritative.
It produces:
* An **IP geolocation** block (country, state, city, latitude/longitude, time zone) derived from the public IP.
* **Network-risk flags** — `is_vpn_or_tor` and `is_data_center` — to detect masking attempts (VPN, Tor exit nodes, hosting providers, anonymisers).
* **Cross-document distance calculations** — straight-line km between the IP location, the ID document's location, and the proof-of-address document's location.
* **Cross-session matches** — when the same IP address or device identity appears across sessions belonging to different users (grouped by `vendor_data`).
* **Device fingerprint recovery** — a high-confidence recovery signal that links sessions even after the user clears storage, switches incognito modes, or reinstalls the app.
## Where it appears in API responses
The decision endpoint returns Device & IP analysis as the plural array **`ip_analyses[]`** in `GET /v3/session/{sessionId}/decision/`. Entries are deduplicated `Location` observations keyed on (`node_id`, `ip_address`, `device_fingerprint`) — a single node can yield multiple entries when the user's IP or device changes mid-session.
```text theme={null}
GET /v3/session/{sessionId}/decision/
──▶ { "ip_analyses": [ { status, node_id, ip_address, ip, id_document, poa_document, warnings, matches, … }, … ] }
```
The shape below mirrors the canonical schema — see [Data models](/reference/data-models#ip-analysis).
## Schema
See [Device & IP analysis in the Data Models reference](/reference/data-models#ip-analysis) for the canonical schema.
```typescript theme={null}
interface IPAnalysisV3 {
status: 'Not Finished' | 'Approved' | 'Declined' | 'In Review' | 'Resub Requested';
node_id: string | null;
// Device
device_brand: string | null;
device_model: string | null;
browser_family: string | null;
os_family: string | null;
platform: 'mobile' | 'tablet' | 'desktop' | null;
device_fingerprint: string | null;
// IP geolocation
ip_country: string | null;
ip_country_code: string | null; // ISO 3166-1 alpha-2
ip_state: string | null;
ip_city: string | null;
latitude: number | null;
longitude: number | null;
ip_address: string;
isp: string | null;
organization: string | null;
is_vpn_or_tor: boolean;
is_data_center: boolean;
time_zone: string | null;
time_zone_offset: string | null;
// Distance blocks — each carries its own location + distances to the other two
ip: {
location: { latitude: number; longitude: number } | null;
distance_from_id_document: number | null; // km
distance_from_poa_document: number | null; // km
};
id_document: {
location: { latitude: number; longitude: number } | null;
distance_from_ip: number | null;
distance_from_poa_document: number | null;
};
poa_document: {
location: { latitude: number; longitude: number } | null;
distance_from_ip: number | null;
distance_from_id_document: number | null;
};
warnings: Warning[]; // see Data Models — Warning object
// Cross-session matches — up to 5 IP matches + 5 device matches
matches: IPAnalysisMatch[];
}
interface IPAnalysisMatch {
session_id: string; // matching session UUID
session_number: number;
vendor_data: string | null;
verification_date: string | null; // "YYYY-MM-DDTHH:MM:SSZ"
match_type: 'ip_address' | 'device_fingerprint';
match_source: 'ip_address' | 'persistent_id' | 'legacy_fp' | 'recovered_high';
matched_value: string | null; // shared IP, device identifier, or recovered device UUID
status: string; // lifecycle status of the matching session
is_blocklisted: boolean; // currently always false
api_service: string | null; // standalone API service; null for workflow sessions
source: 'session';
device_info: {
device_brand: string | null;
device_model: string | null;
browser_family: string | null;
os_family: string | null;
platform: string | null;
device_fingerprint: string | null;
};
location_info: {
ip_address: string | null;
ip_country: string | null;
ip_country_code: string | null;
ip_state: string | null;
ip_city: string | null;
is_vpn_or_tor: boolean;
is_data_center: boolean;
};
confidence: number; // 0–1, = 1 - P(false positive)
match_mode: 'deterministic' | 'probabilistic' | 'co_occurrence';
// Present only when match_source = 'recovered_high'
recovery_similarity?: number; // cosine similarity, 4 decimals
tls_ja4_corroborated?: boolean; // TLS JA4 fingerprints of both observations agree
recovery_gate_reason?: string; // e.g. 'hardware_root_match'
}
```
### Match semantics
`match_source` tells you how the match was reached, and `confidence` scores how likely it is to be *wrong* (`confidence = 1 - P(false positive)`):
| `match_source` | Meaning | `confidence` | `match_mode` |
| ---------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------- |
| `ip_address` | Shared IP — network co-location, never a device-identity claim. | `0.0` | `co_occurrence` |
| `persistent_id` | Exact stored persistent device identifier. | `1.0` | `deterministic` |
| `legacy_fp` | Legacy `didit-fp-*` device-fingerprint hash. | `0.5` | `probabilistic` |
| `recovered_high` | High-confidence fuzzy fingerprint-recovery hit after strict hard gates. | `1.0` when hardware-rooted, otherwise `< 1.0`, boosted when TLS JA4 and IP country independently agree | `deterministic` or `probabilistic` |
Lower-confidence recovery candidates surface as risk warnings only — never in `matches[]`. IP matches and device matches are capped at 5 each, so the array holds at most 10 entries.
## Status values
Statuses are feature-level `FeatureStatusChoices` values. Each fired warning maps to the action configured for it on the workflow node; the strongest action wins.
| Value | Meaning |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The IP analysis node has not completed for this session (default). |
| `Approved` | No warning fired, or every fired warning is configured to No action. |
| `In Review` | At least one fired warning is configured to Review (see [warnings](/core-technology/ip-analysis/warnings-ip-analysis)). |
| `Declined` | A blocklist warning fired (`IP_ADDRESS_IN_BLOCKLIST` or `DEVICE_FINGERPRINT_IN_BLOCKLIST` — always decline), or a configurable warning is set to Decline for your workflow. |
| `Resub Requested` | A reviewer requested resubmission of this step. |
Custom status rules configured on the IP analysis node can further adjust the resulting status.
## VPN, proxy, and Tor detection
`is_vpn_or_tor` and `is_data_center` are independent boolean flags returned for every session. When `is_vpn_or_tor` is `true`, Didit also emits the `PRIVATE_NETWORK_DETECTED` warning so your team can act on it via workflow configuration. Datacenter-only traffic (without VPN/Tor) sets `is_data_center=true` but does not auto-warn — use this as an additional signal in your own scoring.
## Examples
### Approved
```json theme={null}
{
"ip_analyses": [
{
"status": "Approved",
"node_id": "ip-1",
"device_brand": "Apple",
"device_model": "iPhone",
"browser_family": "Mobile Safari",
"os_family": "iOS",
"platform": "mobile",
"device_fingerprint": "didit-fp-8d2c7a91f4b3e042",
"ip_country": "Spain",
"ip_country_code": "ES",
"ip_state": "Barcelona",
"ip_city": "Barcelona",
"latitude": 41.4022,
"longitude": 2.1407,
"ip_address": "83.50.226.71",
"isp": "Telefonica",
"organization": "Telefonica de Espana",
"is_vpn_or_tor": false,
"is_data_center": false,
"time_zone": "Europe/Madrid",
"time_zone_offset": "+0100",
"ip": {
"location": { "latitude": 41.4022, "longitude": 2.1407 },
"distance_from_id_document": 23.4,
"distance_from_poa_document": 12.3
},
"id_document": {
"location": { "latitude": 41.2706, "longitude": 1.9770 },
"distance_from_ip": 23.4,
"distance_from_poa_document": 18.7
},
"poa_document": {
"location": { "latitude": 41.3128, "longitude": 2.0540 },
"distance_from_ip": 12.3,
"distance_from_id_document": 18.7
},
"warnings": [],
"matches": []
}
]
}
```
### Declined — IP blocklist hit + VPN + duplicate device
```json theme={null}
{
"ip_analyses": [
{
"status": "Declined",
"node_id": "ip-1",
"device_brand": null,
"device_model": null,
"browser_family": "Chrome",
"os_family": "Linux",
"platform": "desktop",
"device_fingerprint": "didit-fp-a13c0d22e8b94471",
"ip_country": "Netherlands",
"ip_country_code": "NL",
"ip_state": "North Holland",
"ip_city": "Amsterdam",
"latitude": 52.3676,
"longitude": 4.9041,
"ip_address": "45.61.20.5",
"isp": "Example Hosting",
"organization": "Example Hosting BV",
"is_vpn_or_tor": true,
"is_data_center": true,
"time_zone": "Europe/Amsterdam",
"time_zone_offset": "+0100",
"ip": {
"location": { "latitude": 52.3676, "longitude": 4.9041 },
"distance_from_id_document": 1834.2,
"distance_from_poa_document": 1822.5
},
"id_document": {
"location": { "latitude": 40.4168, "longitude": -3.7038 },
"distance_from_ip": 1834.2,
"distance_from_poa_document": 14.0
},
"poa_document": {
"location": { "latitude": 40.5070, "longitude": -3.6720 },
"distance_from_ip": 1822.5,
"distance_from_id_document": 14.0
},
"warnings": [
{
"feature": "LOCATION",
"risk": "IP_ADDRESS_IN_BLOCKLIST",
"additional_data": { "ip_address": "45.61.20.5" },
"log_type": "error",
"short_description": "IP address in blocklist",
"long_description": "The IP address used for this session was found in the application's IP blocklist, indicating a known suspicious or forbidden origin.",
"node_id": "ip-1"
},
{
"feature": "LOCATION",
"risk": "PRIVATE_NETWORK_DETECTED",
"additional_data": null,
"log_type": "warning",
"short_description": "Private network (VPN/Tor) detected",
"long_description": "The system detected that the user tried to use a private network (VPN/TOR) to complete the verification process.",
"node_id": "ip-1"
},
{
"feature": "LOCATION",
"risk": "COUNTRY_FROM_DOCUMENT_DOES_NOT_MATCH_COUNTRY_FROM_IP",
"additional_data": { "document_country_code": "ESP", "ip_country_code": "NLD" },
"log_type": "warning",
"short_description": "Document country does not match IP country",
"long_description": "The country from the document does not match the country from the IP address, suggesting a potential mismatch between the document and the user's location.",
"node_id": "ip-1"
},
{
"feature": "LOCATION",
"risk": "DUPLICATED_DEVICE_FINGERPRINT",
"additional_data": {
"duplicated_session_id": "11111111-2222-3333-4444-555555555555",
"duplicated_session_number": 1042,
"api_service": null,
"match_source": "persistent_id"
},
"log_type": "warning",
"short_description": "Duplicated device fingerprint from another session",
"long_description": "The same device fingerprint was detected in another session with a different vendor_data, which may indicate multiple identities verified from the same device.",
"node_id": "ip-1"
}
],
"matches": [
{
"session_id": "11111111-2222-3333-4444-555555555555",
"session_number": 1042,
"vendor_data": "user-other",
"verification_date": "2026-05-28T14:03:21Z",
"match_type": "device_fingerprint",
"match_source": "persistent_id",
"matched_value": "pid_9f1c2b7a8d3e4f50",
"status": "Declined",
"is_blocklisted": false,
"api_service": null,
"source": "session",
"device_info": {
"device_brand": null,
"device_model": null,
"browser_family": "Chrome",
"os_family": "Linux",
"platform": "desktop",
"device_fingerprint": "didit-fp-a13c0d22e8b94471"
},
"location_info": {
"ip_address": "45.61.23.99",
"ip_country": "Netherlands",
"ip_country_code": "NL",
"ip_state": "North Holland",
"ip_city": "Amsterdam",
"is_vpn_or_tor": true,
"is_data_center": true
},
"confidence": 1.0,
"match_mode": "deterministic"
}
]
}
]
}
```
## Related
* [Device & IP analysis warnings](/core-technology/ip-analysis/warnings-ip-analysis) — every warning code IP analysis can emit
* [Device & IP analysis overview](/core-technology/ip-analysis/overview) — what each block measures
* [Data models — IP analysis](/reference/data-models#ip-analysis) — canonical schema
* [Webhooks](/integration/webhooks) — `status.updated` carries the same IP analysis payload
# Device & IP analysis warnings
Source: https://docs.didit.me/core-technology/ip-analysis/warnings-ip-analysis
Every Device & IP analysis warning code: VPN/Tor, country mismatch, blocklists, duplicate IP/device, recovered devices, and expected-IP mismatch.
## Overview
Device & IP analysis emits warning tags on `ip_analyses[].warnings[]` whenever it detects suspicious device, network, or location behavior. Six tags have a configurable action (Decline / Review / No action) per workflow node; the two blocklist tags always force `Declined`, and the two allowlist tags are always informational. Each warning's `log_type` mirrors the action that applied: `error` (Decline), `warning` (Review), or `information` (No action). Each risk fires at most once per session.
Every risk below is verified against the live decision pipeline; warnings carry the feature tag `LOCATION` and the standard [warning object](/reference/data-models#warning-object) shape.
The device fingerprint warnings are intentionally split into exact and recovered signals:
* `DUPLICATED_DEVICE_FINGERPRINT` means the same deterministic device identity (`match_source` `persistent_id` or `legacy_fp`) was reused across sessions with different `vendor_data` values.
* `DEVICE_RECOVERED_HIGH_CONFIDENCE` means v2 fingerprint recovery matched the session to a previously seen device **after** the persistent ID changed (storage cleared, incognito mode, app reinstall). The warning only appears when the match passes the high-confidence similarity threshold and hard gates.
## Warnings produced
| Risk | Cause | `log_type` | Affects status | Recommended remediation |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `PRIVATE_NETWORK_DETECTED` | `is_vpn_or_tor` was `true` — the session was opened via VPN, proxy, or Tor exit node. `additional_data` is `null`. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Decide whether your risk tolerance allows masked traffic. If you accept it, leave on No action. |
| `COUNTRY_FROM_DOCUMENT_DOES_NOT_MATCH_COUNTRY_FROM_IP` | The ISO country of the ID document differs from the country derived from the IP address. `additional_data` carries both ISO-3 codes: `document_country_code`, `ip_country_code`. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Review when the user travels legitimately. Decline when paired with VPN or duplicate-device signals. |
| `EXPECTED_IP_ADDRESS_MISMATCH` | The session was created with an expected IP address, and the live IP differs. `additional_data` carries `expected_ip_address` and `actual_ip_address`. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Use when you pre-pin the IP at session creation. Refuse the session or step up auth on mismatch. |
| `IP_ADDRESS_IN_BLOCKLIST` | The session's IP address matches an entry in the application's IP blocklist. `additional_data`: `ip_address`. | always `error` | Forces `Declined`. | Reject the session. Audit the blocklist source for false positives if needed. |
| `DEVICE_FINGERPRINT_IN_BLOCKLIST` | The session's device fingerprint matches an entry in the device blocklist. `additional_data`: `device_fingerprint`. | always `error` | Forces `Declined`. | Reject the session. |
| `IP_ADDRESS_IN_ALLOWLIST` | The session's IP had duplicate matches but is on the application's IP allowlist, so the duplicate-IP warning was skipped. `additional_data`: `ip_address`. | always `information` | None. | Use for trusted corporate NATs, QA networks, or known shared access points. |
| `DEVICE_FINGERPRINT_IN_ALLOWLIST` | The session's device fingerprint had deterministic duplicate matches but is on the device allowlist, so the duplicate-device warning was skipped. `additional_data`: `device_fingerprint`. | always `information` | None. | Use only for trusted shared devices. Blocklists still take priority. |
| `DUPLICATED_IP_ADDRESS` | The same IP was used in another session with a different `vendor_data`. `additional_data`: `duplicated_session_id`, `duplicated_session_number`, `api_service`. Skipped when the IP is allowlisted. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Common on corporate or shared NAT. Pair with other signals (duplicate device, country mismatch) before declining. |
| `DUPLICATED_DEVICE_FINGERPRINT` | The same deterministic device identity was reused across sessions with different `vendor_data`. `additional_data`: `duplicated_session_id`, `duplicated_session_number`, `api_service`, `match_source` (`persistent_id` or `legacy_fp`). Skipped when the fingerprint is allowlisted. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Strong evidence of multi-account abuse. Review or decline depending on fraud tolerance. |
| `DEVICE_RECOVERED_HIGH_CONFIDENCE` | The v2 device recovery model matched the session to a previously seen device with high confidence, even after the persistent ID changed. `additional_data`: `duplicated_session_id`, `duplicated_session_number`, `api_service`, `match_source` (`recovered_high`), `recovery_similarity`, `recovery_match_device_uuid`. Not suppressed by the device allowlist. | mirrors configured action | Configurable: Decline / Review / No action (default: No action). | Start with Review, measure your false-positive rate, then tighten the action. Strong signal for storage-reset or incognito attempts. |
| `IP_LOCATION_NOT_ALLOWED` | IP geofencing is enabled and the session IP is located in a country or state/region marked as not allowed. `additional_data`: `ip_country_code` (ISO-3), `ip_country`, `ip_state`, `restricted_by` (`country` or `state`). | mirrors configured action | Configurable: Decline / Review / No action (default: Decline). | Use to enforce jurisdictional restrictions. Combine with `vpn_detection_action` so masked traffic cannot bypass the fence. |
When the recovered device has no eligible sibling sessions yet (so nothing lands in `matches[]`), `DEVICE_RECOVERED_HIGH_CONFIDENCE` still fires with a fallback `additional_data` shape: `recovery_match_device_uuid`, `recovery_match_similarity`, `recovery_match_band`, `recovery_gate_reason`.
`IP_LOCATION_NOT_ALLOWED` fires when IP geofencing is enabled (`is_ip_geofencing_enabled`) and the session IP is located in a country or state/region marked as not allowed in `ip_geofencing_by_country`. `additional_data` carries `ip_country_code` (ISO-3), `ip_country`, `ip_state`, and `restricted_by` (`country` or `state`). State/region entries override the country flag and are matched case-insensitively against the IP provider's region name (e.g. `"California"`); countries not listed in the config are allowed.
## Device and runtime integrity
Signals reported by the native SDKs and the web client describing the state of the device the verification ran on. They exist to detect injection attacks: an attacker who can root a device or hook the app process can replace the camera feed before it ever reaches the biometric checks.
Every one is configurable per workflow and defaults to **no action**, so enabling the feature changes no decisions until you opt in.
| Risk | Cause | Config key | Default |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------- |
| `DEVICE_APP_TAMPERED` | Platform attestation failed to verify that the request came from a genuine, unmodified build of the application on a genuine device. | `device_app_tampered_action` | `NO_ACTION` |
| `DEVICE_RUNTIME_HOOKING_DETECTED` | A runtime instrumentation or hooking framework (Frida, Xposed, LSPosed, Substrate) was detected in the application process. | `device_hooking_action` | `NO_ACTION` |
| `DEVICE_ROOTED_OR_JAILBROKEN` | The device shows root or jailbreak indicators, which remove the operating-system protections that prevent camera interception. | `device_rooted_action` | `NO_ACTION` |
| `DEVICE_EMULATOR_DETECTED` | The session originated from an emulator or simulator rather than a physical device. | `device_emulator_action` | `NO_ACTION` |
| `DEVICE_DEBUGGER_ATTACHED` | A debugger or process tracer was attached to the application during the verification. | `device_debugger_action` | `NO_ACTION` |
| `AUTOMATION_FRAMEWORK_DETECTED` | The session was driven by a browser automation framework or headless browser rather than by a person. | `automation_detected_action` | `NO_ACTION` |
| `DEVICE_INTEGRITY_SIGNALS_MISSING` | The client reported an SDK version that supports integrity reporting, but no integrity signals arrived — which can indicate the signals were stripped in transit. | `device_integrity_missing_action` | `NO_ACTION` |
Each probe is best-effort. When a check cannot run it reports **not determined** rather than a clean result, so a failing probe never produces a false positive against a legitimate user. Signals are reported from native SDK **4.4.0** onwards; sessions from earlier SDKs legitimately send nothing and never raise `DEVICE_INTEGRITY_SIGNALS_MISSING`.
Root and emulator signals are common among legitimate users on modified but non-fraudulent devices. Start these on Review rather than Decline and watch your own decline rate before tightening.
## Configurable settings
Per-node workflow controls (also configurable globally on the application's verification settings; every action defaults to **No action** unless noted):
| Setting | Drives | Default |
| ----------------------------- | ------------------------------------------------------ | --------- |
| `vpn_detection_action` | `PRIVATE_NETWORK_DETECTED` | No action |
| `ip_mismatch_action` | `COUNTRY_FROM_DOCUMENT_DOES_NOT_MATCH_COUNTRY_FROM_IP` | No action |
| `expected_ip_mismatch_action` | `EXPECTED_IP_ADDRESS_MISMATCH` | No action |
| `duplicated_ip_action` | `DUPLICATED_IP_ADDRESS` | No action |
| `duplicated_device_action` | `DUPLICATED_DEVICE_FINGERPRINT` | No action |
| `recovered_device_action` | `DEVICE_RECOVERED_HIGH_CONFIDENCE` | No action |
| `ip_geofencing_action` | `IP_LOCATION_NOT_ALLOWED` | Decline |
IP and device allowlists suppress only the exact duplicate-IP or deterministic duplicate-device warning for the allowlisted value. They do not suppress VPN/proxy, country mismatch, expected-IP mismatch, recovered-device, or blocklist warnings.
Recovered-device warnings are useful for detecting fraud rings that rotate accounts and sessions from the same hardware, but Didit is conservative by design — the system prefers missing some duplicate users over merging unrelated devices when the evidence is not strong enough. **Recommended rollout**: start with Review, watch the volume for two weeks, then tighten the action if your false-positive rate is low.
## Duplicate device vs. recovered device
Treat the two warnings differently — they trigger on different evidence:
| Warning | Trigger | Typical meaning | Recommended first action |
| ---------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `DUPLICATED_DEVICE_FINGERPRINT` | Exact persistent device identity (`persistent_id`) or legacy fingerprint hash (`legacy_fp`) match. | Strong evidence that the same device appears across different users. | Review or Decline, depending on your fraud tolerance. |
| `DEVICE_RECOVERED_HIGH_CONFIDENCE` | High-confidence v2 recovery (`recovered_high`) after storage, session, or app identity changed. | Strong signal for incognito / storage-reset / app-reinstall attempts. Intentionally separated so you can monitor it independently. | Review first, then tighten after measuring your false-positive rate. |
## Cross-session matches
When the same IP address, exact device identity, or recovered device is detected across sessions belonging to **different users**, Didit records these as `matches[]` on `ip_analyses[]`. Sessions are grouped by `vendor_data`: sessions with the same `vendor_data` are treated as the same user and excluded from matches. Without `vendor_data`, every session is treated as a unique user and all potential duplicates are surfaced — we strongly recommend always providing `vendor_data` to reduce noise.
Each match includes:
* `session_id`, `session_number`, `vendor_data`, and `verification_date` of the matching session, plus its lifecycle `status` and `api_service` (`null` for workflow sessions); `source` is always `session`
* `match_type` — `ip_address` or `device_fingerprint`
* `match_source` — `ip_address`, `persistent_id`, `legacy_fp`, or `recovered_high`
* `matched_value` — the shared IP, the device identifier, or (for recovered matches) the recovered device UUID
* `confidence` (0–1, `1 - P(false positive)`) and `match_mode` (`deterministic` / `probabilistic` / `co_occurrence`)
* `device_info` — `device_brand`, `device_model`, `browser_family`, `os_family`, `platform`, `device_fingerprint`
* `location_info` — `ip_address`, `ip_country`, `ip_country_code`, `ip_state`, `ip_city`, `is_vpn_or_tor`, `is_data_center`
* Recovered-device extras when `match_source` is `recovered_high`: `recovery_similarity`, `tls_ja4_corroborated`, `recovery_gate_reason`
IP matches and device matches are capped at 5 each (at most 10 entries). See the [report page](/core-technology/ip-analysis/report-ip-analysis#match-semantics) for the full match schema and confidence model.
## Examples
### VPN + country mismatch (configured to Review)
```json theme={null}
{
"warnings": [
{
"feature": "LOCATION",
"risk": "PRIVATE_NETWORK_DETECTED",
"additional_data": null,
"log_type": "warning",
"short_description": "Private network (VPN/Tor) detected",
"long_description": "The system detected that the user tried to use a private network (VPN/TOR) to complete the verification process.",
"node_id": "ip-1"
},
{
"feature": "LOCATION",
"risk": "COUNTRY_FROM_DOCUMENT_DOES_NOT_MATCH_COUNTRY_FROM_IP",
"additional_data": { "document_country_code": "ESP", "ip_country_code": "NLD" },
"log_type": "warning",
"short_description": "Document country does not match IP country",
"long_description": "The country from the document does not match the country from the IP address, suggesting a potential mismatch between the document and the user's location.",
"node_id": "ip-1"
}
]
}
```
### Blocklist hit (forces decline)
```json theme={null}
{
"warnings": [
{
"feature": "LOCATION",
"risk": "IP_ADDRESS_IN_BLOCKLIST",
"additional_data": { "ip_address": "45.61.20.5" },
"log_type": "error",
"short_description": "IP address in blocklist",
"long_description": "The IP address used for this session was found in the application's IP blocklist, indicating a known suspicious or forbidden origin.",
"node_id": "ip-1"
}
]
}
```
### Recovered-device high confidence (configured to Review)
```json theme={null}
{
"warnings": [
{
"feature": "LOCATION",
"risk": "DEVICE_RECOVERED_HIGH_CONFIDENCE",
"additional_data": {
"duplicated_session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"duplicated_session_number": 5101,
"api_service": null,
"match_source": "recovered_high",
"recovery_similarity": 0.9874,
"recovery_match_device_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"log_type": "warning",
"short_description": "Device matches a previously seen device with high confidence",
"long_description": "A previously seen device matched this session with high confidence through v2 fuzzy fingerprint recovery after passing strict hard gates, indicating the same physical device under a fresh persistent_id.",
"node_id": "ip-1"
}
]
}
```
## Related
* [Device & IP analysis report](/core-technology/ip-analysis/report-ip-analysis) — full response shape and status semantics
* [Device & IP analysis overview](/core-technology/ip-analysis/overview) — what each block measures
* [Data models — IP analysis](/reference/data-models#ip-analysis) — canonical schema
* [Webhooks](/integration/webhooks) — `status.updated` carries IP analysis warnings
### Warning types
# Cross-organization identity claims
Source: https://docs.didit.me/core-technology/liveness/cross-organization-identity-claims
How Didit reports identity evidence from other organizations without ever labelling a person as fraudulent — separate result dimensions, claim-scoped evidence, and the default-off sharing switch.
## The rule this page exists for
A face is a stable identity. It is not a fraud label, and Didit will not treat it as one.
That distinction matters most in the case that motivated it: a real, consenting person attempts
to verify under an identity that is not theirs. The attempt must be stopped. But three separate
people must be left alone afterwards — that same person when they later verify with their own
valid identity, the person whose identity was borrowed (who is the victim, not the offender),
and anyone whose face merely looks similar.
So fraud meaning is attached to an **identity claim**, which is an event, never to the person
who made it or to the identity they claimed.
## Four dimensions, four fields
A decision is never collapsed into one word chosen on your behalf. Each of these is reported
independently, and you decide what your product does with the combination:
| Dimension | What it answers | Where it appears |
| ---------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Input integrity** | Was the capture live, unmodified and from a real camera? | Liveness warnings (`LIVENESS_FACE_ATTACK`, `VIRTUAL_CAMERA_DETECTED`, …) |
| **Identity match** | Did the person match the identity they claimed, against a trusted reference? | Face match and duplicate-face warnings on the session |
| **Behaviour and network evidence** | Have other organizations seen claim events for this same person that did not match? | `CROSS_ORG_IDENTITY_CLAIM_PATTERN` |
| **Final decision** | What the session's status became | `status` on the decision payload |
A failed liveness check, an identity mismatch and a network pattern are three different
statements about three different things. Reading them as one number is how a bad photo turns
into an accusation.
### Identity match needs a trusted reference
`match` and `mismatch` are only produced when there is something trustworthy to compare a face
against — a government document portrait, or an identity already strongly bound to that face.
An email address or a phone number with no prior strong binding gives you nothing to compare a
face to. In that case the answer is **no reference**, never `mismatch`, and never a `match`
inferred from face history alone.
## What cross-organization evidence can and cannot say
When cross-organization sharing is enabled for your organization, a session can raise
`CROSS_ORG_IDENTITY_CLAIM_PATTERN`. It means:
> Other participating organizations recorded identity-claim events for this same person in
> which the claimed identity did not match the evidence on those sessions.
It does **not** mean the person is fraudulent, and no Didit response will ever say that. Three
guarantees back this up:
* **It is suppressed when your own check passed.** If this session's own identity match is
`match`, the warning is not raised at all. A person who verifies successfully against their
own trusted reference is verified — whatever they may have been involved in before belongs
to those claims, not to this one.
* **It cannot decline.** The action is Review or No action. Setting Decline on
`cross_org_identity_claim_pattern_action` is downgraded to Review, because history about a
person may open an investigation into the current claim but may never reject it.
* **It is aggregate only.** The payload carries how many organizations, how many mismatched
claims, how many distinct identities were claimed and how recent — never who they are, never
their sessions, never any of their data.
`CROSS_ORG_FRAUD_FACE` is the retired version of this warning and is no longer raised. It is
kept as a value so existing logs and rules keep resolving; historical occurrences are records,
not judgments about a person.
## Face-only lookup: a person id, not a verdict
Recognising the same human being again is a separate capability from deciding anything about
them. A face-only lookup returns a **stable opaque person id** for high-confidence repeat
captures of the same face, and nothing else.
* The id is a random identifier. It is not derived from a face, an embedding, a session, an
organization or a customer reference, so it discloses nothing beyond "this capture was seen
before".
* It carries no fraud state. There is no field on it that could.
* A low-confidence or possible match never merges two people. Over-merging fuses two human
beings into one identity and cannot be undone from the data; under-merging only costs recall.
## Multi-account detection is same-organization and segmented
"Has this face opened several accounts with **us**" is a different product question from
cross-organization evidence, and it is answered inside your own organization only.
Set `multi_account_segment` on session create to give that search an explicit realm. Two
sessions in different segments are never duplicates of each other, which is what lets you run
separate personal and business account bases — or one segment per sub-client — without a person
who legitimately holds one account in each looking like account farming. Omit it and every
session sits in the single default realm, which is exactly today's behaviour.
Blocklist and allowlist matching deliberately ignore segments: a blocklisted face is
blocklisted everywhere in your organization, so a segment can never be used to escape one.
## Privacy
* Faces never cross the organization boundary. The shared fraud index physically cannot hold
one — the database rejects the write.
* What does cross the boundary is a one-way keyed hash of an exact value (a document number, a
device, an IP, a phone, an email) that a participating organization labelled as fraud.
* Cross-organization reads answer with counts and recency buckets. The contributing
organization is never named, and no raw value, image or session ever leaves.
* Sharing is reciprocal and opt-in: an organization that does not contribute does not query.
* Cross-organization **face** intelligence specifically is off by default and stays off until
the data-protection impact assessment and legal review for the biometric-person identifier
and the claim-event model are complete.
# Liveness Detection
Source: https://docs.didit.me/core-technology/liveness/overview
Detect deepfakes, masks, and presentation attacks with passive and active liveness. 99.9% accuracy, iBeta-tested, pay-per-call from $0.10.
Didit's Liveness Detection solution provides enterprise-grade biometric verification through advanced computer vision and machine learning algorithms. Our system achieves **99.9% accuracy** with a false acceptance rate (FAR) of less than **0.1%**, ensuring robust protection against spoofing attacks.
The Academy lesson on the hosted user flow reaches the liveness selfie at 8:49, followed at 9:16 by the passive, 3D flash, and 3D action methods.
## Liveness Detection Methods
Our platform implements three distinct anti-spoofing technologies, each tailored to different security needs and user experiences:
Method
Description
Security Level
Best For
**`3D Action & Flash`**
• Combines multi-factor biometric verification with a **randomized action sequence** and **dynamic light pattern analysis**.
• At the start, the user is prompted to perform a simple action—like **blinking** or **nodding**—ensuring real-time interaction.
• Simultaneously, the system projects a sequence of light patterns onto the face, analyzing the **reflections** to confirm the face's three-dimensional structure.
• **Deep learning algorithms** examine micro-expressions and the light reflection responses to verify the presence of a live person.
• Offers the **highest security** by integrating behavioral (action) and physical (light-based depth) cues, making it nearly impossible to spoof with static images, videos, or even advanced masks.
Highest
Banking, healthcare, government applications
**`3D Flash`**
• Uses **dynamic light pattern analysis** to validate facial topology without requiring user interaction.
• Projects a series of light patterns onto the face at over **30 frames per second**, analyzing the reflections to create a **depth map**.
• This depth map confirms the face's three-dimensional structure, distinguishing it from flat images or 2D spoofs.
• Provides a **seamless experience** while maintaining **high security** against presentation attacks like photos or screens.
• Relies on **single-frame deep learning analysis** to detect signs of liveness.
• Examines the image for **artifacts**, **texture patterns**, and other subtle indicators that differentiate a real face from a spoof.
• A **convolutional neural network (CNN)** validates facial features and identifies anomalies, such as those from printed photos or digital screens.
• Offers **fast and convenient** verification but provides **standard security**, suitable for low-risk use cases.
Standard
Low-friction scenarios, consumer applications
**Advanced Security of 3D Flash and 3D Action & Flash:**
> * These methods are engineered to defeat **sophisticated spoofing attacks**, such as **high-quality masks**, **deepfakes**, and **video replays**.
> * By projecting dynamic light patterns and analyzing their reflections, they detect how light interacts with a **real 3D face** versus a flat or artificial surface.
> * The **3D Action & Flash** method adds an extra layer of security with a randomized action (e.g., blink or nod), requiring **real-time behavioral responses** that pre-recorded media or synthetic identities cannot replicate.
> * Proven to deliver **high accuracy** and **low false acceptance rates**, these methods are ideal for high-stakes environments.
Each method generates a **normalized liveness score (0-100%)** based on our proprietary algorithm, which evaluates multiple security factors in real time.
### Configurable Thresholds
You can customize security levels by setting different thresholds for liveness scores. For example:
These thresholds can be adjusted based on your **risk tolerance** and **security requirements**, offering flexibility across use cases.
## Retries & actionable feedback
In a Didit session, a liveness capture that fails for a **fixable reason** (poor lighting, face not centered, occlusion, a borderline score) does not immediately decline. The user is asked to **retry with specific guidance**, up to a configurable number of attempts.
* **Attempt budget** — `face_liveness_max_attempts` controls the total number of liveness submissions per session. Default **3** (one initial attempt plus two retries), configurable per workflow node (range 1–3).
* **Retry feedback** — while attempts remain, a retryable failure keeps the user on the liveness step and returns a body carrying actionable `feedback_codes` (the Web and Mobile SDKs surface these as coaching messages automatically):
```json theme={null}
{
"detail": "Move to a brighter, evenly lit place and try again.",
"error": "LOW_FACE_LUMINANCE",
"feedback_codes": ["LOW_FACE_LUMINANCE"],
"attempts_used": 1,
"max_attempts": 3
}
```
* **Retryable codes** — `LOW_FACE_LUMINANCE` (too dark), `HIGH_FACE_LUMINANCE` (too bright), `NO_FACE_DETECTED`, `LOW_FACE_QUALITY`, `HIGH_FACE_OCCLUSION`, `LOW_LIVENESS_SCORE`, `MULTIPLE_FACES_DETECTED`, `AGE_NOT_DETECTED`.
* **Guaranteed retry for capture conditions** — `LOW_FACE_LUMINANCE`, `HIGH_FACE_LUMINANCE`, `LOW_FACE_QUALITY`, `HIGH_FACE_OCCLUSION` and `NO_FACE_DETECTED` describe the room the user is standing in, not the person. When an attempt fails on those codes alone, the user always gets a second attempt with the coaching message above, even on a workflow configured with `face_liveness_max_attempts: 1`. Every other code still follows the configured budget exactly.
* **Exhaustion & fraud** — once the budget is exhausted the computed status is applied (`Declined` or `In Review`). Confirmed presentation attacks (`LIVENESS_FACE_ATTACK`), blocklist hits and duplicate faces **decline immediately** and are never retried.
Retries apply to the **session flow** only. The standalone `POST /v3/passive-liveness/` endpoint is stateless and single-shot — it returns the result directly with no retry budget.
## How It Works
The user is guided through a **simple, intuitive interface** tailored to the selected liveness method.
| Check | Description |
| ---------------------- | -------------------------------------------------------------------- |
| **Real-time feedback** | Ensures proper lighting, positioning, and framing |
| **Quality checks** | Monitors for blur, glare, and optimal facial visibility |
| **Adaptive capture** | Adjusts to various device capabilities and network conditions |
| **Method guidance** | Shows method-specific instructions (e.g., "Blink now" for 3D Action) |
Advanced algorithms process the captured media in **real time** to detect spoofing attempts. The analysis differs per method:
* Verifies the user's action (e.g., **blink** or **nod**) to confirm it's performed correctly and live
* Analyzes **light pattern reflections** to validate the face's three-dimensional structure
* Uses deep learning to assess **micro-expressions** and other behavioral cues
* Combines behavioral + physical signals for the **strongest spoof resistance**
* Processes a sequence of **light pattern reflections** at 30+ FPS to build a detailed **depth map**
* Confirms the face's 3D properties, distinguishing it from flat or 2D spoofs
* No user interaction required — fully **passive from the user's perspective**
* Applies **deep learning** to a single frame to detect **texture patterns** and **artifacts**
* A convolutional neural network (CNN) validates facial features and identifies anomalies
* Fastest method — works with a **single photo capture**
Multi-layered detection identifies **presentation attacks** including photos, screens, masks, and deepfakes.
The system compares the liveness score against your **configured thresholds** and delivers results instantly.
| Output | Description |
| ------------------- | ---------------------------------------------------------------------- |
| **Liveness score** | Normalized 0–100% confidence score |
| **Decision** | Approved, Declined, or In Review based on your thresholds |
| **Method** | Which liveness method was used (`passive`, `flash`, `action_flash`) |
| **Reference image** | Captured frame used for the liveness check |
| **Warnings** | Any anomalies detected (e.g., low quality, potential spoof indicators) |
Results are delivered via **API response**, **webhook**, or viewable in the **Business Console** with detailed audit logs for compliance.
You can configure which liveness method to use per workflow in the [Business Console](/console/workflows). Different workflows can use different methods depending on your risk requirements.
# Liveness report
Source: https://docs.didit.me/core-technology/liveness/report-liveness
Read Didit's Liveness report: status, confidence score, detection method, age estimation, cross-session face matches, and where it appears in responses.
## Overview
The **liveness report** captures everything Didit observed while verifying that a real, live person was in front of the camera — not a printed photo, screen replay, or deepfake. It includes the liveness method, a 0–100 confidence score, the reference selfie and capture video, an optional age estimate, passive-liveness quality metrics, and cross-session face matches against your application's history and blocklist.
The report is produced after the user completes the liveness step in a workflow (or hits the standalone face endpoint). Didit runs one of three liveness methods (active 3D, flashing, or passive), scores the capture, screens for known spoof patterns, and compares the face biometrically against every other face you've seen in your application.
Each report carries its own `status` — independent of the overall session status — that reflects how the liveness step alone resolved:
* **Approved** — face detected, liveness score above the review threshold, no auto-decline warning fired.
* **In Review** — one or more warnings routed to review fired, or the score is at or below the review threshold but above the decline threshold.
* **Declined** — an auto-decline condition fired (`NO_FACE_DETECTED`, `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, or the age risks on age-estimation flows), a configurable warning was routed to decline, or the score is at or below the decline threshold.
* **Not Finished** — the user never completed the liveness step.
## Where it appears in API responses
The liveness report appears as `liveness_checks[]` in `GET /v3/session/{sessionId}/decision/` — **always a JSON array**, never a singular `liveness` object. Multiple entries appear when a workflow runs liveness more than once (for example a primary check plus a re-capture).
* **Session decision API** — `GET /v3/session/{sessionId}/decision/` returns `liveness_checks[]` at the top level. See [Retrieve session decision](/sessions-api/retrieve-session).
* **Webhooks** — `session.status.updated` payloads include the same `liveness_checks[]` array once the step has produced data. See [Webhooks](/integration/webhooks).
* **Standalone liveness API** — see [Liveness standalone API](/standalone-apis/passive-liveness).
Read `response.liveness_checks[0]` and iterate the array — the singular `liveness` shape some older tutorials referenced does not exist on the v3 decision endpoint.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#liveness-check) reference page. The fields below mirror that canonical schema.
| Field | Type | Description |
| ----------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status` | `"Approved" \| "Declined" \| "In Review" \| "Not Finished" \| "Resub Requested"` | Liveness-step status (see below). |
| `node_id` | string \| null | Workflow graph node that produced this report. |
| `method` | `"ACTIVE_3D" \| "FLASHING" \| "PASSIVE"` | The liveness method that produced this capture. |
| `score` | number 0–100 \| null | Liveness confidence score, rounded to 2 decimals. |
| `reference_image` | string (signed URL) \| null | Reference selfie extracted from the capture. Signed URL with a limited validity window. |
| `video_url` | string (signed URL) \| null | Full liveness capture video (active methods). Signed URL with a limited validity window. |
| `age_estimation` | number \| null | Estimated age in years of the largest face in the reference image. |
| `face_quality` | number 0–100 \| null | Passive-liveness face quality, normalized to a percentage. Populated only when `method = "PASSIVE"`. |
| `face_luminance` | number 0–100 \| null | Passive-liveness facial luminance, normalized from 0–255 to a percentage. Populated only when `method = "PASSIVE"`. |
| `matches[]` | array | Cross-session face matches (see below). |
| `warnings[]` | array | Module-level warnings — see [Liveness warnings](/core-technology/liveness/warnings-liveness). Each entry follows the [Warning object](/reference/data-models#warning-object) shape (`feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`). |
### Cross-session face matches
`matches[]` lists the faces in your application's history that exceeded the biometric similarity threshold against this selfie — up to 5 entries, blocklisted and allowlisted faces first, then by descending similarity. Eligible candidates are blocklisted faces, allowlisted faces, faces from `Approved` sessions, and faces imported against your vendor users. Faces belonging to the same user (same `vendor_data`, or the same vendor-user when available) are excluded from the **entire** search — both duplicate detection and blocklist screening.
Each entry includes:
| Field | Type | Description |
| ----------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_id` | string (UUID) \| null | The matched session. `null` when `source` is `imported` or `list_entry`. |
| `session_number` | number \| null | Sequential session number for the matched session. `null` for non-session sources. |
| `similarity_percentage` | number 0–100 | Biometric similarity score, clamped to 0–100. |
| `vendor_data` | string \| null | Your reference data from the matched session (or vendor user, for imported faces). |
| `verification_date` | string (ISO 8601) \| null | When the matched session (or imported face) was created. |
| `user_details` | object \| null | `{ full_name, document_type, document_number }` from the matched session's KYC. Imported faces populate `full_name` only (the two document fields are `null`); `null` when no details are on file. |
| `match_image_url` | string (signed URL) \| null | Reference face from the matched session, imported face, or list entry. |
| `status` | string \| null | Status of the matched session (e.g. `"Approved"`). `null` for non-session sources. |
| `is_blocklisted` | boolean | `true` when the matched face is on your face blocklist. |
| `is_allowlisted` | boolean | `true` when the matched face is on your face allowlist (duplicate-face actions are skipped). |
| `api_service` | string \| null | Set when the matched face came from a standalone API call (e.g. `"PASSIVE_LIVENESS"`); `null` for workflow sessions and non-session sources. |
| `source` | `"session" \| "imported" \| "list_entry"` | Where the matched face came from: another verification session, a face uploaded against a vendor user, or an org-managed allow/block list face. |
## Status values
Statuses come from the shared feature-status enum (`FeatureStatusChoices`).
| Status | Meaning | Downstream effect |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `Approved` | Face detected, liveness score above the review threshold, no auto-decline warning fired. | Counts as a successful liveness check. |
| `In Review` | Score at or below the review threshold but above the decline threshold, or a warning routed to review fired. | Session also moves to `In Review` until a reviewer acts. |
| `Declined` | An auto-decline risk fired (`NO_FACE_DETECTED`, `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, or the age risks on age-estimation flows), a configurable warning was routed to decline, or the score is at or below the decline threshold. | Session is declined unless another approved branch satisfies the workflow. |
| `Not Finished` | User abandoned the liveness step (or a retryable capture failure is awaiting another attempt). | The liveness branch did not produce a result. |
| `Resub Requested` | A reviewer requested resubmission of this step from the console. | Transient — replaced once the user re-captures. |
## Examples
### Approved — clean active-3D capture
```json theme={null}
{
"liveness_checks": [
{
"status": "Approved",
"node_id": "liveness_primary",
"method": "ACTIVE_3D",
"score": 92.4,
"reference_image": "https:///.../reference.jpg?signature=...",
"video_url": "https:///.../liveness.mp4?signature=...",
"age_estimation": 24.3,
"face_quality": null,
"face_luminance": null,
"matches": [],
"warnings": []
}
]
}
```
### In Review — passive liveness, low-quality capture and a possible duplicate
```json theme={null}
{
"liveness_checks": [
{
"status": "In Review",
"node_id": "liveness_primary",
"method": "PASSIVE",
"score": 76.1,
"reference_image": "https:///.../reference.jpg?signature=...",
"video_url": null,
"age_estimation": 31.0,
"face_quality": 12.4,
"face_luminance": 18.7,
"matches": [
{
"session_id": "8d2f1c4e-6b7a-4c8d-9e0f-1a2b3c4d5e6f",
"session_number": 123,
"similarity_percentage": 96.8,
"vendor_data": "user-555",
"verification_date": "2024-07-15T10:23:45Z",
"user_details": {
"full_name": "John Smith",
"document_type": "Passport",
"document_number": "SAMPLE-DOC-12345"
},
"match_image_url": "https:///.../matched.jpg?signature=...",
"status": "Approved",
"is_blocklisted": false,
"is_allowlisted": false,
"api_service": null,
"source": "session"
}
],
"warnings": [
{
"feature": "LIVENESS",
"risk": "LOW_FACE_QUALITY",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face quality",
"long_description": "The facial image quality is below the acceptable threshold, which may affect the reliability of liveness detection. This could be due to camera resolution, focus, or compression artifacts.",
"node_id": "liveness_primary"
},
{
"feature": "LIVENESS",
"risk": "LOW_FACE_LUMINANCE",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face luminance",
"long_description": "The facial image is too dark, which may affect the accuracy of liveness detection. Better lighting conditions are recommended.",
"node_id": "liveness_primary"
},
{
"feature": "LIVENESS",
"risk": "POSSIBLE_DUPLICATED_FACE",
"additional_data": {
"duplicated_session_id": "8d2f1c4e-6b7a-4c8d-9e0f-1a2b3c4d5e6f",
"duplicated_session_number": 123,
"api_service": null
},
"log_type": "warning",
"short_description": "Possible duplicated face from other approved session",
"long_description": "The system identified a possible duplicate face from another approved session, requiring further investigation.",
"node_id": "liveness_primary"
}
]
}
]
}
```
Here the face quality (12.4) sits below the default review threshold (15), the luminance (18.7) sits below the default minimum (20), and the workflow's duplicate-face action is set to Review — so all three warnings carry `log_type: "warning"` and the report resolves to `In Review`.
## Security note
Liveness videos and selfies are biometric data — the signed URLs are temporary and expire (after 4 hours by default). Treat them as short-lived: do not cache or surface them publicly. Typically your application only needs `status`, `score`, and the warnings — store as little as possible to minimize biometric data on your servers.
## Related
* [Liveness warnings](/core-technology/liveness/warnings-liveness) — full warning enum, causes, and remediation.
* [Webhooks](/integration/webhooks) — listen for `session.status.updated` to receive the report.
* [Data models — Liveness check](/reference/data-models#liveness-check) — canonical schema with every field.
* [Face match report](/core-technology/face-match/report-face-match) — the companion 1:1 face match against the document portrait.
# Liveness warnings
Source: https://docs.didit.me/core-technology/liveness/warnings-liveness
Every warning Didit's liveness module emits — face attacks, low-score and quality triggers, blocklist matches, duplicate faces — with cause and remediation.
## Overview
Warnings on the liveness report flag every condition Didit observed while running the liveness check. They land in the `warnings[]` array on each item of `liveness_checks[]` (see [Liveness report](/core-technology/liveness/report-liveness)), with `feature` set to `"LIVENESS"`. Each entry follows the shared [warning object](/reference/data-models#warning-object) shape: `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`.
Every warning has three layers:
1. **The `risk` code** — a stable identifier you can match on in your code (the codes listed below).
2. **The `log_type`** — one of `information`, `warning`, or `error`. Auto-decline risks always carry `error`. For configurable risks it is derived from the configured action: **No action** → `information`, **Review** → `warning`, **Decline** → `error`. For threshold-driven risks (`LOW_LIVENESS_SCORE`, `LOW_FACE_QUALITY`) it is `error` when the value crosses the decline threshold and `warning` when it only crosses the review threshold.
3. **The decision impact** — the same routing drives the liveness report's `status`: a Review action moves it to `In Review`, a Decline action (or any auto-decline risk) to `Declined`, and No action leaves it untouched.
Some risks are emitted only when the liveness method is `PASSIVE` (multiple-faces, quality, and luminance checks). The tables below list the full set of liveness risk codes and their severities — no other liveness risk codes exist.
## Auto-decline conditions
The following risks always force the liveness report (and the session) to `Declined`, with `log_type: "error"`:
| Risk | Trigger |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `NO_FACE_DETECTED` | No face was located during the liveness capture, or the user exhausted retry attempts. |
| `LIVENESS_FACE_ATTACK` | The model detected a presentation attack (mask, screen replay, deepfake-class spoof). |
| `FACE_IN_BLOCKLIST` | The face matched an entry on your application's face blocklist. |
| `AGE_BELOW_MINIMUM` | Estimated age below the configured minimum. Only emitted on adaptive age-verification workflows and `AGE_ESTIMATION` nodes. |
| `AGE_NOT_DETECTED` | Age could not be estimated. Only emitted on age-estimation flows with ID-verification fallback disabled. |
When `NO_FACE_DETECTED` fires, `LOW_LIVENESS_SCORE` is suppressed (there is no face to score).
## Configurable verification settings
In the Didit console, liveness risks are grouped under the following workflow settings:
| Group | Risks | Configuration |
| ----------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Liveness score thresholds** | `LOW_LIVENESS_SCORE` | Review threshold + decline threshold on `score`. At or below review → `In Review`; at or below decline → `Declined`. |
| **Duplicate face action** | `DUPLICATED_FACE`, `POSSIBLE_DUPLICATED_FACE` | One shared setting: No action (default) / Review / Decline. Skipped entirely when the matching face is in your face allowlist. |
| **Duplicate face identity mismatch** | `DUPLICATED_FACE_NAME_MISMATCH` | Own setting (`face_liveness_duplicated_face_name_mismatch_action`): No action / Review (default) / Decline. Only evaluated when `DUPLICATED_FACE` also fires — see below. |
| **Possible blocklisted face** | `POSSIBLE_FACE_IN_BLOCKLIST` | Not configurable in workflows — always routed to Review (`log_type: "warning"`). |
| **Multiple faces action** *(passive only)* | `MULTIPLE_FACES_DETECTED` | No action (default) / Review / Decline. |
| **Face quality thresholds** *(passive only)* | `LOW_FACE_QUALITY` | Review threshold (default 15) + decline threshold (default 0, i.e. decline disabled). |
| **Face luminance** *(passive only)* | `LOW_FACE_LUMINANCE`, `HIGH_FACE_LUMINANCE` | Min / max thresholds (defaults 20 / 80 on the normalized 0–100 scale) + a per-direction action (default Review). |
| **Age thresholds** *(adaptive workflows / `AGE_ESTIMATION` node)* | `AGE_BELOW_MINIMUM`, `AGE_NOT_DETECTED` | Minimum and borderline age thresholds; both risks auto-decline the liveness step. With ID-verification fallback enabled, the workflow routes the user to a document check instead of ending the session. |
## Warnings produced
### Core liveness
| Risk | Cause | Severity | Affects status? | Remediation |
| ---------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------- |
| `NO_FACE_DETECTED` | No face located during the capture, or the user exhausted retry attempts (`MAX_ATTEMPTS_REACHED`). | `error` | **Auto-decline** | Re-capture in better lighting; ensure the face is centered. |
| `LIVENESS_FACE_ATTACK` | Model detected a presentation attack. | `error` | **Auto-decline** | Treat as fraud. The session is automatically declined. |
| `LOW_LIVENESS_SCORE` | `score` at or below the workflow's review threshold. Suppressed when `NO_FACE_DETECTED` is also present. | `warning`; escalates to `error` when the score is at or below the decline threshold | Configurable (review / decline thresholds) | Re-capture; tune thresholds if you observe false positives. |
### Capture channel integrity
Injection-attack signals reported by the client at capture time. Unlike presentation attacks (an artefact held in front of a real camera), these describe the camera feed itself being replaced or intercepted.
Each is configurable per workflow and defaults to **no action**, so enabling the feature changes no decisions until you opt in.
| Risk | Cause | Config key | Default |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ----------- |
| `VIRTUAL_CAMERA_DETECTED` | The capture device was identified as a software virtual camera (OBS, ManyCam, Snap Camera, DroidCam and similar) rather than a physical camera. | `virtual_camera_action` | `NO_ACTION` |
| `EXTERNAL_CAPTURE_DEVICE_DETECTED` | The capture device was an external camera or a capture card rather than a built-in camera. | `external_capture_device_action` | `NO_ACTION` |
| `FRAME_INJECTION_SUSPECTED` | The captured frames show indicators of software compositing or injection into the camera pipeline rather than a genuine live capture. | `frame_injection_action` | `NO_ACTION` |
| `SCREEN_CAPTURE_DURING_CAPTURE` | Screen recording or screen sharing was active on the device during the biometric capture. | `screen_capture_action` | `NO_ACTION` |
Browsers expose no OS-level camera-source attestation to any vendor, so on web these signals are corroborating evidence rather than proof. On web, a verdict of `null` means **not determined** (device labels are unavailable until camera permission is granted) and never fires a risk.
### Recording sanity forensics
`LIVENESS_VIDEO_ANOMALY` is computed **server-side on the stored liveness recording**, independently of the liveness model. It detects a moving sequence repeated across multiple cycles, consistent with a looped feed.
| Risk | Cause | Config key | Default |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------- |
| `LIVENESS_VIDEO_ANOMALY` | The recording contains a repeated moving sequence. Camera stalls, repeated still frames, dark lead-ins and slow frame delivery alone do not raise this signal. | `face_liveness_video_anomaly_action` | `NO_ACTION` |
Set the action on your Liveness or Age Estimation node: `NO_ACTION` records an information finding, `REVIEW` can escalate a decided verification to In Review, and `DECLINE` can escalate it to Declined. The check runs asynchronously, so an escalation can arrive after the initial result. A more severe existing decision is not downgraded, and manual review decisions are preserved.
Version 2 findings include `forensics_version: 2`, `repeated_sequence` evidence and `repeated_moving_sequence` in `anomaly_reasons`. Delivery measurements such as `unique_frame_ratio`, `static_pair_ratio`, `decoded_fps_ratio` and `max_frame_gap_seconds` remain diagnostics; they cannot establish a replay on their own. Older findings describe the detector version used at the time and are not retroactively reclassified.
This is a conservative additional signal, not complete detection of synthetic media or injected video. A recording with no anomaly is not proof of liveness. Short or ambiguous captures and captures without usable replay evidence remain unflagged by this check. Missing or unreadable recordings produce no finding from this check; other verification checks still apply.
### Cross-session face matching
| Risk | Cause | Additional data | Severity |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `FACE_IN_BLOCKLIST` | Face matched a blocklisted face. | `blocklisted_session_id`, `blocklisted_session_number`, `api_service` | `error` — **auto-decline** |
| `POSSIBLE_FACE_IN_BLOCKLIST` | Face matched a blocklisted face at a lower-confidence band. | `blocklisted_session_id`, `blocklisted_session_number`, `api_service` | `warning` — always routed to Review in workflows |
| `FACE_IN_ALLOWLIST` | Face matched an allowlisted face. | `allowlisted_session_id`, `allowlisted_session_number`, `api_service` | `information` — duplicate-face action skipped |
| `POSSIBLE_FACE_IN_ALLOWLIST` | Face matched an allowlisted face at a lower-confidence band. | `allowlisted_session_id`, `allowlisted_session_number`, `api_service` | `information` — possible duplicate-face action skipped |
| `DUPLICATED_FACE` | Face matched an already-approved face from another user (an `Approved` session with different `vendor_data`, or a face imported against one of your vendor users). | `duplicated_session_id`, `duplicated_session_number`, `api_service` | Configurable — `information` by default |
| `POSSIBLE_DUPLICATED_FACE` | Face matched another approved or imported face at a lower-confidence band. | `duplicated_session_id`, `duplicated_session_number`, `api_service` | Configurable — `information` by default |
| `DUPLICATED_FACE_NAME_MISMATCH` | Emitted alongside `DUPLICATED_FACE` when the matched session's identity does not match this session's resolved name (ID document, `expected_details`, or linked vendor user), using the same name-match scoring and threshold as ID verification. A face reused under a different name is a stronger fraud signal than a plain duplicate, so it carries its own configurable action. | `duplicated_session_id`, `duplicated_session_number`, `api_service`, `name_match_score` | Configurable — `warning` by default. Never relaxes `DUPLICATED_FACE`'s own configured outcome, only escalates it. |
For matches against imported faces or list-entry faces (which have no session), the `*_session_id`, `*_session_number`, and `api_service` values in `additional_data` are `null`.
Faces belonging to the same user (same `vendor_data`, or the same vendor user when available) are excluded from the **entire** index search — both duplicate detection and blocklist screening. Match precedence is: blocklist exact → allowlist exact → duplicate exact → blocklist possible → allowlist possible → duplicate possible — only one of these six mutually exclusive codes appears per report. `DUPLICATED_FACE_NAME_MISMATCH` is not part of that precedence group: it is an additional code emitted **on top of** `DUPLICATED_FACE` when the identity check also fails, never in its place. `additional_data.name_match_score` never carries the names being compared — only ids and a score, so no PII lands in the warning payload.
### Passive liveness quality (only when `method = "PASSIVE"`)
| Risk | Cause | Severity | Remediation |
| ------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------- |
| `MULTIPLE_FACES_DETECTED` | More than one face was visible during the capture. The largest face is always used for scoring and matching. | Configurable — `information` by default | Recapture solo. |
| `LOW_FACE_QUALITY` | Face quality below the review threshold (default 15). | `warning`; `error` when below the decline threshold (default 0 — decline disabled) | Better focus / less occlusion / centered face. |
| `LOW_FACE_LUMINANCE` | Normalized luminance below the minimum threshold (default 20). | Configurable — `warning` by default (action defaults to Review) | Better lighting. |
| `HIGH_FACE_LUMINANCE` | Normalized luminance above the maximum threshold (default 80). | Configurable — `warning` by default (action defaults to Review) | Reduce lighting; avoid backlight. |
`LOW_FACE_LUMINANCE` and `HIGH_FACE_LUMINANCE` are mutually exclusive — the luminance is either below the minimum or above the maximum, never both.
### Age estimation (adaptive workflows and `AGE_ESTIMATION` nodes)
| Risk | Cause | Severity | Remediation |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------- |
| `AGE_BELOW_MINIMUM` | Estimated age below the configured minimum (or borderline threshold when ID-verification fallback is enabled). | `error` | Workflow falls back to ID verification when configured. |
| `AGE_NOT_DETECTED` | Age could not be estimated and ID-verification fallback is disabled. | `error` | Enable fallback or re-capture. |
## Exact description strings
The API returns these exact `short_description` and `long_description` strings for each risk:
| `risk` | `short_description` | `long_description` |
| ------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NO_FACE_DETECTED` | No Face Detected in liveness | The system couldn't identify a face during the liveness check, which may be due to poor image quality, improper positioning, or technical issues. |
| `LIVENESS_FACE_ATTACK` | Liveness Face Attack | The system detected a potential attempt to bypass the liveness check. |
| `LOW_LIVENESS_SCORE` | Low liveness score | The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data. |
| `LIVENESS_VIDEO_ANOMALY` | Liveness video stream anomaly | The recorded liveness video contains a repeated moving sequence consistent with a looped feed. Camera stalls, repeated still frames and slow frame delivery alone do not raise this signal. This is an independent recording check, not a complete assessment of whether the capture is live. |
| `FACE_IN_BLOCKLIST` | Face in blocklist | The system identified a face in the blocklist, which means the face is not allowed to be verified. |
| `POSSIBLE_FACE_IN_BLOCKLIST` | Possible face in blocklist | The system identified a possible face in the blocklist, which means the face is not allowed to be verified. |
| `FACE_IN_ALLOWLIST` | Face in allowlist | The face matched the application's face allowlist, so duplicate-face actions were skipped for this signal. |
| `POSSIBLE_FACE_IN_ALLOWLIST` | Possible face in allowlist | The face possibly matched the application's face allowlist, so possible duplicate-face actions were skipped for this signal. |
| `DUPLICATED_FACE` | Duplicated face from other approved session | The system identified a duplicated face from another approved session, requiring further investigation. |
| `POSSIBLE_DUPLICATED_FACE` | Possible duplicated face from other approved session | The system identified a possible duplicate face from another approved session, requiring further investigation. |
| `DUPLICATED_FACE_NAME_MISMATCH` | Duplicated face under a different name | The same face was already approved on another session, but under a different name. The identity behind the duplicate does not match the identity declared on this session, which is a strong indicator of identity fraud. |
| `MULTIPLE_FACES_DETECTED` | Multiple faces detected | Multiple faces were detected in the liveness image. The system uses the largest face for liveness verification and face comparison, but the presence of multiple faces may require additional review. |
| `LOW_FACE_QUALITY` | Low face quality | The facial image quality is below the acceptable threshold, which may affect the reliability of liveness detection. This could be due to camera resolution, focus, or compression artifacts. |
| `LOW_FACE_LUMINANCE` | Low face luminance | The facial image is too dark, which may affect the accuracy of liveness detection. Better lighting conditions are recommended. |
| `HIGH_FACE_LUMINANCE` | High face luminance | The facial image is too bright or overexposed, which may affect the accuracy of liveness detection. Reduced lighting or avoiding direct light is recommended. |
| `AGE_BELOW_MINIMUM` | Age below minimum | The age of the face is below the minimum age threshold for the application. |
| `AGE_NOT_DETECTED` | Age not detected | The system couldn't identify the age of the face, which is necessary for document verification. |
## Standalone passive liveness API
The standalone [Liveness API](/standalone-apis/passive-liveness) reuses the same risk codes with a few behavioral differences:
* `NO_FACE_DETECTED` and `LOW_LIVENESS_SCORE` are mutually exclusive — the score check only runs when a face was found. `LOW_LIVENESS_SCORE` fires against the request's `face_liveness_score_decline_threshold` (default 30) and always carries `log_type: "error"`.
* `POSSIBLE_FACE_IN_BLOCKLIST` is treated as a decline (`log_type: "error"`) instead of review.
* `MULTIPLE_FACES_DETECTED` carries `log_type: "warning"`, while the duplicate-face risks stay informational. Only `error`-level warnings flip the standalone response's `status` to `Declined`.
* Standalone warning entries include `feature` but no `node_id`.
## Examples
### Auto-decline on detected attack
```json theme={null}
"warnings": [
{
"feature": "LIVENESS",
"risk": "LIVENESS_FACE_ATTACK",
"additional_data": null,
"log_type": "error",
"short_description": "Liveness Face Attack",
"long_description": "The system detected a potential attempt to bypass the liveness check.",
"node_id": "liveness_primary"
}
]
```
### Configurable warnings on a passive capture
```json theme={null}
"warnings": [
{
"feature": "LIVENESS",
"risk": "LOW_FACE_QUALITY",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face quality",
"long_description": "The facial image quality is below the acceptable threshold, which may affect the reliability of liveness detection. This could be due to camera resolution, focus, or compression artifacts.",
"node_id": "liveness_primary"
},
{
"feature": "LIVENESS",
"risk": "LOW_FACE_LUMINANCE",
"additional_data": null,
"log_type": "warning",
"short_description": "Low face luminance",
"long_description": "The facial image is too dark, which may affect the accuracy of liveness detection. Better lighting conditions are recommended.",
"node_id": "liveness_primary"
},
{
"feature": "LIVENESS",
"risk": "MULTIPLE_FACES_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "Multiple faces detected",
"long_description": "Multiple faces were detected in the liveness image. The system uses the largest face for liveness verification and face comparison, but the presence of multiple faces may require additional review.",
"node_id": "liveness_primary"
}
]
```
### Duplicate face with payload
```json theme={null}
"warnings": [
{
"feature": "LIVENESS",
"risk": "DUPLICATED_FACE",
"additional_data": {
"duplicated_session_id": "8d2f1c4e-6b7a-4c8d-9e0f-1a2b3c4d5e6f",
"duplicated_session_number": 123,
"api_service": null
},
"log_type": "warning",
"short_description": "Duplicated face from other approved session",
"long_description": "The system identified a duplicated face from another approved session, requiring further investigation.",
"node_id": "liveness_primary"
}
]
```
Here the duplicate-face action is set to Review, so the warning carries `log_type: "warning"` and the liveness report's status is `In Review`. With the default (No action) it would carry `log_type: "information"` instead. `api_service` is `null` because the matched face came from a workflow session, not a standalone API call.
### Duplicate face under a different name
```json theme={null}
"warnings": [
{
"feature": "LIVENESS",
"risk": "DUPLICATED_FACE",
"additional_data": {
"duplicated_session_id": "8d2f1c4e-6b7a-4c8d-9e0f-1a2b3c4d5e6f",
"duplicated_session_number": 123,
"api_service": null
},
"log_type": "information",
"short_description": "Duplicated face from other approved session",
"long_description": "The system identified a duplicated face from another approved session, requiring further investigation.",
"node_id": "liveness_primary"
},
{
"feature": "LIVENESS",
"risk": "DUPLICATED_FACE_NAME_MISMATCH",
"additional_data": {
"duplicated_session_id": "8d2f1c4e-6b7a-4c8d-9e0f-1a2b3c4d5e6f",
"duplicated_session_number": 123,
"api_service": null,
"name_match_score": 42.5
},
"log_type": "warning",
"short_description": "Duplicated face under a different name",
"long_description": "The same face was already approved on another session, but under a different name. The identity behind the duplicate does not match the identity declared on this session, which is a strong indicator of identity fraud.",
"node_id": "liveness_primary"
}
]
```
`DUPLICATED_FACE` stays at its own configured action (here, No action — `log_type: "information"`) while `DUPLICATED_FACE_NAME_MISMATCH` carries its own action, defaulting to Review (`log_type: "warning"`). The liveness report's status follows the most severe of the two: `In Review` here, even though the plain duplicate-face setting alone would not have flagged it.
### Recording sanity anomaly with its measurements
```json theme={null}
"warnings": [
{
"feature": "LIVENESS",
"risk": "LIVENESS_VIDEO_ANOMALY",
"additional_data": {
"forensics_version": 2,
"duration_seconds": 6.0,
"repeated_sequence": {
"period_frames": 6,
"cycles": 3,
"matched_pairs": 12
},
"anomaly_reasons": ["repeated_moving_sequence"]
},
"log_type": "information",
"short_description": "Liveness video stream anomaly",
"long_description": "The recorded liveness video contains a repeated moving sequence consistent with a looped feed. Camera stalls, repeated still frames and slow frame delivery alone do not raise this signal. This is an independent recording check, not a complete assessment of whether the capture is live.",
"node_id": "liveness_primary"
}
]
```
This example shows the replay evidence from a recording containing three copies of a moving sequence. `log_type` is `information` because the configured action is `NO_ACTION`. The frame-delivery diagnostics, omitted from this example for brevity, do not trigger the finding.
## Warning types
Each risk is assigned a severity based on your application's configuration. The three severities are:
## Related
* [Liveness report](/core-technology/liveness/report-liveness) — full report schema and statuses.
* [Face match warnings](/core-technology/face-match/warnings-face-match) — companion 1:1 face match warnings.
* [Webhooks](/integration/webhooks) — `session.status.updated` carries the warnings as soon as the liveness step finishes.
* [Data models — Liveness check](/reference/data-models#liveness-check) — canonical schema with every field.
* [Data models — Warning object](/reference/data-models#warning-object) — the shape of every entry in `warnings[]`.
# NFC Verification
Source: https://docs.didit.me/core-technology/nfc-verification/overview
Verify e-passports and eIDs via NFC chip reading. Cryptographic validation, tamper-proof checks, highest-security ID auth. Pay-per-call $0.15.
NFC-based authentication lets you verify identity documents by reading the secure chip embedded in modern passports and IDs using a mobile phone's NFC capabilities. This provides the **highest level of security available** for ID Verification.
***
## Key Benefits
| Benefit | Description |
| ---------------------- | --------------------------------------------------------------------- |
| **Highest Security** | Verifies cryptographic signatures directly from government issuers |
| **Easy to Use** | Works automatically with any NFC-enabled phone |
| **Tamper-Proof** | Detects document manipulation invisible to the human eye |
| **Comprehensive Data** | Extracts additional data not available through OCR alone |
| **Flexible Fallback** | Gracefully falls back to standard verification if NFC isn't available |
***
## How to Enable NFC Verification
### Native SDK Integration (Recommended)
NFC verification is available through our **native SDKs**, which provide full access to device NFC capabilities:
| Platform | NFC Support | Documentation |
| ----------- | -------------- | ----------------------------------------------------------------- |
| **iOS** | ✅ Full Support | [iOS SDK Documentation](/integration/native-sdks/ios-sdk) |
| **Android** | ✅ Full Support | [Android SDK Documentation](/integration/native-sdks/android-sdk) |
The iOS SDK handles all NFC configuration automatically, including:
* ISO7816 application identifiers for ePassports
* Entitlements and capabilities setup
* Real-time chip reading with visual feedback
### Web Integration
> ⚠️ **Browser Limitations**
>
> NFC verification is **not available in web browsers** due to platform restrictions:
>
> | Browser | NFC Support |
> | -------------------- | ------------------------------------------------------------------ |
> | **Chrome (Android)** | Experimental Web NFC API - unstable, requires explicit permissions |
> | **Safari (iOS)** | No Web NFC API support |
> | **Other Browsers** | No support |
>
> For web integrations, NFC verification is automatically skipped and users proceed with standard document + selfie verification.
***
## How It Works
The user starts the verification process through your app or platform:
* **Native SDK**: Launch the verification flow directly in your app
* **Web/Mobile Web**: User is guided to use the Didit App for NFC-enabled verification, or continues with standard verification
The user photographs their ID document, capturing:
* **MRZ** (Machine Readable Zone) — used to derive chip access keys
* **Document front and back images** — visual data and security features
* **Visual security features** — holograms, watermarks, microprint
The system automatically checks device and document compatibility:
| Check | Description |
| --------------------- | ------------------------------------------ |
| **Document type** | Is it an ePassport/eID with an NFC chip? |
| **Device capability** | Does the phone support NFC reading? |
| **Permissions** | Are the necessary NFC permissions granted? |
If NFC is unavailable, verification continues seamlessly with standard document + biometric verification.
For compatible devices and documents:
1. User is guided to position their document near the phone's NFC reader
2. Visual feedback shows real-time reading progress
3. Secure communication is established with the chip using PACE/BAC protocols
The system extracts comprehensive data from the chip following **ICAO 9303** standards:
| Data Group | Contents | Description |
| ----------- | ------------------------ | ------------------------------------------------ |
| **SOD** | Document Security Object | Digital signatures for all data groups |
| **DG1** | Personal Data | Name, ID number, birth date, expiry date |
| **DG2** | Facial Image | High-resolution digital photo of the holder |
| **DG7** | Signature | Digital representation of the holder's signature |
| **DG11-14** | Additional Data | May include fingerprints, iris scans (if stored) |
The extracted data undergoes rigorous cryptographic verification:
Each data group's hash is verified against the signatures stored in the SOD, ensuring data integrity.
The chain of trust is validated: **Document Certificate → Country Signing CA → ICAO PKD Root**, checking against CSCA master lists and the ICAO Public Key Directory.
Certificates are verified against Certificate Revocation Lists (CRLs) to ensure they haven't been revoked and the document hasn't been reported stolen/compromised.
NFC-extracted data is cross-validated against OCR data from document photos, facial biometric comparison, and MRZ checksum verification.
The final verification result includes:
| Output | Description |
| --------------------------- | ----------------------------------------------------------- |
| **NFC verification status** | Success, failed, or skipped |
| **Data consistency score** | How well NFC data matches visual data |
| **Certificate validation** | Full chain verification status |
| **Confidence boost** | NFC adds significant confidence to the overall verification |
***
## Supported Documents
NFC verification works with **ePassports** and **eIDs** that contain an NFC chip, typically indicated by this symbol on the document:
Most passports issued after 2006 contain NFC chips. Coverage varies by country for national ID cards. Check all the [supported documents](/core-technology/nfc-verification/supported-documents-nfc-verification).
***
## Integration Examples
### iOS SDK (Available Now)
```swift theme={null}
import DiditSdk
// NFC is automatically enabled when the document supports it
let config = DiditSdk.Configuration(
// NFC configuration is handled automatically
// Just ensure your app has the required entitlements
)
let result = try await DiditSdk.shared.startVerification(
with: sessionId,
configuration: config
)
```
**[Complete iOS SDK Documentation →](/integration/native-sdks/ios-sdk)**
### Android SDK (Coming Soon)
```kotlin theme={null}
// Coming soon - use WebView integration in the meantime
// See Android SDK documentation for WebView setup
```
**[Android SDK Documentation →](/integration/native-sdks/android-sdk)**
***
## Security Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ NFC Verification Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Document │───▶│ Phone │───▶│ Didit Backend │ │
│ │ NFC Chip │ │ NFC Read │ │ │ │
│ └──────────┘ └──────────┘ │ ┌────────────────┐ │ │
│ │ │ ICAO PKD │ │ │
│ │ │ Validation │ │ │
│ │ └────────────────┘ │ │
│ │ ┌────────────────┐ │ │
│ │ │ CSCA Master │ │ │
│ │ │ List Check │ │ │
│ │ └────────────────┘ │ │
│ │ ┌────────────────┐ │ │
│ │ │ CRL Revocation │ │ │
│ │ │ Check │ │ │
│ │ └────────────────┘ │ │
│ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Verification Result │ │
│ │ + Confidence Score │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
***
## FAQ
What if the user's phone doesn't support NFC?
The verification flow automatically detects NFC capability. If unavailable, users proceed with standard document + selfie verification. The verification is still secure, just without the additional NFC confidence boost.
What if the document doesn't have an NFC chip?
Many documents (especially older passports and some national IDs) don't have NFC chips. The system detects this and seamlessly continues with standard verification.
How much does NFC verification improve accuracy?
NFC verification provides cryptographic proof of document authenticity directly from the issuing government. This significantly increases confidence in the verification result and catches sophisticated forgeries that might pass visual inspection.
Is NFC verification required?
No. NFC is an enhancement, not a requirement. Your verification workflow can be configured to require NFC, prefer it when available, or skip it entirely based on your risk tolerance.
***
# NFC verification report
Source: https://docs.didit.me/core-technology/nfc-verification/report-nfc-verification
How to read Didit's NFC / ePassport report: status, chip data, certificate authenticity, where it appears in API responses, and example payloads.
## Overview
The **NFC verification report** captures what Didit read and validated from a document's NFC chip — an ePassport, an electronic ID card, or another ICAO 9303 e-document. It includes the chip's biographical data, cryptographic integrity checks (SOD and Data Group hashes), and a summary of the Document Signer Certificate (DSC) used by the issuing country.
The report is produced after the user completes the NFC step in a workflow. Didit reads the chip's Data Groups on the user's device, verifies the Document Security Object (SOD) signature against the issuing country's CSCA certificate, checks each Data Group hash against the values the SOD signed, and cross-checks the chip's MRZ against the MRZ that was OCR'd off the visible side of the document.
Each report carries its own `status` — independent of the overall session status — that reflects how the NFC step alone resolved:
* **Approved** — the chip was read and none of the warnings that fired are configured to route the step to review or decline.
* **In Review** — at least one warning fired whose configured action is **Review** (for example `NFC_CHIP_NOT_VERIFIED` or `NFC_TRUST_ANCHOR_MISSING` when the unverified-chip action is set to Review).
* **Declined** — at least one warning fired whose configured action is **Decline** (for example `NFC_AND_OCR_DATA_NOT_SAME` when the data-inconsistency action is set to Decline).
* **Not Finished** — the user never completed the NFC step; the chip read was abandoned or the device did not support NFC.
* **Resub Requested** — a reviewer asked the user to resubmit; the report will be superseded by a new attempt.
## Where it appears in API responses
The NFC report ships inside the `nfc_verifications[]` array — **always a JSON array**, never a singular `nfc` object. Multiple entries appear when a workflow runs NFC more than once (for example a step-up flow); each entry carries its own `node_id`.
* **Session decision API** — appears as `nfc_verifications[]` at the top level of `GET /v3/session/{sessionId}/decision/`. See [Retrieve session decision](/sessions-api/retrieve-session).
* **Webhooks** — `status.updated` payloads include the same `nfc_verifications[]` array once the NFC step has produced data. See [Webhooks](/integration/webhooks).
* **Standalone NFC** — for direct chip submission, see the [NFC overview](/core-technology/nfc-verification/overview).
When your workflow includes an NFC step that was skipped or bypassed, the decision payload also carries a top-level `nfc_skip_reason` field stating exactly why — the document has no chip, the issuing country's certificate is unavailable, the device has no NFC reader, the verification ran without a native SDK (no NFC access from a browser), the chip access key could not be derived from the MRZ, or the user chose to skip. It stays `null` for outcomes outside that taxonomy — most commonly a user who abandoned mid-attempt (report status `Not Finished`) or a session where NFC is still pending. See [Skip reasons](/core-technology/nfc-verification/warnings-nfc-verification#skip-reasons) for the exact values.
Older documentation referenced a singular `nfc` field. That shape does **not** exist on the v3 decision endpoint — it only appears on destinations explicitly pinned to V2 webhooks. Always read `response.nfc_verifications[0]` and iterate the array.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#nfc-verification) reference page. The fields below mirror that canonical schema.
| Field | Type | Description |
| ---------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status` | `"Not Finished" \| "Approved" \| "Declined" \| "In Review" \| "Resub Requested"` | NFC-step status (see meanings above). |
| `node_id` | string \| null | Workflow graph node that produced this report. Lets you tell two NFC checks apart in a multi-step workflow. |
| `is_nfc_skipped` | boolean | `true` when this NFC step was explicitly skipped instead of read. |
| `skip_reason` | string \| null | Why the step was skipped — only set when `is_nfc_skipped` is `true`. Same values as the session-level `nfc_skip_reason`; see [Skip reasons](/core-technology/nfc-verification/warnings-nfc-verification#skip-reasons). |
| `portrait_image` | string (signed URL) \| null | Portrait extracted from the chip (DG2). Short-lived presigned link — download promptly; re-fetch the decision for a fresh URL. |
| `signature_image` | string (signed URL) \| null | Holder's signature image from the chip (DG7), when present. Same short-lived presigned behavior. |
| `chip_data` | object \| null | Flat object with the biographical data parsed from the chip. Always present after a successful read: `dgs` (list of Data Groups read), `surname`, `name`, `country`, `nationality`, `birth_date` (`YYYY-MM-DD`), `expiry_date` (`YYYY-MM-DD`), `sex` (`M`/`F`), `document_type` (MRZ document code, e.g. `P`), `document_number`, `optional_data`, the MRZ check digits (`birth_date_hash`, `expiry_date_hash`, `document_number_hash`, `optional_data_hash`, `final_hash`), `mrz_string`, and `mrz_type` (`TD1`/`TD2`/`TD3`). When the chip carries DG11, `place_of_birth` and `address` are added (and `full_name` / `full_name_non_latin` for documents with a non-Latin name); these keys are **absent**, not null, otherwise. `null` when the NFC step was skipped. |
| `authenticity.sod_integrity` | boolean | `true` when the SOD signature chains to a valid issuing-country CSCA and the DSC is neither revoked nor expired. |
| `authenticity.dg_integrity` | boolean | `true` when every Data Group's hash matches the value the SOD signed. A `false` value means the chip data was tampered with or partially read. |
| `certificate_summary` | object \| null | Summary of the DSC that signed this chip: `issuer`, `subject`, `serial_number`, `not_valid_before`, `not_valid_after` (timestamps as `YYYY-MM-DD HH:MM:SS`), plus a `validation` object with the individual check results (`csca_verified`, `csca_missing`, `dsc_revoked`, `dsc_expired`, `csca_expired`). `csca_missing` is `true` when the DSC's issuing CSCA trust anchor is not available. `null` when the SOD could not be loaded or the step was skipped. |
| `warnings[]` | array | Module-level warnings, each with `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, and `node_id` — see [NFC warnings](/core-technology/nfc-verification/warnings-nfc-verification). |
## Status values
| Status | Meaning | Downstream effect |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `Approved` | Chip read and no warning fired with a Review or Decline action — including the case where warnings fired but their configured action is "No action". | Counts as a successful NFC check; will not, by itself, push the session into review. |
| `In Review` | A warning fired whose configured action is Review (`SKIPPED_NFC_VALIDATION`, `NFC_CHIP_NOT_VERIFIED`, `NFC_TRUST_ANCHOR_MISSING`, or `NFC_AND_OCR_DATA_NOT_SAME`, depending on your settings). | The session also moves to `In Review` until a reviewer acts. |
| `Declined` | A warning fired whose configured action is Decline. | The session is declined unless another approved branch satisfies the workflow. |
| `Not Finished` | User abandoned the NFC step, the device lacked NFC support, or the chip read never completed. | The NFC branch did not produce a result; check workflow fallbacks. |
| `Resub Requested` | A reviewer requested resubmission of this step. | A new attempt replaces this report once the user retries. |
The two certificate warnings (`DSC_CERTIFICATE_REVOKED`, `DSC_CERTIFICATE_EXPIRED`) are informational: they appear in `warnings[]` but never change the NFC `status` by themselves. See [NFC warnings](/core-technology/nfc-verification/warnings-nfc-verification) for the full behavior matrix.
## Examples
### Approved — chip read and cryptographically valid
```json theme={null}
{
"nfc_verifications": [
{
"status": "Approved",
"node_id": "nfc_primary",
"portrait_image": "https:///.../epassport/_face.jpg?signature=...",
"signature_image": "https:///.../epassport/_signature.jpg?signature=...",
"chip_data": {
"dgs": ["DG1", "DG2", "DG7", "DG11"],
"surname": "DOE",
"name": "JOHN",
"country": "ESP",
"nationality": "ESP",
"birth_date": "1990-05-15",
"expiry_date": "2030-01-01",
"sex": "M",
"document_type": "P",
"document_number": "AAB000123",
"optional_data": "",
"birth_date_hash": "4",
"expiry_date_hash": "7",
"document_number_hash": "3",
"optional_data_hash": "",
"final_hash": "4",
"mrz_string": "P/.../epassport/_face.jpg?signature=...",
"signature_image": null,
"chip_data": {
"dgs": ["DG1", "DG2"],
"surname": "DOE",
"name": "JOHN",
"country": "ESP",
"nationality": "ESP",
"birth_date": "1990-05-15",
"expiry_date": "2030-01-01",
"sex": "M",
"document_type": "P",
"document_number": "AAB000254",
"optional_data": "",
"birth_date_hash": "4",
"expiry_date_hash": "7",
"document_number_hash": "6",
"optional_data_hash": "",
"final_hash": "6",
"mrz_string": "P
Customize the accepted document types and countries through your [custom workflows](/console/workflows) to align with your business requirements and compliance needs.
### Security and Reliability Features
Our NFC verification system ensures:
* **Chip Authentication**: Verifies the authenticity of the NFC chip
* **Passive Authentication**: Validates the integrity of stored data
* **Active Authentication**: Prevents chip cloning (where supported)
* **Real-time Validation**: Confirms document validity during verification
* **Data Protection**: Implements end-to-end encryption for all NFC communications
### Security and Accuracy Standards
We maintain the highest standards of accuracy and security in NFC-based ID Verification:
* **Regular Updates**: Our NFC-supported document database is continuously updated with new documents and countries
* **Continuous Improvement**: NFC reading and data extraction algorithms are continually refined for accuracy and efficiency
* **Strict Data Protection**: Rigorous data protection and privacy measures safeguard all NFC-extracted information
* **ICAO Compliance**: Verification processes comply with ICAO standards and international guidelines for NFC-based identity verification
***
## Supported NFC Documents by Country
The table below shows all supported NFC documents by country. Each checkmark indicates document type availability.
This list is regularly updated as we expand our NFC verification capabilities. If a country or document type you need is not listed, please contact our support team for the most current information.
# NFC verification warnings
Source: https://docs.didit.me/core-technology/nfc-verification/warnings-nfc-verification
Every warning Didit's NFC module emits — skipped chip read, unverified chip, unavailable trust anchor, OCR/chip mismatch, certificate revoked or expired — with cause, severity, and remediation.
## Overview
Warnings on the NFC report flag every condition Didit observed while reading and validating the chip. They land in the `warnings[]` array on each item of `nfc_verifications[]` (see [NFC report](/core-technology/nfc-verification/report-nfc-verification)), with `feature` set to `"NFC"`. Each entry follows the shared [warning object](/reference/data-models#warning-object) shape: `feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`.
Every warning has three layers:
1. **The `risk` code** — a stable identifier you can match on in your code (the codes listed below).
2. **The `log_type`** — one of `information`, `warning`, or `error`. It is derived from the action your workflow configures for that risk: **No action** → `information`, **Review** → `warning`, **Decline** → `error`.
3. **The decision impact** — the same configured action also drives the NFC report's `status`: a Review action moves it to `In Review`, a Decline action to `Declined`.
Four NFC risks are configurable this way (`SKIPPED_NFC_VALIDATION`, `NFC_CHIP_NOT_VERIFIED`, `NFC_TRUST_ANCHOR_MISSING`, `NFC_AND_OCR_DATA_NOT_SAME`). The two certificate risks (`DSC_CERTIFICATE_REVOKED`, `DSC_CERTIFICATE_EXPIRED`) are **informational only**: they always carry `log_type: "information"` and never change the NFC status by themselves.
The full set of risk codes the NFC module produces is below. No other NFC risk codes exist.
## Warnings produced
| `risk` | Cause | Severity (`log_type`) | Affects status? | Remediation |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SKIPPED_NFC_VALIDATION` | The chip read was skipped — the workflow allows skipping (`allow_nfc_skip`) and the user tapped skip, or the client skipped because it could not read NFC. The exact reason is recorded in `additional_data.skip_reason` (see [Skip reasons](#skip-reasons)) and drives the warning's `long_description`. | Follows the **skip NFC action** setting (`skip_nfc_action`): information / warning / error. | Configurable — No action, Review, or Decline. | If your policy requires NFC, set the skip action to Decline (or disallow skipping entirely) and instruct the user to retry from an NFC-capable device. Otherwise leave it at No action to fall back to OCR-only verification. |
| `NFC_CHIP_NOT_VERIFIED` | The chip was read but failed cryptographic verification for a reason other than an explicitly identified missing trust anchor. A Data Group hash, the SOD signature, or another passive-authentication check did not verify. | Follows the **unverified chip action** setting (`unverified_chip_action`). | Configurable — No action, Review, or Decline. | Treat this as a potentially invalid or altered chip. Inspect `authenticity` and `certificate_summary.validation` to see which check failed. |
| `NFC_TRUST_ANCHOR_MISSING` | The chip's Document Signer Certificate (DSC) belongs to a Country Signing Certification Authority (CSCA) generation that is not available in Didit's trust store (`certificate_summary.validation.csca_missing` is `true`). Didit therefore cannot establish the SOD's chain of trust. | Follows the **unverified chip action** setting (`unverified_chip_action`). | Configurable — No action, Review, or Decline. | This is a trust-store coverage gap, not evidence that the chip was altered. Review the document through your fallback checks and contact Didit support with the issuing country so the missing CSCA can be investigated. |
| `NFC_AND_OCR_DATA_NOT_SAME` | The MRZ read from the chip does not match the MRZ OCR'd from the visible side of the document (compared after normalization, ignoring `<` filler). Strong tampering signal. | Follows the **data inconsistency action** setting (`inconsistent_data_action`) — the same setting that governs ID-verification data-consistency checks. | Configurable — No action, Review, or Decline. | Treat as tampering unless proven otherwise; Decline is the typical setting. The `additional_data` payload includes the OCR MRZ lines (`ocr_mrz`, array) and the chip MRZ (`nfc_mrz`, single string) for manual comparison. |
| `DSC_CERTIFICATE_REVOKED` | The Document Signer Certificate that signed this chip appears in the issuing country's Certificate Revocation List (CRL). | Always `information`. | No — never changes the NFC status by itself. | Strong signal that the signing key may have been compromised; route these sessions to manual review via your own logic if needed. Note the chip will usually also fire `NFC_CHIP_NOT_VERIFIED`, which is configurable. |
| `DSC_CERTIFICATE_EXPIRED` | The DSC's validity window has passed (`certificate_summary.validation.dsc_expired` is `true`). | Always `information`. | No — never changes the NFC status by itself. | Common with older documents. As with revocation, an expired DSC also fails full SOD validation, so `NFC_CHIP_NOT_VERIFIED` fires alongside it and carries your configured action. |
`SKIPPED_NFC_VALIDATION` is mutually exclusive with `NFC_CHIP_NOT_VERIFIED` and `NFC_TRUST_ANCHOR_MISSING`: chip-verification warnings are only evaluated when the chip read was not skipped. The two chip-verification warnings are also mutually exclusive.
## Exact description strings
The API returns these exact `short_description` and `long_description` strings for each risk:
| `risk` | `short_description` | `long_description` |
| --------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SKIPPED_NFC_VALIDATION` | Skipped NFC Validation | Reason-specific — see [Skip reasons](#skip-reasons). Falls back to "The Near Field Communication (NFC) chip validation process was not completed, potentially due to technical issues or lack of NFC support." on legacy sessions recorded before skip reasons existed. |
| `NFC_CHIP_NOT_VERIFIED` | NFC chip not verified | The Near Field Communication (NFC) chip failed passive authentication for a reason other than an explicitly identified missing trust anchor. The chip data or signature could be invalid or altered. |
| `NFC_TRUST_ANCHOR_MISSING` | NFC trust anchor unavailable | The Near Field Communication (NFC) chip could not be verified because the issuing country's Country Signing Certification Authority (CSCA) trust anchor is not available. This is a trust-store coverage gap, not evidence that the chip was altered. |
| `NFC_AND_OCR_DATA_NOT_SAME` | NFC and OCR data not same | The Near Field Communication (NFC) chip data and the OCR data don't match, indicating potential document tampering or data inconsistency. |
| `DSC_CERTIFICATE_REVOKED` | Document Signer Certificate revoked | The Document Signer Certificate (DSC) used to sign the ePassport chip data has been revoked by the issuing country's Certificate Revocation List (CRL), indicating the signing key may have been compromised. |
| `DSC_CERTIFICATE_EXPIRED` | Document Signer Certificate expired | The Document Signer Certificate (DSC) used to sign the ePassport chip data has expired, meaning the certificate's validity period has passed. |
## Skip reasons
`SKIPPED_NFC_VALIDATION` warnings carry `additional_data.skip_reason`, one of six stable codes stating exactly why NFC did not run (on sessions recorded before skip reasons existed, `additional_data` may be `null` — treat the field as optional when processing historical decisions). The same taxonomy appears at the session level as `nfc_skip_reason` on the decision payload — including for sessions where NFC could never run at all (chipless document, web integration) and therefore no NFC report or warning exists.
| `skip_reason` | Meaning | `long_description` |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USER_SKIPPED` | NFC was offered on a compatible device and the user chose to skip. | The NFC chip reading step was offered on a compatible device, but the user chose to skip it (skipping is allowed by the workflow configuration). |
| `DOCUMENT_WITHOUT_CHIP` | The document design has no readable NFC chip (not an ICAO 9303 electronic document). | NFC verification could not be performed because this document does not have a readable NFC chip: it is not an ICAO 9303 electronic document. |
| `CHIP_CERTIFICATE_UNAVAILABLE` | The chip exists, but no issuing-country signing certificate (CSCA) covers the document's issue period, so the signature cannot be verified. | NFC verification could not be performed because the issuing country's signing certificate (CSCA) covering this document's issue period is not available, so the chip's signature cannot be verified. |
| `DEVICE_WITHOUT_NFC` | A native SDK reported the phone has no NFC reader available. | NFC verification could not be performed because the user's device reported that it has no NFC reader available. |
| `INTEGRATION_WITHOUT_NFC_ACCESS` | The verification ran without a native SDK (for example in a web browser), which has no access to the phone's NFC reader. | NFC verification could not be performed because the verification did not run through a native mobile SDK, so there was no access to the phone's NFC reader (web browsers cannot read NFC chips). |
| `MRZ_KEY_UNAVAILABLE` | The chip access key (BAC/PACE seed) could not be derived from the document's MRZ. | NFC verification could not be performed because the chip access key could not be derived from the document's machine-readable zone (MRZ). |
An NFC report and its `SKIPPED_NFC_VALIDATION` warning only exist when the client reached the NFC step and explicitly skipped it (typically `USER_SKIPPED`, or `DEVICE_WITHOUT_NFC` when the reader turned out to be unavailable at scan time). When the NFC step was bypassed before it could ever be offered — chipless document, missing certificate, no NFC capability reported, unreadable MRZ key — no report or warning is created and the reason surfaces only in the session-level `nfc_skip_reason` field, without affecting the session status.
## Configuration
Three workflow settings control how NFC warnings route. Each can be set to **No action**, **Review**, or **Decline**:
1. **Skip NFC action** (`skip_nfc_action`) — applies to `SKIPPED_NFC_VALIDATION`. Skipping is only offered to users when `allow_nfc_skip` is enabled.
2. **Unverified chip action** (`unverified_chip_action`) — applies to both `NFC_CHIP_NOT_VERIFIED` and `NFC_TRUST_ANCHOR_MISSING`, so the session status behavior remains the same while the risk code explains why verification failed.
3. **Data inconsistency action** (`inconsistent_data_action`) — applies to `NFC_AND_OCR_DATA_NOT_SAME`. This is the same data-inconsistency setting used by ID verification.
The two `DSC_CERTIFICATE_*` risks have no action setting — they are always informational.
## Examples
### In Review — chip could not be verified
```json theme={null}
{
"warnings": [
{
"feature": "NFC",
"risk": "NFC_CHIP_NOT_VERIFIED",
"additional_data": null,
"log_type": "warning",
"short_description": "NFC chip not verified",
"long_description": "The Near Field Communication (NFC) chip failed passive authentication for a reason other than an explicitly identified missing trust anchor. The chip data or signature could be invalid or altered.",
"node_id": "nfc_primary"
},
{
"feature": "NFC",
"risk": "DSC_CERTIFICATE_EXPIRED",
"additional_data": null,
"log_type": "information",
"short_description": "Document Signer Certificate expired",
"long_description": "The Document Signer Certificate (DSC) used to sign the ePassport chip data has expired, meaning the certificate's validity period has passed.",
"node_id": "nfc_primary"
}
]
}
```
Here the unverified-chip action is set to Review, so `NFC_CHIP_NOT_VERIFIED` carries `log_type: "warning"` and the NFC report's status is `In Review`. The expired-DSC entry is purely informational.
### Declined — chip / OCR mismatch
```json theme={null}
{
"warnings": [
{
"feature": "NFC",
"risk": "NFC_AND_OCR_DATA_NOT_SAME",
"additional_data": {
"ocr_mrz": ["P
## Related
* [NFC report](/core-technology/nfc-verification/report-nfc-verification) — full report schema and statuses.
* [Webhooks](/integration/webhooks) — `status.updated` fires on session status changes; the decision payload (including NFC warnings) is attached when the session reaches Approved, Declined, In Review, or Abandoned.
* [Data models — NFC verification](/reference/data-models#nfc-verification) — canonical field-by-field schema.
* [Data models — Warning object](/reference/data-models#warning-object) — the shape of every entry in `warnings[]`.
# Phone Verification Overview
Source: https://docs.didit.me/core-technology/phone-verification/overview
Verify phone numbers with OTP across SMS, WhatsApp, Telegram, RCS, and voice. Pay-per-call $0.04 + carrier, carrier detection, risk scoring.
Didit's Phone Verification provides a reliable method to verify user phone numbers through one-time passcodes (OTP). This feature adds an essential layer of security to your identity verification process, ensuring legitimate user contact information.
In the Academy lesson on reading a verification result, the 8:08 chapter covers phone verification and duplicate detection.
## How it works
Our Phone Verification solution delivers comprehensive phone number validation through a simple, user-friendly process. The system combines OTP verification with advanced risk assessment to provide reliable phone verification at scale.
The system securely collects the following information:
| Data Collected | Details |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Phone Number** | If not provided during session creation, the user enters their phone number in international format. If provided, the user must verify the pre-filled number. |
| **Country Code** | Automatically suggested based on IP location or user selection. |
| **Carrier Type** | Detected whether mobile or landline number. |
Our verification system:
* Generates a secure, time-limited one-time passcode
* Delivers the code via SMS to the provided number
* Ensures optimized delivery through multiple carrier integrations
* Provides fallback options for delivery challenges
The user completes verification by:
* Entering the received code into the verification interface
* Submitting within the configured timeframe (typically 5–10 minutes)
* Requesting a new code if needed (with appropriate rate limiting)
Our system performs advanced checks:
| Check | Description |
| ---------------------- | --------------------------------------------------------- |
| **Format & Carrier** | Verifies the phone number format and carrier information. |
| **Disposable Numbers** | Checks against known disposable/virtual number databases. |
| **SIM Swap Detection** | Identifies potential SIM swap risks. |
| **Activity Patterns** | Detects suspicious activity patterns. |
| **Historical Data** | Cross-references with historical verification data. |
Access verification results through multiple channels:
| Channel | Description |
| ------------- | ------------------------------------------------------------- |
| **Dashboard** | Real-time dashboard updates. |
| **Webhooks** | Instant webhook notifications. |
| **API** | RESTful API integration. |
| **Reports** | Comprehensive reports with detailed verification information. |
## Verification Features
Our Phone Verification service offers several key features to enhance your verification process:
#### OTP Verification
* **Secure Code Generation**: Randomly generated one-time passcodes
* **SMS Delivery**: Fast and reliable delivery to mobile devices
* **Configurable Timeouts**: Set expiration times based on your security requirements
* **Retry Options**: Allow users to request new codes with appropriate rate limiting
#### Phone Number Analysis
* **Format Validation**: Ensure the phone number follows the correct international format
* **Carrier Detection**: Identify the telecommunications provider associated with the number
* **Number Type**: Distinguish between mobile, landline, and VoIP numbers
* **Country Validation**: Verify phone number matches expected country format
#### Risk Assessment
* **Disposable Number Detection**: Identify temporary numbers used to avoid traceability
* **Virtual Number Identification**: Flag numbers that may be virtual or VoIP-based
* **Activity Monitoring**: Track suspicious patterns across verification attempts
* **Blocklist Checking**: Check against internal lists of previously misused numbers
## Pricing
Phone verification is charged **per message sent** with transparent, pay-as-you-go pricing:
| Component | Cost |
| --------------- | ----------------------------- |
| **Didit Fee** | \$0.04 per verification |
| **Carrier Fee** | Varies by country and channel |
| **Total** | \$0.04 + carrier fee |
We support multiple delivery channels with different pricing (availability varies by country):
| Channel | Description |
| ------------ | ------------------------------------- |
| **SMS** | Traditional text message delivery |
| **WhatsApp** | Delivery via WhatsApp messaging |
| **Telegram** | Delivery via Telegram messaging |
| **RCS** | Rich Communication Services messaging |
| **Viber** | Delivery via Viber messaging |
| **Zalo** | Delivery via Zalo messaging (Vietnam) |
You only pay when a message is sent. If the user abandons before OTP delivery, you are not charged.
→ [Phone Verification Pricing](/getting-started/phone-verification-pricing)
# Phone Verification Report
Source: https://docs.didit.me/core-technology/phone-verification/report-phone-verification
Parse Phone Verification responses: carrier data, line type, OTP lifecycle, cross-session matches, and risk warnings. Pay-per-OTP from $0.04 + carrier fees.
The Phone Verification report captures the full outcome of a phone OTP challenge: who we sent the code to, which carrier and channel handled it, how many attempts it took, whether the number is disposable or VoIP, and any cross-session matches against your blocklist or other users' sessions.
This page documents the JSON shape returned by the decision endpoint so you can parse OTP outcomes, carrier metadata, and risk flags for each verified number.
## Overview
A phone report is produced every time a workflow node runs the Phone Verification feature. Each report represents one OTP challenge against one phone number and contains:
* The phone number broken into prefix, national number and full E.164 form, plus ISO country code and country name.
* Carrier metadata (`name`, `type`) resolved from the destination network.
* Boolean risk flags (`is_disposable`, `is_virtual`) derived from line-type analysis.
* The actual delivery channel used (`verification_method`) and the count of OTP send attempts.
* A chronological `lifecycle[]` log of every send, retry, delivery event, and code-check attempt.
* A `matches[]` array surfacing the same number on other sessions — any status, across KYC, KYB and standalone API verifications — or your blocklist.
* A `warnings[]` array — risk events emitted during the verification (see [Phone Verification warnings](/core-technology/phone-verification/warnings-phone-verification)).
* A `node_id` that identifies which workflow graph node produced the report (V3 sessions only).
In hosted verification flows Didit runs the OTP exchange for you. For direct API integrations the same flow is two calls — [`POST /v3/phone/send/`](/standalone-apis/phone-send) to deliver the code and [`POST /v3/phone/check/`](/standalone-apis/phone-check) to validate the user's entry. A pending OTP is valid for **5 minutes from the first send** (retries do not extend the window). In workflow sessions users get 2 OTP send attempts (`phone_max_retries`) and 2 code-entry attempts (`phone_max_check_attempts`) by default — both tunable per workflow node; exhausting either cap finalizes the step as `Declined` with `VERIFICATION_CODE_ATTEMPTS_EXCEEDED`. The standalone phone API allows 3 code-entry attempts per verification.
## Where it appears in API responses
The decision endpoint (`GET /v3/session/{sessionId}/decision/`) returns phone reports under the plural array key **`phone_verifications`**. The array contains one entry per Phone Verification node in the workflow graph — typically one, but step-up flows may produce several.
```json theme={null}
{
"session_id": "11111111-1111-1111-1111-111111111111",
"status": "Approved",
"phone_verifications": [
{ "status": "Approved", "node_id": "feature_phone_1", "...": "..." }
]
}
```
A `null` value means no Phone Verification step has run yet. Iterate the array (rather than reading `phone_verifications[0]`) when your workflow can collect more than one phone number.
## Schema
The canonical field-by-field schema lives on the [Data models](/reference/data-models#phone-verification) page.
```typescript theme={null}
interface PhoneVerification {
status: "Not Finished" | "Approved" | "Declined" | "In Review" | "Expired";
phone_number_prefix: string; // E.g. "+34"
phone_number: string; // National number, no prefix
full_number: string; // Full E.164, e.g. "+34600600600"
country_code: string; // ISO 3166-1 alpha-2, e.g. "ES"
country_name: string;
carrier: {
name: string;
type: // From LineTypeChoices
| "mobile" | "fixed_line" | "voip" | "isp" | "vpn"
| "toll_free" | "premium_rate" | "shared_cost" | "local_rate"
| "satellite" | "pager" | "payphone" | "voice_mail"
| "calling_cards" | "service" | "short_codes_commercial"
| "universal_access" | "other" | "unknown";
};
is_disposable: boolean; // Temporary / burner number
is_virtual: boolean; // true when carrier.type is voip, isp or vpn
verification_method: // Actual channel that delivered the OTP
| "sms" | "whatsapp" | "telegram" | "voice" | "rcs" | "viber" | "zalo";
verification_attempts: number; // OTP send attempts (initial send + resends)
verified_at: string | null; // ISO 8601, set when a valid OTP was entered
warnings: Warning[]; // Risk events emitted during verification
lifecycle: PhoneLifecycleEvent[]; // OTP send/retry/delivery/check timeline
matches: PhoneMatch[]; // Cross-session / blocklist matches (max 5)
node_id: string; // Workflow graph node that produced this report
}
```
### Status values
| Status | Meaning |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The OTP send has been initiated but the verification has not been finalized yet. |
| `Approved` | The user entered a valid OTP and no declining risk matched. |
| `Declined` | An auto-decline warning fired (blocklist, high-risk, attempts exceeded) or a risk action configured to `DECLINE` matched. |
| `In Review` | A risk action configured to `REVIEW` routed the step to manual review. |
| `Expired` | The 5-minute OTP window elapsed with no valid code — applies to verifications created via the standalone phone API. |
### Lifecycle event types
`lifecycle[]` is sorted chronologically. Each event has `type`, `timestamp`, `details`, and a billable `fee` (when applicable):
| Event type | Emitted when |
| --------------------------------------- | ---------------------------------------------------------------------------- |
| `PHONE_VERIFICATION_MESSAGE_SENT` | First OTP send attempt over the requested channel. |
| `PHONE_VERIFICATION_RETRY_MESSAGE_SENT` | Subsequent OTP send after the user requested a resend. |
| `PHONE_VERIFICATION_BLOCKED` | The send was blocked (spam, suspicious, repeated, invalid number). |
| `PHONE_DELIVERY_DELIVERED` | Carrier confirmed delivery on the actual channel used. |
| `PHONE_DELIVERY_UNDELIVERABLE` | Carrier reported the message could not be delivered. |
| `VALID_CODE_ENTERED` | The user submitted the correct OTP code. |
| `INVALID_CODE_ENTERED` | The user submitted an incorrect OTP code (counts toward the code-entry cap). |
| `PHONE_VERIFICATION_APPROVED` | Feature-level status was set to `Approved`. |
| `PHONE_VERIFICATION_DECLINED` | Feature-level status was set to `Declined`. |
| `PHONE_VERIFICATION_IN_REVIEW` | Feature-level status was set to `In Review`. |
| `PHONE_VERIFICATION_EXPIRED` | The OTP window elapsed without a valid code. |
The `details` payload depends on the event family:
* **Send events** (`PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`) — `{ status, reason, channel, actual_channel }`. `status` is `Success`, `Retry` or `Blocked`; `reason` is `null` unless the send was blocked (`invalid_phone_number`, `repeated_attempts`, `suspicious`, `spam`, `unknown`); `channel` is the requested channel and `actual_channel` the channel that actually delivered (e.g. a WhatsApp send that fell back to SMS).
* **Delivery events** (`PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`) — `{ channel, status }` with `status` `delivered` or `undeliverable`.
* **Check events** (`VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`) — `{ code_tried, status }` with `status` `Approved`, `Declined`, `Failed` or `Expired or Not Found`.
* **Final status events** — `null`, except `PHONE_VERIFICATION_DECLINED` / `PHONE_VERIFICATION_IN_REVIEW`, which carry `{ "reason": "" }` (e.g. `PHONE_NUMBER_IN_BLOCKLIST`).
`fee` on a send event is the estimated price until delivery is confirmed, then the actual billed amount — delivery is billed per delivered message when the verification finalizes, and only `Blocked` sends are guaranteed free. Delivery, check and status events always carry `fee: 0`.
### Cross-session matches
`matches[]` records other sessions in the same application where the same number was seen — across KYC, KYB and standalone API verifications, regardless of their status — plus blocklist hits configured in the [management-api lists](/management-api/lists/overview). Sessions sharing the current session's `vendor_data` are excluded (the same end-user does not match themselves), and the array is capped at **5** entries. When the number is on your blocklist but none of the matched sessions is blocklisted, a synthetic entry with `source: "list_entry"` (all session fields `null`) is prepended to the array.
```typescript theme={null}
interface PhoneMatch {
session_id: string | null; // null when source = "list_entry"
session_number: number | null;
vendor_data: string | null;
verification_date: string | null; // ISO 8601, creation date of the matched session
phone_number: string;
status: string | null; // Status of the matched session
is_blocklisted: boolean;
api_service: string | null; // Set when the matched session came from a standalone API
source: "session" | "list_entry"; // "list_entry" = manual blocklist hit
}
```
## Examples
### Approved — WhatsApp OTP, mobile carrier
```json theme={null}
{
"status": "Approved",
"phone_number_prefix": "+34",
"phone_number": "600600600",
"full_number": "+34600600600",
"country_code": "ES",
"country_name": "Spain",
"carrier": { "name": "Orange", "type": "mobile" },
"is_disposable": false,
"is_virtual": false,
"verification_method": "whatsapp",
"verification_attempts": 1,
"verified_at": "2025-08-24T09:12:39.684292Z",
"warnings": [],
"lifecycle": [
{
"type": "PHONE_VERIFICATION_MESSAGE_SENT",
"timestamp": "2025-08-24T09:12:30.580554Z",
"details": { "status": "Success", "reason": null, "channel": "whatsapp", "actual_channel": "whatsapp" },
"fee": 0.04
},
{
"type": "PHONE_DELIVERY_DELIVERED",
"timestamp": "2025-08-24T09:12:31.000000Z",
"details": { "channel": "whatsapp", "status": "delivered" },
"fee": 0
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2025-08-24T09:12:39.662157Z",
"details": { "code_tried": "123456", "status": "Approved" },
"fee": 0
},
{
"type": "PHONE_VERIFICATION_APPROVED",
"timestamp": "2025-08-24T09:12:39.684292Z",
"details": null,
"fee": 0
}
],
"matches": [],
"node_id": "feature_phone_1"
}
```
### Declined — blocklisted VoIP number
The user entered a valid OTP, but finalization found the number on the application's blocklist (auto-decline) and flagged its VoIP line type (`voip_number_action` left at `NO_ACTION`, so it logs as `information`). Because no matched session is blocklisted, the blocklist hit appears as a synthetic `list_entry` match.
```json theme={null}
{
"status": "Declined",
"phone_number_prefix": "+1",
"phone_number": "5551234567",
"full_number": "+15551234567",
"country_code": "US",
"country_name": "United States",
"carrier": { "name": "VoIP Carrier", "type": "voip" },
"is_disposable": false,
"is_virtual": true,
"verification_method": "sms",
"verification_attempts": 1,
"verified_at": "2025-08-24T09:11:10.221340Z",
"warnings": [
{
"feature": "PHONE",
"risk": "PHONE_NUMBER_IN_BLOCKLIST",
"additional_data": { "blocklisted_session_id": null, "blocklisted_session_number": null, "api_service": null },
"log_type": "error",
"short_description": "Phone number in blocklist",
"long_description": "The system detected that the phone number is in the blocklist, which is not allowed.",
"node_id": "feature_phone_1"
},
{
"feature": "PHONE",
"risk": "VOIP_NUMBER_DETECTED",
"additional_data": null,
"log_type": "information",
"short_description": "VoIP number detected",
"long_description": "The system detected that the phone number is a VoIP number, which is not allowed.",
"node_id": "feature_phone_1"
}
],
"lifecycle": [
{
"type": "PHONE_VERIFICATION_MESSAGE_SENT",
"timestamp": "2025-08-24T09:11:00.000000Z",
"details": { "status": "Success", "reason": null, "channel": "sms", "actual_channel": "sms" },
"fee": 0.04
},
{
"type": "VALID_CODE_ENTERED",
"timestamp": "2025-08-24T09:11:10.221340Z",
"details": { "code_tried": "482917", "status": "Approved" },
"fee": 0
},
{
"type": "PHONE_VERIFICATION_DECLINED",
"timestamp": "2025-08-24T09:11:10.350000Z",
"details": { "reason": "PHONE_NUMBER_IN_BLOCKLIST" },
"fee": 0
}
],
"matches": [
{
"session_id": null,
"session_number": null,
"vendor_data": null,
"verification_date": null,
"phone_number": "+15551234567",
"status": null,
"is_blocklisted": true,
"api_service": null,
"source": "list_entry"
}
],
"node_id": "feature_phone_1"
}
```
## Related
* [Phone Verification overview](/core-technology/phone-verification/overview) — feature behavior, pricing, supported channels.
* [Phone Verification warnings](/core-technology/phone-verification/warnings-phone-verification) — every risk code, decline triggers and configurable actions.
* [Data models — phone verification](/reference/data-models#phone-verification) — canonical field-by-field schema.
* [Standalone phone API](/standalone-apis/phone-send) — `POST /v3/phone/send/` and [`POST /v3/phone/check/`](/standalone-apis/phone-check) endpoints.
* [Management API — Lists](/management-api/lists/overview) — manage the phone blocklist that feeds `matches[]` with `source: list_entry`.
# Phone Verification Warnings
Source: https://docs.didit.me/core-technology/phone-verification/warnings-phone-verification
Reference for every Phone Verification risk code: HIGH_RISK_PHONE_NUMBER, VOIP_NUMBER_DETECTED, DISPOSABLE_NUMBER_DETECTED, blocklist, and OTP caps.
The Phone Verification feature emits **warnings** whenever a risk signal fires on the OTP flow or the number itself: a known bad number, a VoIP or disposable line, an OTP attempt cap, or a hit against another user's session. This page lists every code, what triggers it, and how to configure the workflow response.
## Overview
Warnings are tagged with feature `PHONE`. They appear under `phone_verifications[].warnings[]` in `GET /v3/session/{sessionId}/decision/` and follow the standard [warning object](/reference/data-models#warning-object) shape (`feature`, `risk`, `additional_data`, `log_type`, `short_description`, `long_description`, `node_id`). Each warning is also routed into the per-session log so it surfaces in the Business Console under the Phone section.
Some warnings are **hard auto-decline** triggers — they always set the feature status to `Declined`. Others map to a **configurable action** (`DECLINE`, `REVIEW`, or `NO_ACTION`, depending on the setting) defined in your workflow settings, so the same risk code may decline one workflow while only flagging another.
## Auto-decline conditions
The following warnings always decline the Phone Verification step regardless of configuration, and always carry `log_type: "error"`:
* `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` — the user exhausted an OTP attempt cap: by default 2 wrong code submissions (`phone_max_check_attempts`) or more than 2 OTP sends (`phone_max_retries`), both tunable per workflow node. The standalone phone API allows 3 code attempts.
* `PHONE_NUMBER_IN_BLOCKLIST` — the number is in your blocklist managed via the [Lists API](/management-api/lists/overview), or it matches a blocklisted session.
`HIGH_RISK_PHONE_NUMBER` — the delivery provider's anti-fraud layer refusing to send the OTP (`Blocked` send) — declines by default but is **no longer unconditional**: see [`high_risk_phone_action`](#blocked-sends) below.
## Configurable risks
Each of the following risks maps to a workflow setting you can configure to `DECLINE`, `REVIEW`, or `NO_ACTION` (the default — the warning is recorded without affecting the status). The configured action also sets the warning's `log_type`: `DECLINE` → `error`, `REVIEW` → `warning`, `NO_ACTION` → `information`.
| Setting | Risk code | Fires when |
| -------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `voip_number_action` | `VOIP_NUMBER_DETECTED` | The carrier resolves to a `voip`, `isp`, or `vpn` line type. |
| `disposable_number_action` | `DISPOSABLE_NUMBER_DETECTED` | The lookup flags the number as a temporary / burner provider. |
| `duplicated_phone_number_action` | `DUPLICATED_PHONE_NUMBER` | The number matches another session of any status belonging to a different end-user. Skipped when the number is allowlisted. |
Duplicate detection runs within your application across KYC, KYB, and standalone API phone verifications, and groups sessions by `vendor_data` — sessions sharing the same `vendor_data` are treated as one end-user and are excluded from match results. Leave `vendor_data` empty and every session is treated as a distinct user.
### Blocked sends
`HIGH_RISK_PHONE_NUMBER` follows the same pattern with a narrower choice set, because a send the provider refused cannot be approved:
| Setting | Risk code | Fires when |
| ------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `high_risk_phone_action` | `HIGH_RISK_PHONE_NUMBER` | The delivery provider's anti-fraud layer refused to send the OTP (`Blocked` send). Accepts only `DECLINE` or `REVIEW`, and defaults to `DECLINE`. |
The setting is applied **only when every high-risk block on the step carries `additional_data.risk_factors` of exactly `["device_attribute"]`** — the provider's "this device looks suspicious" signal, which is what fires on ordinary mobile lines. Any other risk factor, any mix that merely includes `device_attribute`, or a block with no risk factors at all declines regardless of the setting. The blocking reason (`repeated_attempts`, `suspicious`, or `spam`) is a separate field, returned in `additional_data.blocked_reason`.
Independently of the setting, a number on a phone allowlist created via the [Lists API](/management-api/lists/overview) is never auto-declined for a blocked send: the step goes to **In Review**, the warning carries `log_type: "warning"`, and `additional_data.phone_number_in_allowlist` is `true`.
## Verification attempt limits
The Phone Verification feature applies a hard cap on OTP attempts to prevent abuse:
* **Code-entry attempts** — Default **2** wrong submissions (`phone_max_check_attempts`) before the step finalizes as `Declined` with `VERIFICATION_CODE_ATTEMPTS_EXCEEDED`. The standalone phone API allows **3** code attempts per verification.
* **Send attempts** — Default **2** sends in total (`phone_max_retries`): the initial send plus one resend. A further send request finalizes the step and raises `VERIFICATION_CODE_ATTEMPTS_EXCEEDED`.
* **OTP validity** — A pending OTP is valid for **5 minutes from the first send**; retries do not extend the window.
Both caps are tunable per workflow node. Independently of these caps, the delivery provider's anti-fraud layer can block a send outright (reason `repeated_attempts`, `suspicious`, or `spam`) — that raises `HIGH_RISK_PHONE_NUMBER`, not the attempts warning.
## Warnings produced
| Tag | `log_type` | Description |
| ------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` | `error` (always) | The user exceeded the OTP code-entry or send cap. Auto-declines. |
| `HIGH_RISK_PHONE_NUMBER` | Follows `high_risk_phone_action`, or `warning` when allowlisted | The delivery provider blocked the OTP send. Declines by default; set `high_risk_phone_action` to `REVIEW` to route a device-signal-only block to In Review instead (see [Blocked sends](#blocked-sends)). A number on a phone allowlist also goes to In Review. `additional_data`: `{ blocked_reason, risk_factors, phone_number_in_allowlist? }`. |
| `PHONE_NUMBER_IN_BLOCKLIST` | `error` (always) | The number is in a blocklist created via the [Lists API](/management-api/lists/overview), or matches a blocklisted session. Auto-declines. `additional_data`: `{ blocklisted_session_id, blocklisted_session_number, api_service }` (all `null` for a pure list hit). |
| `PHONE_NUMBER_IN_ALLOWLIST` | `information` | The number matched other sessions but is in an allowlist created via the [Lists API](/management-api/lists/overview), so duplicate-phone actions are skipped. `additional_data`: `{ phone_number }`. |
| `DISPOSABLE_NUMBER_DETECTED` | Follows `disposable_number_action` | The number is a temporary / burner provider often used to evade traceability. |
| `VOIP_NUMBER_DETECTED` | Follows `voip_number_action` | The number resolves to a `voip`, `isp`, or `vpn` line rather than a standard mobile or fixed line. |
| `DUPLICATED_PHONE_NUMBER` | Follows `duplicated_phone_number_action` | The number matches another session — of any status — belonging to a different end-user (grouped by `vendor_data`). `additional_data`: `{ duplicated_session_id, duplicated_session_number, api_service }`. |
## Cross-session matches
When a number is detected on other sessions or on your blocklist, those hits also appear under `phone_verifications[].matches[]` (capped at 5 entries). Each match carries `session_id`, `session_number`, `vendor_data`, `verification_date`, `phone_number`, `status`, `is_blocklisted`, `api_service`, and a `source` of `session` (another session with the same number, any status) or `list_entry` (a synthetic blocklist hit prepended to the array). If the current number is on your phone allowlist, Didit keeps the match evidence but emits `PHONE_NUMBER_IN_ALLOWLIST` instead of applying the duplicate-phone action. See the [Phone Verification report](/core-technology/phone-verification/report-phone-verification#cross-session-matches) for the full match schema.
## Example
A blocked send (`HIGH_RISK_PHONE_NUMBER`) on a VoIP number, with `voip_number_action` configured to `REVIEW`:
```json theme={null}
{
"warnings": [
{
"feature": "PHONE",
"risk": "HIGH_RISK_PHONE_NUMBER",
"additional_data": { "blocked_reason": "suspicious" },
"log_type": "error",
"short_description": "High risk phone number",
"long_description": "The system detected that the phone number is a high risk phone number, which is not allowed.",
"node_id": "feature_phone_1"
},
{
"feature": "PHONE",
"risk": "VOIP_NUMBER_DETECTED",
"additional_data": null,
"log_type": "warning",
"short_description": "VoIP number detected",
"long_description": "The system detected that the phone number is a VoIP number, which is not allowed.",
"node_id": "feature_phone_1"
}
]
}
```
## Warning types
Each risk is assigned a severity based on your application's configuration. Severities fall into three categories:
## Related
* [Phone Verification overview](/core-technology/phone-verification/overview) — feature behavior, pricing, supported channels.
* [Phone Verification report](/core-technology/phone-verification/report-phone-verification) — full response shape including `lifecycle[]` and `matches[]`.
* [Data models — phone verification](/reference/data-models#phone-verification) — canonical field-by-field schema.
* [Management API — Lists](/management-api/lists/overview) — manage the phone blocklist used by `PHONE_NUMBER_IN_BLOCKLIST`.
# Proof of Address Overview
Source: https://docs.didit.me/core-technology/proof-of-address/overview
Verify residential addresses with AI-powered extraction from utility bills, bank statements, and government documents. Pay-per-call $0.20.
Proof of Address (PoA) verification allows you to verify a user's residential address through official documents. This feature simplifies the address verification process, enabling users to capture or upload documents that confirm their address.
The Academy lesson on reading a verification result reaches the proof of address check at 7:20.
## How it works
Didit's Proof of Address Verification delivers a robust solution for verifying a user's residential address through official documentation. Our solution combines advanced AI, computer vision, and comprehensive security checks to provide fast, reliable address verification at scale.
Users can verify their address through:
| Method | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Auto Capture** | Our AI automatically detects optimal document positioning and captures automatically at the perfect moment |
| **Upload Option** | Users can upload existing documents in various formats (PDF, JPG, PNG) |
| **Multi-Page Support** | Seamlessly handles multi-page documents like bank statements |
Powerful processing capabilities:
* High-precision OCR for extracting address information
* Intelligent document classification to identify document types
* Name matching with identity documents for cross-verification
* Issue date extraction and validation against configured requirements
* Format and pattern matching for all address fields
Our AI-powered system performs extensive checks:
| Check | Description |
| --------------------------- | --------------------------------------------------------- |
| **Document Authenticity** | Verification of document legitimacy |
| **Tamper Detection** | Image integrity analysis to detect manipulation |
| **Address Standardization** | Formatting and normalizing address fields |
| **Geocoding** | Providing location coordinates from the extracted address |
| **Language Detection** | Identifying and validating document language |
Access verification results through multiple channels:
* Real-time dashboard updates
* Instant webhook notifications
* RESTful API integration
* Comprehensive reports with detailed verification session information
## Document Requirements
To ensure successful address verification, documents should meet these criteria:
#### Accepted Document Types
The following table lists the accepted document types and their respective subtypes. All documents must be issued within the last 3 months unless otherwise specified.
| Document Type | Subtypes |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Utility Bill** | Electricity Bill, Water Bill, Gas Bill, Internet Bill, Phone Bill, Cable TV Bill, Satellite TV Bill, Trash Collection Bill, Sewage Bill, Heating Bill, Combined Utilities Bill, Municipal Services Bill |
| **Bank Statement** | Account Statement, Credit Card Statement, Bank Letter Confirming Address, Mortgage Statement, Loan Statement, Savings Account Statement, Checking Account Statement, Investment Account Statement, Business Account Statement, Credit Union Statement, Debit Card Statement, Line of Credit Statement |
| **Government-Issued Document** | Tax Assessment, Voter Registration Card, ID document, Residency Certificate, Government Letter/Notification, Census Letter, Individual Registration Certificate, Property Tax Bill, Vehicle Registration, Social Security Statement, Unemployment Benefits Letter, Disability Benefits Letter, Pension Statement, Court Summons, Jury Duty Notice, Municipal Permit, Immigration Document |
| **Other Proof of Address** | Lease Agreement, Rental Agreement, Employer Letter Confirming Address, Insurance Policy Document, School Enrollment Letter, Notarized Affidavit of Address, Solicitor Letter Confirming Address, Student Loan Statement, Court Document Confirming Address, Homeowners Insurance Policy, Renters Insurance Policy, Auto Insurance Policy, Health Insurance Statement, Medical Bill, Hospital Bill, Subscription Service Bill, Gym Membership Statement, Property Management Letter, Homeowners Association Statement, University Transcript, College Enrollment Verification, Payroll Stub, Employment Verification Letter, Retirement Account Statement, Brokerage Account Statement |
#### Country availability
By default, Proof of Address supports the same countries and territories as ID Verification, including Turks and Caicos Islands (`TCA`) and Tuvalu (`TUV`). Every supported country offers the four document categories listed above. You can restrict this default coverage in your workflow's Proof of Address settings.
If an existing workflow does not show a newly supported country, open its Proof of Address settings and save them again to refresh the workflow's allowed-country configuration.
#### General Requirements
* Valid document within 90 days of issue date
* Physically intact (free from damage, scratches, or stains)
* Complete address information clearly visible
* Document issuer name and logo visible
* Name on document should match the user's verified identity
* Different from ID document (cannot be the same document used for ID verification)
#### Image Requirements
* Original document captured in real-time or high-quality scan
* Supported formats: JPG, JPEG, PNG, TIFF, PDF
* Maximum file size: 15MB
* Full-color image with all corners visible
* No digital editing or manipulation
* All pages included for multi-page documents
# Proof of address report
Source: https://docs.didit.me/core-technology/proof-of-address/report-proof-of-address
Parse the proof-of-address report: extracted address, document type, issuer, dates, name-match scores, PDF overlay-manipulation evidence, and warnings.
## Overview
Proof-of-address (POA) verification accepts a single PDF or image (TIFF, JPG, JPEG, PNG, WEBP, optionally ZIP-compressed) up to 15 MB, runs document-type classification, LLM-based extraction, and PDF/EXIF forensics, and returns:
* The **extracted address** — both raw and structured (`street_1`, `city`, `region`, `postal_code`, lat/lng).
* **Document metadata** — file size, content type, creation and modified dates, EXIF dates, signature info, and any overlay-manipulation evidence from PDF forensics.
* **Name match scores** against the verified ID document and against expected details supplied at session creation.
* **Bank-related extra fields** (account number, IBAN, sort code, routing number, SWIFT/BIC, branch) when the document is a bank statement.
POA extraction performs unstructured-text reading, which introduces a **5–15 second** latency per document. Webhooks fire only when the entire workflow step completes — do not poll the decision endpoint faster than every 5 s during this window.
## Where it appears in API responses
The decision endpoint returns POA as the plural array **`poa_verifications[]`** in `GET /v3/session/{sessionId}/decision/`. Each entry is one execution of the POA node in your workflow; sessions with multiple POA steps (e.g., bank statement + utility bill) produce one entry per execution with its own `node_id`.
```text theme={null}
GET /v3/session/{sessionId}/decision/
──▶ { "poa_verifications": [ { node_id, status, document_type, poa_address, … }, … ] }
```
The shape below mirrors the canonical schema — see [Data models](/reference/data-models#proof-of-address).
## Schema
See [Proof of address in the Data Models reference](/reference/data-models#proof-of-address) for the canonical schema.
```typescript theme={null}
interface POAV3 {
status: 'Not Finished' | 'Approved' | 'Declined' | 'In Review';
node_id: string | null;
issuing_state: string | null; // ISO 3166-1 alpha-3
document_type: // never null — falls back to 'UNKNOWN'
| 'UTILITY_BILL'
| 'BANK_STATEMENT'
| 'GOVERNMENT_ISSUED_DOCUMENT'
| 'OTHER_POA_DOCUMENT'
| 'UNKNOWN';
document_subtype: string; // e.g. 'ELECTRICITY_BILL', 'ACCOUNT_STATEMENT';
// never null — falls back to 'UNKNOWN'
issuer: string | null;
issue_date: string | null; // YYYY-MM-DD
expiration_date: string | null; // YYYY-MM-DD when present
// Extracted address
poa_address: string | null; // raw, single-line
poa_formatted_address: string | null; // geocoded/formatted
poa_parsed_address: {
address_type?: string;
street_1?: string;
street_2?: string;
city?: string;
region?: string;
postal_code?: string;
country?: string; // ISO 3166-1 alpha-2
raw_results?: object;
document_location?: { latitude: number; longitude: number };
} | null;
document_file: string | null; // presigned URL, expires after ~4 hours
document_language: string | null; // ISO 639-1, e.g. 'en'
name_on_document: string | null;
// Expected details (when supplied at session creation)
expected_details_address: string | null;
expected_details_formatted_address: string | null;
expected_details_parsed_address: object | null;
// Match scores (float, 0-100)
name_match_score_expected_details: number | null;
name_match_score_id_verification: number | null;
// PDF + image forensics
document_metadata: {
file_size: number | null;
content_type: string | null;
creation_date: string | null;
modified_date: string | null;
creator: string | null;
producer: string | null;
software: string | null;
encryption: string | null;
is_signed: boolean | null;
is_tampered: boolean | null;
signature_info: object | null;
exif_original_date: string | null;
exif_digitized_date: string | null;
processed_by_known_editor: boolean | string | null;
has_different_creation_mod_date: boolean | null;
overlay_manipulation: {
detected: boolean;
analyzed: boolean;
signals: string[]; // 'duplicate_font_subset', 'glyph_fragmentation'
duplicate_font_subsets: object[];
fragmented_fonts: object[];
manipulated_regions: {
page: number;
x: number;
y: number;
width: number;
height: number;
page_width: number;
page_height: number;
}[];
} | null; // null for non-PDF uploads or when not analyzed
} | null;
// Bank-related and custom fields
extra_fields: {
bank_account_number: string | null;
bank_iban: string | null;
bank_sort_code: string | null;
bank_routing_number: string | null;
bank_swift_bic: string | null;
bank_branch_name: string | null;
bank_branch_address: string | null;
document_phone_number: string | null;
additional_names: string[];
[customField: string]: unknown;
};
extra_files: string[]; // presigned URLs for additional uploaded files
// QR codes and barcodes
detected_codes: {
type: string; // 'QR_CODE', 'CODE_128', 'EAN_13', 'DATA_MATRIX', 'PDF_417', …
page_number: number; // 1-based
position: [number, number][] | null;
x: number | null;
y: number | null;
width: number | null;
height: number | null;
page_width: number;
page_height: number;
raw_payload: string; // base64, empty string when unavailable
parsed_payload: unknown | null; // null when located but undecodable
}[];
warnings: Warning[]; // see Data Models — Warning object
}
```
Each entry in `warnings[]` is a [Warning object](/reference/data-models#warning-object): `{ feature, risk, additional_data, log_type, short_description, long_description, node_id }`, with `feature` always `"PROOF_OF_ADDRESS"` for this report.
## Status values
`status` uses the feature-level status enum (`FeatureStatusChoices`).
| Value | Meaning |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The POA node has not completed for this session (default state). |
| `Approved` | The document parsed cleanly and no warning resolved to Decline or Review. |
| `In Review` | At least one configurable warning is set to **Review** for your workflow (and nothing declined). |
| `Declined` | A hard-decline warning fired (`MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`), or a configurable warning is set to **Decline** for your workflow. |
Custom status rules configured on the workflow node can further adjust the computed status; rules combine with precedence Declined > In Review > Approved. See [Proof of address warnings](/core-technology/proof-of-address/warnings-proof-of-address) for the full risk-to-action mapping, including the workflow-only retry behavior of `FUTURE_ISSUE_DATE`.
## PDF overlay-manipulation evidence
When the submitted document is a PDF, `document_metadata.overlay_manipulation` may contain forensic evidence of suspected text overlays:
* `detected` — `true` when the check found manipulation evidence.
* `analyzed` — `true` when the PDF could be analysed at all.
* `signals` — the checks that fired (`duplicate_font_subset`, `glyph_fragmentation`).
* `manipulated_regions` — page-coordinate rectangles around the suspected edited text. Each rectangle uses the PDF page coordinate space (`x`, `y`, `width`, `height`, `page_width`, `page_height`) so reviewers can highlight the affected area in a PDF preview.
The field is `null` when the document is not a PDF or the PDF could not be analysed. When `detected=true`, the system also emits a `SUSPECTED_DOCUMENT_MANIPULATION` warning whose `additional_data` mirrors the signals and rectangles above (unless a stronger signal — modification after digital signing, `is_tampered=true` — took priority, in which case `additional_data` describes the signature tampering instead).
## QR codes and barcodes
Every POA report includes `detected_codes`, covering every processed page. Each item contains:
| Field | Description |
| --------------------------- | ------------------------------------------------------------------------------------------ |
| `type` | Detected symbology, such as `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, or `PDF_417` |
| `page_number` | 1-based page number |
| `position` | Detected polygon in rendered-page pixels, or `null` when no reliable location is available |
| `x`, `y`, `width`, `height` | Bounding box in rendered-page pixels, or `null` when no reliable location is available |
| `page_width`, `page_height` | Rendered page dimensions used by the coordinates |
| `parsed_payload` | Decoded string or structured value, or `null` when the code can only be located |
| `raw_payload` | Raw decoder payload as a base64 string when available; otherwise an empty string |
`detected_codes` is an empty array when no supported code is found. Didit never guesses a payload: damaged or unreadable codes may be returned with their location and `parsed_payload: null`, while failures in code detection do not fail the POA verification.
## Examples
### Approved — bank statement
```json theme={null}
{
"poa_verifications": [
{
"status": "Approved",
"node_id": "feature_poa_1",
"issuing_state": "USA",
"document_type": "BANK_STATEMENT",
"document_subtype": "ACCOUNT_STATEMENT",
"issuer": "National Bank",
"issue_date": "2026-04-15",
"expiration_date": null,
"document_language": "en",
"name_on_document": "John A. Smith",
"poa_address": "123 Main St, Apartment 4B, New York, NY 10001",
"poa_formatted_address": "123 Main St, Apartment 4B, New York, NY 10001, USA",
"poa_parsed_address": {
"address_type": "Street",
"street_1": "123 Main St",
"street_2": "Apartment 4B",
"city": "New York",
"region": "NY",
"postal_code": "10001",
"country": "US",
"document_location": { "latitude": 40.7128, "longitude": -74.0060 }
},
"document_file": "https:///.../poa.pdf?X-Amz-Expires=14400",
"document_metadata": {
"file_size": 246810,
"content_type": "application/pdf",
"creation_date": "2026-04-15",
"modified_date": "2026-04-15",
"creator": "PDFKit",
"producer": "Quartz",
"software": null,
"encryption": null,
"is_signed": false,
"is_tampered": false,
"signature_info": null,
"exif_original_date": null,
"exif_digitized_date": null,
"processed_by_known_editor": false,
"has_different_creation_mod_date": false,
"overlay_manipulation": null
},
"name_match_score_expected_details": 100,
"name_match_score_id_verification": 98,
"expected_details_address": null,
"expected_details_formatted_address": null,
"expected_details_parsed_address": null,
"extra_fields": {
"bank_account_number": "****1234",
"bank_iban": null,
"bank_sort_code": null,
"bank_routing_number": "021000089",
"bank_swift_bic": "NABNUS33",
"bank_branch_name": "Manhattan Midtown",
"bank_branch_address": "111 Wall St, New York, NY",
"document_phone_number": null,
"additional_names": []
},
"extra_files": [],
"detected_codes": [
{
"type": "QR_CODE",
"page_number": 1,
"position": [[820, 1120], [1002, 1120], [1002, 1302], [820, 1302]],
"x": 820,
"y": 1120,
"width": 182,
"height": 182,
"page_width": 1190,
"page_height": 1684,
"raw_payload": "aHR0cHM6Ly9uYXRpb25hbGJhbmsuZXhhbXBsZS9zdG10Lzk5ODE=",
"parsed_payload": "https://nationalbank.example/stmt/9981"
},
{
"type": "CODE_128",
"page_number": 1,
"position": [[112, 1480], [560, 1480], [560, 1540], [112, 1540]],
"x": 112,
"y": 1480,
"width": 448,
"height": 60,
"page_width": 1190,
"page_height": 1684,
"raw_payload": "MDAxMjM0NTY3ODk=",
"parsed_payload": "001234567 89"
}
],
"warnings": []
}
]
}
```
### Declined — overlay manipulation + name mismatch
```json theme={null}
{
"poa_verifications": [
{
"status": "Declined",
"node_id": "feature_poa_1",
"issuing_state": "GBR",
"document_type": "UTILITY_BILL",
"document_subtype": "ELECTRICITY_BILL",
"issuer": "BritishEnergy",
"issue_date": "2026-03-10",
"expiration_date": null,
"document_language": "en",
"name_on_document": "JOHN B SMYTH",
"poa_address": "44 King's Road, London, SW3 4UD",
"poa_formatted_address": "44 King's Road, London, SW3 4UD, United Kingdom",
"poa_parsed_address": {
"street_1": "44 King's Road",
"city": "London",
"postal_code": "SW3 4UD",
"country": "GB",
"document_location": { "latitude": 51.4880, "longitude": -0.1690 }
},
"document_file": "https:///.../poa.pdf?X-Amz-Expires=14400",
"document_metadata": {
"file_size": 412910,
"content_type": "application/pdf",
"creation_date": "2026-03-10",
"modified_date": "2026-05-12",
"creator": "Adobe Acrobat Pro",
"producer": "Adobe PDF Library",
"software": "Adobe Acrobat",
"encryption": null,
"is_signed": false,
"is_tampered": false,
"signature_info": null,
"exif_original_date": null,
"exif_digitized_date": null,
"processed_by_known_editor": false,
"has_different_creation_mod_date": true,
"overlay_manipulation": {
"detected": true,
"analyzed": true,
"signals": ["duplicate_font_subset", "glyph_fragmentation"],
"duplicate_font_subsets": [{ "page": 1, "base_font": "Helvetica" }],
"fragmented_fonts": [],
"manipulated_regions": [
{ "page": 1, "x": 102.4, "y": 318.1, "width": 187.0, "height": 18.0, "page_width": 595, "page_height": 842 }
]
}
},
"name_match_score_expected_details": 62,
"name_match_score_id_verification": 60,
"expected_details_address": null,
"expected_details_formatted_address": null,
"expected_details_parsed_address": null,
"extra_fields": {
"bank_account_number": null, "bank_iban": null, "bank_sort_code": null,
"bank_routing_number": null, "bank_swift_bic": null,
"bank_branch_name": null, "bank_branch_address": null,
"document_phone_number": null, "additional_names": []
},
"extra_files": [],
"detected_codes": [],
"warnings": [
{
"feature": "PROOF_OF_ADDRESS",
"risk": "SUSPECTED_DOCUMENT_MANIPULATION",
"additional_data": {
"reason": "Overlay-text manipulation detected: same base font embedded with multiple subset prefixes on 1 page(s).",
"detection_method": "overlay_text_manipulation",
"signals": ["duplicate_font_subset", "glyph_fragmentation"],
"duplicate_font_subsets": [{ "page": 1, "base_font": "Helvetica" }],
"fragmented_fonts": [],
"manipulated_regions": [
{ "page": 1, "x": 102.4, "y": 318.1, "width": 187.0, "height": 18.0, "page_width": 595, "page_height": 842 }
]
},
"log_type": "error",
"short_description": "Suspected document manipulation",
"long_description": "The system detected signs of potential document manipulation or editing.",
"node_id": "feature_poa_1"
},
{
"feature": "PROOF_OF_ADDRESS",
"risk": "NAME_MISMATCH_ID_VERIFICATION",
"additional_data": {
"poa_name": "JOHN B SMYTH",
"kyc_name": "John A. Smith",
"match_score": 60
},
"log_type": "warning",
"short_description": "Name mismatch with ID verification",
"long_description": "The full name on the document does not match the name from the user's verified identity documents.",
"node_id": "feature_poa_1"
}
]
}
]
}
```
## Related
* [Proof of address warnings](/core-technology/proof-of-address/warnings-proof-of-address) — every warning code POA can emit
* [Proof of address overview](/core-technology/proof-of-address/overview) — accepted document types and verification rules
* [Data models — Proof of address](/reference/data-models#proof-of-address) — canonical schema
* [Webhooks](/integration/webhooks) — `status.updated` carries POA payloads
`document_file` and `extra_files[]` are presigned URLs that expire after about 4 hours. Re-fetch the decision endpoint to refresh URLs. Store only the verification status and parsed address fields on your side — minimise retained document data.
# Proof of address warnings
Source: https://docs.didit.me/core-technology/proof-of-address/warnings-proof-of-address
Every proof-of-address warning: name and address mismatches, expired or unsupported documents, overlay manipulation, extraction failures, and configurable actions.
## Overview
Proof-of-address (POA) verification emits warnings on `poa_verifications[].warnings[]` for every quality, authenticity, or matching issue detected during extraction and forensic analysis. Each warning is a [Warning object](/reference/data-models#warning-object) with `feature` set to `"PROOF_OF_ADDRESS"`. A handful of risks always force `Declined`; the rest follow a per-risk action (`DECLINE` / `REVIEW` / `NO_ACTION`) configured on your workflow.
Every risk below is verified against the live decision pipeline; the exact short and long description strings are reproduced verbatim in the tables.
POA extraction introduces a **5–15 second** latency per document. Warnings are written only when the workflow step completes; do not poll faster than every 5 s.
## Two production paths
POA warnings are produced on two different paths, and the risk set differs between them:
* **Workflow sessions** (the POA step inside a verification session) — the upload endpoint first runs blocking validation with a retry loop (see [Retry behavior](#retry-behavior-workflow-sessions)), then the final stored warnings are computed by `check_poa_risks` plus the multi-document name check.
* **Standalone API** (`POST /v3/poa/`) — warnings come from `check_poa_risks` only. There is no retry loop, no verified-ID context, and only one document, so `NAME_MISMATCH_ID_VERIFICATION`, `POA_NAME_MISMATCH_BETWEEN_DOCUMENTS`, `POA_MAX_ATTEMPTS_EXCEEDED`, and `FUTURE_ISSUE_DATE` are **never** produced by the standalone API. The pre-extraction validation errors are discarded on this path.
`POOR_DOCUMENT_QUALITY` is defined in the risk enum and reserved in the action mapping, but **no code path in the POA pipeline produces it** — it never appears in `warnings[]` on either path. Do not branch on it.
## Auto-decline warnings (always force `Declined`)
These risks are in `AUTO_DECLINE_RISKS`; when logged, the POA status is `Declined` regardless of configuration, and `log_type` is always `error`.
| Risk | Cause | Recommended remediation |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `MISSING_ADDRESS_INFORMATION` | No address could be extracted from the document. | Ask the user to resubmit a document that clearly shows their address. |
| `POA_DOCUMENT_EXPIRED` | The document is expired by one of two paths: its **printed expiration date** has passed, or it is **older than the configured maximum age** for its type. Defaults: **3 months** for `UTILITY_BILL` and `BANK_STATEMENT`, **12 months** for `GOVERNMENT_ISSUED_DOCUMENT` and `OTHER_POA_DOCUMENT` (a month counts as 30 days; `-1` disables the check). `additional_data` always carries `expiration_reason` (`printed_expiration` or `age_window`), `document_type`, `document_subtype`, and `issue_date`; the `printed_expiration` path adds `expiration_date`, and the `age_window` path adds `max_age_months`. | Ask the user for a valid or more recent document, or raise `age_months` for that type. |
| `INVALID_DOCUMENT_TYPE` | The document could not be classified into any supported POA type (`document_type` falls back to `UNKNOWN`). | Ask the user to resubmit; check the file is not corrupted. |
| `UNABLE_TO_VALIDATE_DOCUMENT_AGE` | The document-age check itself failed (error while computing the expiration from the issue date). | Ask the user for a document with a clearly readable issue date. |
`FUTURE_ISSUE_DATE` is also listed in `AUTO_DECLINE_RISKS`, but it has a single producer in pre-extraction validation (an issue date more than 7 days in the future) whose errors only feed the **workflow retry loop** — it surfaces as a blocking upload error there, is never produced by the standalone API, and is not written to `warnings[]`. See [Retry behavior](#retry-behavior-workflow-sessions).
## Configurable warnings
Each of these follows an action knob on the workflow's POA node (`DECLINE` / `REVIEW` / `NO_ACTION`). The warning's `log_type` mirrors the configured action: `error` for Decline, `warning` for Review, `information` for No action.
| Risk | Cause | Action knob | Default |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ----------- |
| `NAME_MISMATCH_WITH_PROVIDED` | The name on the document scores below `poa_name_match_score_threshold` (default 86) against the name supplied at session creation (`expected_details`). | `poa_name_or_address_mismatch_action` | Review |
| `NAME_MISMATCH_ID_VERIFICATION` | The name on the document scores below the threshold against the verified ID document. `additional_data`: `{ poa_name, kyc_name, match_score }`. Workflow sessions only. | `poa_name_or_address_mismatch_action` | Review |
| `ADDRESS_MISMATCH_WITH_PROVIDED` | The extracted address differs from the address supplied via API, or either address could not be verified as a residential address. | `poa_name_or_address_mismatch_action` | Review |
| `POA_COUNTRY_MISMATCH_WITH_PROVIDED` | The document's country does not match the country supplied via API (ISO-3 normalised). `additional_data`: `{ expected_country, extracted_country }`. | `poa_name_or_address_mismatch_action` | Review |
| `POA_NAME_MISMATCH_BETWEEN_DOCUMENTS` | Two or more POA documents in the same session carry different names. `additional_data` includes both node IDs, both names, and the match score. Workflow sessions only. | `poa_name_or_address_mismatch_action` | Review |
| `DOCUMENT_METADATA_MISMATCH` | The file is empty (`file_size` 0) or its EXIF dates are malformed. | `poa_document_issues_action` | Review |
| `SUSPECTED_DOCUMENT_MANIPULATION` | Forensics found manipulation evidence — modification after digital signing (`is_tampered`), overlay-text manipulation (with `additional_data.manipulated_regions` rectangles), a known PDF editor, inconsistent EXIF dates, or a suspicious re-export. `additional_data.detection_method` names the check that fired. | `poa_document_authenticity_action` | **Decline** |
| `UNSUPPORTED_DOCUMENT_LANGUAGE` | The document's language is not in `poa_languages_allowed`. | `poa_unsupported_language_action` | **Decline** |
| `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | The detected document type is not enabled in `poa_documents_allowed` for the document's country, or its subtype is explicitly disabled. | `poa_unsupported_document_type_action` | **Decline** |
| `ISSUER_NOT_IDENTIFIED` | The issuing institution could not be identified from the document. | `poa_issuer_not_identified_action` | Review |
| `UNABLE_TO_EXTRACT_ISSUE_DATE` | No valid issue date could be located on the document. | `poa_issue_date_not_detected_action` | Review |
| `POA_NAME_NOT_DETECTED` | No name was detected on the document. | `poa_issue_date_not_detected_action` (shared knob) | Review |
| `UNPARSABLE_OR_INVALID_ADDRESS` | An address was extracted but could not be parsed and geocoded into a valid residential address. | `poa_unparsable_or_invalid_address_action` | Review |
| `POA_MAX_ATTEMPTS_EXCEEDED` | The user exhausted `poa_max_retry_attempts` (default 2) in the workflow retry loop. `additional_data`: `{ attempts }`. Workflow sessions only. | `poa_max_attempts_exceeded_action` | **Decline** |
There is **one shared action** for all name and address mismatches: `poa_name_or_address_mismatch_action` covers both name risks, both address risks, and the cross-document name check — you cannot configure name and address mismatch behavior separately. On the standalone API the same group follows the `poa_address_mismatch_action` request option (the `poa_name_mismatch_action` option is accepted but never read).
## Standalone API differences (`POST /v3/poa/`)
The standalone endpoint builds the same risk-to-action mapping but with request-level options that only accept `DECLINE` or `NO_ACTION` (all defaulting to `DECLINE`):
* `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` are **hard-coded to Decline** on this endpoint.
* `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are **informational** here — their actions default to No action and are not configurable per request.
* `FUTURE_ISSUE_DATE`, `POOR_DOCUMENT_QUALITY`, `NAME_MISMATCH_ID_VERIFICATION`, `POA_NAME_MISMATCH_BETWEEN_DOCUMENTS`, and `POA_MAX_ATTEMPTS_EXCEEDED` are never produced — a future-dated document is **not** auto-declined on this endpoint.
See the [POST /v3/poa/ API reference](/standalone-apis/proof-of-address) for the request options.
## Retry behavior (workflow sessions)
In a verification session, the POA upload step runs blocking validation before the document is accepted. When it finds a blocking error — `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_EXTRACT_ISSUE_DATE`, `FUTURE_ISSUE_DATE`, `POA_NAME_NOT_DETECTED`, `MISSING_ADDRESS_INFORMATION`, `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`, `UNSUPPORTED_DOCUMENT_LANGUAGE`, or `POA_DOCUMENT_EXPIRED` — the upload is rejected with `400` and the risk code as the message, and the user can try again with a better document.
For `POA_DOCUMENT_EXPIRED`, the `400` body also includes `expiration_reason` (`printed_expiration` or `age_window`) so your UI can show the accurate reason; on the `age_window` path it additionally includes `max_age_months`. On the `printed_expiration` path `max_age_months` is intentionally omitted, because the document was rejected for its printed expiration date rather than an age window.
Once the user has burned `poa_max_retry_attempts` attempts (default 2, configurable 2–5), the submission is accepted anyway: the stored warnings are recomputed from the saved document, `POA_MAX_ATTEMPTS_EXCEEDED` is appended, and the status is resolved from the full risk set. This is why `FUTURE_ISSUE_DATE` can block an upload yet never appears in `warnings[]`.
## Configurable settings
The workflow's POA node exposes these knobs (matching `VerificationSettings` fields):
| Setting | Controls | Default |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `poa_name_or_address_mismatch_action` | `NAME_MISMATCH_WITH_PROVIDED`, `NAME_MISMATCH_ID_VERIFICATION`, `ADDRESS_MISMATCH_WITH_PROVIDED`, `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, `POA_NAME_MISMATCH_BETWEEN_DOCUMENTS` | Review |
| `poa_document_issues_action` | `DOCUMENT_METADATA_MISMATCH` | Review |
| `poa_document_authenticity_action` | `SUSPECTED_DOCUMENT_MANIPULATION` | Decline |
| `poa_unsupported_language_action` | `UNSUPPORTED_DOCUMENT_LANGUAGE` | Decline |
| `poa_unsupported_document_type_action` | `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | Decline |
| `poa_issuer_not_identified_action` | `ISSUER_NOT_IDENTIFIED` | Review |
| `poa_issue_date_not_detected_action` | `UNABLE_TO_EXTRACT_ISSUE_DATE`, `POA_NAME_NOT_DETECTED` | Review |
| `poa_unparsable_or_invalid_address_action` | `UNPARSABLE_OR_INVALID_ADDRESS` | Review |
| `poa_max_attempts_exceeded_action` | `POA_MAX_ATTEMPTS_EXCEEDED` | Decline |
| `poa_documents_allowed` | Per-country accepted document types, each with `enabled` and `age_months` (`-1` = unlimited); per-subtype overrides via `document_subtypes_config`. | Utility bill and bank statement: 3 months; government-issued and other: 12 months |
| `poa_languages_allowed` | Accepted document languages. | All supported languages |
| `poa_name_match_score_threshold` | Minimum name-match score (0–100) before the `NAME_MISMATCH*` risks fire. | 86 |
| `poa_max_retry_attempts` | Upload attempts before the retry loop gives up (2–5). | 2 |
## Name matching logic
POA name matching tolerates common variations:
* **Middle names and initials** are matched leniently (presence/absence is allowed).
* **Candidate names** — every name detected on the document (`name_on_document` plus `additional_names`) is scored, and the best-scoring candidate is kept.
* **Match threshold** — the default `poa_name_match_score_threshold` is **86**; a score below it fires the relevant `NAME_MISMATCH*` warning.
The numeric scores are exposed on the report as `name_match_score_expected_details` and `name_match_score_id_verification` (0–100). Score values are useful for fine-grained review even when no warning has been raised.
## Examples
### Overlay manipulation (Decline by default)
```json theme={null}
{
"warnings": [
{
"feature": "PROOF_OF_ADDRESS",
"risk": "SUSPECTED_DOCUMENT_MANIPULATION",
"additional_data": {
"reason": "Overlay-text manipulation detected: same base font embedded with multiple subset prefixes on 1 page(s).",
"detection_method": "overlay_text_manipulation",
"signals": ["duplicate_font_subset", "glyph_fragmentation"],
"duplicate_font_subsets": [{ "page": 1, "base_font": "Helvetica" }],
"fragmented_fonts": [],
"manipulated_regions": [
{ "page": 1, "x": 102.4, "y": 318.1, "width": 187.0, "height": 18.0, "page_width": 595, "page_height": 842 }
]
},
"log_type": "error",
"short_description": "Suspected document manipulation",
"long_description": "The system detected signs of potential document manipulation or editing.",
"node_id": "feature_poa_1"
}
]
}
```
### Expired document + missing address (auto-decline)
```json theme={null}
{
"warnings": [
{
"feature": "PROOF_OF_ADDRESS",
"risk": "POA_DOCUMENT_EXPIRED",
"additional_data": {
"expiration_reason": "age_window",
"max_age_months": 3,
"document_subtype": "ELECTRICITY_BILL",
"document_type": "UTILITY_BILL",
"issue_date": "2025-11-02"
},
"log_type": "error",
"short_description": "Document expired",
"long_description": "The submitted document is older than the maximum accepted age of 3 months from its issue date, which exceeds the acceptable time period for validity.",
"node_id": "feature_poa_1"
},
{
"feature": "PROOF_OF_ADDRESS",
"risk": "MISSING_ADDRESS_INFORMATION",
"additional_data": null,
"log_type": "error",
"short_description": "Missing address information",
"long_description": "The document does not contain complete or clear address information that can be extracted.",
"node_id": "feature_poa_1"
}
]
}
```
### Name mismatch (Review by default)
```json theme={null}
{
"warnings": [
{
"feature": "PROOF_OF_ADDRESS",
"risk": "NAME_MISMATCH_ID_VERIFICATION",
"additional_data": {
"poa_name": "JOHN B SMYTH",
"kyc_name": "John A. Smith",
"match_score": 60
},
"log_type": "warning",
"short_description": "Name mismatch with ID verification",
"long_description": "The full name on the document does not match the name from the user's verified identity documents.",
"node_id": "feature_poa_1"
}
]
}
```
## Related
* [Proof of address report](/core-technology/proof-of-address/report-proof-of-address) — full response shape, overlay-manipulation forensics, status semantics
* [Proof of address overview](/core-technology/proof-of-address/overview) — accepted document types
* [Data models — Proof of address](/reference/data-models#proof-of-address) — canonical schema and Warning object
* [Webhooks](/integration/webhooks) — `status.updated` carries POA warnings
### Warning types
# Questionnaires
Source: https://docs.didit.me/core-technology/questionnaires/overview
Build custom KYC questionnaires with a drag-and-drop builder. Multi-language, conditional logic, manual review routing. Pay-per-call $0.10.
Didit's Questionnaires let you design structured, dynamic forms to collect additional information from users during verification. Use our visual builder to drag and drop elements, organize content into sections, localize text for multiple languages, and route results for manual review when needed.
In the Academy lesson on reading a verification result, the 11:54 chapter covers Document AI and questionnaires.
***
## Two Ways to Build Questionnaires
Didit offers **two distinct approaches** to creating questionnaires, allowing you to choose the right level of complexity for your needs:
### 1. Simple Mode: Quick Form Builder
The **Simple Mode** is perfect for creating straightforward forms quickly. Use our intuitive drag-and-drop interface to add elements, set required fields, and publish in minutes.
**Best for:**
* Quick data collection forms
* Simple surveys and attestations
* Teams new to form building
* Single-language questionnaires
**How it works:**
1. Open the Questionnaire Builder
2. Drag elements from the palette onto your form
3. Configure each element (label, required, placeholder)
4. Preview and publish
***
### 2. Advanced Mode: Visual Graph Builder
The **Advanced Mode** unlocks powerful features for complex data collection scenarios using a **visual node-based editor**. Build sophisticated questionnaires with conditional logic, branching paths, and full control over flow.
**Best for:**
* Multi-language questionnaires
* Complex forms with conditional branching
* Compliance-driven data collection with different paths
* Forms requiring manual review workflows
**Key capabilities:**
* **Visual node editor**: Drag, drop, and connect nodes on an infinite canvas
* **Smart connections**: Drag from a node handle to empty space to instantly create and connect a new node
* **Conditional branching**: Route users to different questions based on previous answers
* **Multi-language support**: Translate all user-facing content
* **Keyboard shortcuts**: Undo (Ctrl/Cmd+Z), Redo (Ctrl/Cmd+Shift+Z), Delete (Delete/Backspace)
* **Zoom and pan**: Navigate complex forms with scroll-to-zoom and drag-to-pan
#### Graph Builder Node Types
| Node Type | Color | Description | When to Use |
| ------------------- | ------ | ------------------------------------- | ------------------------------------------------- |
| **Question Nodes** | Blue | Input elements that collect user data | Text inputs, dropdowns, file uploads, dates, etc. |
| **Text Nodes** | Green | Read-only content displayed to users | Instructions, explanations, legal text |
| **Branching Nodes** | Orange | Conditional routing based on answers | "If country is US, show tax form" |
| **Section Nodes** | Gray | Visual grouping of related elements | Organize questions into logical sections |
> **Connection Rules:** Section and Text nodes can connect to Questions and other Text nodes, but not directly to Branching nodes. Branching nodes can connect to any node type.
***
## Questionnaire Templates
Templates help you get started quickly with pre-configured forms for common use cases. You can use them as-is or customize them to fit your needs.
### Pre-Built Templates
| Template | Description | Typical Elements |
| ------------------------ | --------------------------------------------------- | ----------------------------------------- |
| **Source of Funds** | Capture income sources and supporting documentation | Dropdown, Long Text, File Upload |
| **Employment Details** | Collect job title, employer, income range | Short Text, Dropdown, Number |
| **Purpose of Account** | Understand intended account usage | Single Choice, Multiple Choice, Long Text |
| **Beneficial Ownership** | Identify UBOs and ownership structure | Short Text, Number, File Upload |
| **Tax Residency** | Collect tax-related declarations | Country, Consent, File Upload |
| **Risk Assessment** | Gather risk-relevant information | Multiple Choice, Dropdown, Long Text |
***
## How it works
Our questionnaire workflow is designed for flexibility and auditability:
### 1. Build in the Console (No Code)
* Drag and drop elements to create your questionnaire
* Organize content into multiple sections for clarity
* Mark elements as required or optional
* Configure placeholders and helper descriptions
* Add choices (with optional free-text follow-up) for dropdown and single-choice
* Configure file/image upload limits per element
### 2. Localize Content
* Write your content once in the questionnaire's `default_language`
* Define the supported `languages` — when you publish, every title, description, placeholder, choice label, and validation message is **translated automatically** into all of them
* Fine-tune any translation by hand in the builder; manual edits are preserved until the source text changes
* The end-user sees content automatically in the best available language
### 3. Publish and Collect
* Activate the questionnaire and embed it in your verification flow
* End-users complete the sections and submit answers
* File uploads (documents, images) are stored and linked to the response
### 4. Review and Decide
* Responses are saved with a `status`: `Approved`, `In Review`, or `Not Finished`
* You can force all questionnaire responses to go `In Review`, so a person manually approves them
* Review answers and files in the console, then mark as `Approved`
> Note: Questionnaires do not produce risk warnings. Governance is achieved through required fields, validation, and review workflows.
***
## Key capabilities
| Capability | Simple Mode | Advanced Mode |
| --------------------------- | ----------- | ------------- |
| Drag-and-drop builder | ✅ | ✅ |
| Required/optional fields | ✅ | ✅ |
| Placeholders & descriptions | ✅ | ✅ |
| Multiple sections | ✅ | ✅ |
| File upload limits | ✅ | ✅ |
| Visual graph editor | ❌ | ✅ |
| Conditional branching | ❌ | ✅ |
| Multi-language translations | ❌ | ✅ |
| Choice logic (require text) | ❌ | ✅ |
| Force manual review | ❌ | ✅ |
| Undo/Redo support | ❌ | ✅ |
| Keyboard shortcuts | ❌ | ✅ |
**Full feature list:**
* **Visual Builder**: Create and manage questionnaires with an intuitive drag-and-drop editor
* **Sections**: Group related questions; add headers and separators to guide users
* **Automatic translations**: Write content in one language — all user-facing text is translated into every other selected language on publish, with manual fine-tuning supported
* **Validation**: Mark fields required; enforce max files per upload element
* **Choice Logic**: For dropdown/single-choice, optionally require additional text per selected option
* **Uploads**: Collect documents and images with file count limits (e.g., up to 3 files)
* **Manual Review**: Optionally force all responses to `In Review` pending human approval
* **Activation Toggle**: Enable or disable the questionnaire per application
* **Workflow Integration**: Add questionnaires to KYC or run them standalone as a Questionnaire Verification workflow
* **APIs & Webhooks**: Read structured results via API and receive updates
***
## Element types
You can add the following element types in the builder (selectable from the console):
### Input Elements
| Element | Description | Configuration Options |
| ---------------- | --------------------------- | --------------------- |
| **SHORT\_TEXT** | Single-line free text | Placeholder, required |
| **LONG\_TEXT** | Multi-line free text | Placeholder, required |
| **NUMBER** | Numeric input | Placeholder, required |
| **EMAIL** | Email input with validation | Placeholder, required |
| **PHONE** | Phone number input | Placeholder, required |
| **ADDRESS** | Address input | Placeholder, required |
| **DATE\_PICKER** | Date selection | Placeholder, required |
| **TIME** | Time input | Placeholder, required |
### Choice Elements
| Element | Description | Configuration Options |
| -------------------- | ---------------------------- | ----------------------------------------- |
| **DROPDOWN** | Single selection from a list | Choices, `requires_text_input` per choice |
| **SINGLE\_CHOICE** | Radio-style single selection | Choices, `requires_text_input` per choice |
| **MULTIPLE\_CHOICE** | Multi-select checklist | Choices |
| **COUNTRY** | Country selector | Required |
| **CONSENT** | Checkbox or acknowledgement | Required, custom label |
### Upload Elements
| Element | Description | Configuration Options |
| ---------------- | --------------- | --------------------------- |
| **IMAGE** | Image upload | `max_files` (1-5), required |
| **FILE\_UPLOAD** | Document upload | `max_files` (1-5), required |
### Layout Elements
| Element | Description | Notes |
| ------------------- | -------------------------- | ---------------------------- |
| **PARAGRAPH** | Read-only explanatory text | Not answerable |
| **SECTION\_HEADER** | Visual section header | Not answerable, not required |
| **SEPARATOR** | Visual divider | Not answerable, not required |
**Behavioral notes:**
* `SECTION_HEADER` and `SEPARATOR` are presentation-only. They ignore required/placeholder/choices/file limits.
* For choice elements, define choices as `{ label, value, requires_text_input? }`.
* For upload elements, set `max_files` (1–5) to control how many files can be attached.
***
## Forcing manual review
Some compliance workflows require human review of every response. From the console, you can set questionnaires to always result in `In Review`. A reviewer then approves or requests changes, after which the response becomes `Approved`.
**When to use manual review:**
* High-risk customer onboarding
* Sensitive data collection (Source of Funds, UBO declarations)
* Regulatory requirements for human oversight
* Quality assurance during initial rollout
***
## Example use cases
### Source of Funds
Capture income sources, amounts, and upload supporting documents (e.g., payslips, statements). Combine dropdowns, long text, and file uploads.
**Typical structure:**
* Section 1: Employment Status (Dropdown)
* Section 2: Income Sources (Multiple Choice + Long Text)
* Section 3: Supporting Documents (File Upload)
* Section 4: Declaration (Consent)
***
### Association or Membership
Record affiliations, roles, and proof of membership; include consent elements for declarations.
**Typical structure:**
* Organization Name (Short Text)
* Role/Position (Dropdown)
* Duration of Membership (Date Picker)
* Membership Proof (File Upload)
* Declaration (Consent)
***
### Purpose of Relationship & Expected Account Activity
Collect intent, expected transaction volumes, countries of activity, and anticipated counterparties. Use single/multiple choice, number inputs, and paragraphs for context.
**Typical structure:**
* Section 1: Account Purpose (Multiple Choice)
* Section 2: Expected Monthly Volume (Number + Dropdown for currency)
* Section 3: Countries of Activity (Country - multiple)
* Section 4: Transaction Types (Multiple Choice)
* Section 5: Additional Information (Long Text)
***
## Simple vs Advanced Mode: When to Use Each
| Scenario | Recommended Mode |
| ----------------------------- | ---------------- |
| Single-language, basic form | Simple |
| Multi-language support needed | Advanced |
| Quick internal survey | Simple |
| Compliance questionnaire | Advanced |
| One-time data collection | Simple |
| Conditional branching paths | Advanced |
| Force manual review | Advanced |
| Complex multi-section forms | Advanced |
***
## Getting Started
1. **New to questionnaires?** Start with **Simple Mode** to learn the builder
2. **Need translations or conditional logic?** Switch to **Advanced Mode** for the visual graph editor
> See how results are structured in the [Questionnaires Report](/core-technology/questionnaires/report-questionnaire) page.
# Questionnaire Report
Source: https://docs.didit.me/core-technology/questionnaires/report-questionnaire
Didit questionnaire report reference covering sections, items, element types, conditional visibility, and per-item answers in V3 decision responses.
The questionnaire report contains the structured form a user was shown, the path they took through any conditional branches, and the answer for each visible item. It is returned per node, so multi-instance workflows (for example, one questionnaire per UBO) carry one entry per occurrence.
## Overview
Each questionnaire response describes:
* The **questionnaire version** the user saw (title, description, languages, version metadata, sections).
* The **visible items** for the path the user actually took through the form's graph — items hidden by conditional logic are dropped from the response.
* The **answer** for every visible interactive item, in the shape that matches its `element_type`.
* A single **status** for the response as a whole.
Layout elements are reshaped rather than answered: each `SECTION_HEADER` becomes a section boundary (its title and description become the section's `title`/`description`), while `HEADING`, `PARAGRAPH`, and `SEPARATOR` stay in `items[]` without an `answer` key so the report keeps the same visual structure the user saw.
## Where it appears
The questionnaire report appears as the plural array **`questionnaire_responses[]`** in `GET /v3/session/{sessionId}/decision/`, for both KYC and KYB sessions, whenever a questionnaire feature ran. The field is `null` until at least one questionnaire instance has started. Each entry carries a `node_id` so multi-instance workflows can disambiguate which graph step produced it.
```text theme={null}
GET /v3/session/{sessionId}/decision/
──▶ { "questionnaire_responses": [ { node_id, questionnaire_id, title, status, sections, … }, … ] }
```
## Item shape
See [Questionnaire response in the Data Models reference](/reference/data-models#questionnaire-response) for the canonical schema.
```typescript theme={null}
interface QuestionnaireResponse {
questionnaire_id: string; // UUID of the questionnaire version that was answered
title: string; // Questionnaire name, as set in the Console
description: string | null;
languages: string[]; // Supported language codes
default_language: string; // Default language code
is_active: boolean;
is_simple_questionnaire: boolean; // true = flat list, false = branching graph
questionnaire_group_id: string; // UUID shared by all versions of the same questionnaire
version: number; // Version number within the group
published_at: string | null; // ISO 8601 — when this version was published
status: "Approved" | "In Review" | "Not Finished";
sections: Array<{
title: string | null; // From the SECTION_HEADER that opens the section
description: string | null;
items: QuestionnaireItem[];
}>;
node_id: string | null; // Workflow graph node that ran this questionnaire
}
interface QuestionnaireItem {
uuid: string; // Form element UUID
value: string; // Stable node id of the element within the questionnaire graph
element_type:
| "SHORT_TEXT" | "LONG_TEXT"
| "DROPDOWN" | "SINGLE_CHOICE" | "MULTIPLE_CHOICE"
| "NUMBER" | "EMAIL" | "PHONE" | "ADDRESS" | "COUNTRY"
| "DATE_PICKER" | "TIME"
| "CONSENT"
| "FILE_UPLOAD" | "IMAGE"
| "REPEATABLE_GROUP"
| "HEADING" | "PARAGRAPH" | "SEPARATOR"; // layout-only — no answer key
is_required: boolean;
title: string | null; // Localized
description: string | null; // Only populated on HEADING / PARAGRAPH text blocks
placeholder: string | null; // Localized
choices: Array<{ // DROPDOWN / SINGLE_CHOICE / MULTIPLE_CHOICE
label: string; // Localized
value: string; // Stable across languages
requires_text_input?: boolean; // Selecting this option triggers a free-text follow-up
}> | null;
max_files: number | null; // FILE_UPLOAD / IMAGE upload cap (1–5)
required_if: { // Conditional requirement rule, or null
rules: Array<{ field: string; operator: string; value?: unknown }>;
logic: "and" | "or";
} | null;
repeatable_config: { // REPEATABLE_GROUP only, or null
min_items?: number;
max_items?: number;
item_label?: string; // Localized
fields: Array