Notable changes to the platform, the public API, and the SDKs are recorded here. Entries are added as releases ship.
The ID scan API refuses a scan the wallet cannot pay for, before it reads the document
Section titled “The ID scan API refuses a scan the wallet cannot pay for, before it reads the document”A live POST /v1/id-verification call is refused when the wallet cannot cover one scan. The response is 402 payment_required. The document is not read or stored, and nothing is charged. Before, any positive balance let the scan run, and a scan the balance could not cover was not charged. Test mode is unchanged.
The refusal is not stored for your Idempotency-Key. Top up, then send the same request again with the same key, and the scan runs.
A refused call skips the request checks. When the wallet cannot cover a scan, a request that has other errors also returns 402, not 400, 415 or 422.
A retry of a scan that succeeded can be refused. If you retry a successful call with the same Idempotency-Key after the balance drops below the price of one scan, the retry returns 402, not the stored result. Top up and retry: you get the stored result, and you are not charged again.
The ID scan API reads two-sided documents from the front alone, and reports partial reads
Section titled “The ID scan API reads two-sided documents from the front alone, and reports partial reads”POST /v1/id-verification now reads a two-sided document from front alone. Before, a front-only upload of a driver’s license returned 422 document_unreadable, even when the front was clear. back is optional for every document. Without it, the fields are read from the front, and the response is 200 with data_match of NOT_PERFORMED, because there was no back to cross-check against.
A front-only read is charged. A front-only scan that returns 200 is charged like any other readable scan. It used to return an uncharged 422.
A partly read document returns 200 with recognition_status of UNCERTAIN. The response carries the fields that were read and field_issues, one entry per response field that the scan could not fully read. The entry’s code says whether a value is returned for that field. field_issues can be empty when the gap was in a part of the document the API does not return, so treat every UNCERTAIN read as not fully verified. Each entry is a field, a code and a message. The codes are not_read, printed_text_unreadable, invalid_characters and unexpected_field, and they are defined on the endpoint’s response schema. A partial read is charged in live mode. processing_status is PARTIAL when the scan flagged particular fields and SUCCESS when it did not flag any itself, but an UNCERTAIN read can list fields in field_issues either way, so always act on recognition_status and field_issues. The issues are written for your own team, so give the person who submitted the document a generic “retake the photo” message instead. An UNCERTAIN read is not a verified identity, so do not base a solely automated adverse decision on it. A field that the scan found where the document type does not have one is reported as unexpected_field, and its value is not returned. Before, a partly read document returned an uncharged 422.
In test mode, expected_outcome: review now returns a field issue. It returns UNCERTAIN with one not_read issue on date_of_expiry, so you can build against field_issues with a test key. pass, fail and doc_quality keep their status and return no field issue. Test mode still returns the same synthetic result whatever sides you send, so it does not show NOT_PERFORMED. See The standalone ID-scan endpoint.
A 200 with no name, date of birth or document number is now an uncharged 422 document_unreadable. Every 200 carries at least one of them, whether recognition_status is SUCCESS or UNCERTAIN.
A back that you send but that cannot be read still fails the scan. The response is 422 document_unreadable, even when the front is clear. Capture the back again or leave it out.
issuing_country and issuing_region now carry ISO codes. issuing_country is an ISO 3166-1 alpha-2 code such as US. issuing_region is an ISO 3166-2 code such as US-PA, or null for a document issued nationally. Before, they carried internal values. This is a format change for any integration that parsed the earlier values.
The document_unreadable message text changed. It now names the next step for the front and for the back. Key on error.code, not on the message. See document_unreadable.
A retry does not re-read the document. Within 24 hours, a byte-identical retry with the same Idempotency-Key replays the earlier 422, and a retry whose multipart body was rebuilt gets 409 idempotency_conflict, because a new multipart boundary counts as a different body. Either way, send a new Idempotency-Key to scan again, for example after you start sending front-only uploads.
The API says why a verification needs a reviewer, and serves the back of the ID
Section titled “The API says why a verification needs a reviewer, and serves the back of the ID”Retrieving a verification now returns the verdict and the reasons for a review. With the sessions:read scope you already use, GET /v1/sessions/{id} now carries:
outcome, the verdict:approved,declinedorreview, andnulluntil one exists. The create response and each list item carry it too.decided_atanddecision_source: when the decision was recorded, and whether a reviewer or the platform made it.decision_sourcecan be absent on a decided verification, for example when the applicant refused to sign, so an absent value does not mean no decision was made.review.causes: why the verification was sent to a reviewer. Each cause is acodeand amessage, with arule_namewhen one of your own rules sent it.reasonson each entry ofcheck_results: why that check needs a person, was only partly performed, or could not produce a result.
Every code, its category and its message are at Review reasons. The reasons and causes are written for your own reviewers. Do not show them to the person being verified, and do not give them to that person as the reason for a decision.
Keys you already created get these fields now, and so does an AI assistant you already connected. Nothing changes on your side for that to be true, and no key or assistant is asked to consent again. An assistant’s get_session tool returns the session resource, so it now sees the verdict, the causes, your rule names and the count of matched records.
GET /v1/sessions takes two filters. status and outcome each take one value, and they combine with AND. status=awaiting_review is your review queue, and each item now carries outcome and, for a verification that was sent to review, review.causes. See Track verification status.
This changes the answer to a request that already sent one of these parameters. The list used to ignore status and outcome and return every verification. It now filters by them. A value that is not in the list, or a filter sent more than once, returns 422 validation_error, with param naming the filter. limit and starting_after behave as before.
The order of a re-verified check is now defined. A check that was re-verified appears once per run in checks and check_results. The latest run now comes first. For a re-verified ID verification step only the latest run carries reasons; an earlier run of any other check type keeps its own. The order of those runs was not defined before.
The park webhook carries the codes. kyc.session.manual_review_requested gains an optional review object: the cause codes, and each check that has reasons with its reason codes. It carries no sentence, no rule name, no count and no personal data. It is a snapshot of that park, so fetch the verification for its current state. An event stored before this release keeps its original shape when it is retried or replayed. See Create a webhook endpoint.
A verification can send more than one park event. A verification can be parked more than once, for example after a re-verification. Each park now sends its own event, with its own X-Webhook-Id and its own delivery id. Before this release, a second park of a re-verified verification could be dropped as a duplicate of the first.
review after a decision. review stays on a decided verification that its checks, rules or findings sent to review. A verification parked only after a processing error has no review once it is decided.
erroredReason is removed when a verification runs again. A verification that stopped on a processing error carries erroredReason in metadata. The key is now removed when the verification runs again and reaches a new result. A verification that is re-verified and sent to review again is then an ordinary review, with no processing_error cause, in the console’s Needs review filter. Before, the key stayed, and the console still showed that verification as stopped on the earlier error. A verification that was re-verified and finished before this release keeps the key until it runs again. If that earlier re-verification sent it to review, it still has the processing_error cause and is still left out of the Needs review filter until it runs again or is decided. See Session metadata.
The evidence-image endpoint serves the back of the ID. GET /v1/sessions/{id}/idv-image/{artifact} now serves four values: portrait, front, selfie and back. back is the back of a two-sided document, and available_images lists it last when one is stored. A single-sided document has no back, so asking for it returns 404 artifact_not_available. The scope, the rate limit and the activity-log entry are the same as for the other images. See Evidence images.
Keys that carry sessions:read_report can retrieve the back as of this release. We did not mint a new scope. If that is not what you want for a particular key, create a replacement without the reports scope and revoke the old one: see Rotate or revoke an API key. The scope’s sensitivity note in the catalog now names both sides of the ID.
Test-mode image requests return a placeholder. In test mode, a front, back or selfie that available_images lists now returns one small, plain placeholder JPEG instead of 404 artifact_not_available. portrait is unchanged. The placeholder is served by the API only: the reviewer screens and the PDF report still show no front, back or selfie image for a test verification.
Your reviewers see why a verification is waiting. The review list now states it when one of your rules sent the verification, when rules you set to influence the review decision did, or, when nothing is flagged, that a reviewer’s decision is needed. Before, a verification sent to review only by one of your rules read “Nothing needs your review”. The workspace reviewer now shows the notice for a verification parked after a processing error, as the console does. Neither one says that nothing needs review on such a verification. See Manually review a verification.
The record of a decided verification states its causes. After a decision, the verification’s record states the same reasons in the past tense. A verification one of your rules sent to review no longer reads as decided against a clean set of results.
A flagged ID check no longer claims records came back. The reviewer screens no longer say that records came back when the only thing attached to an ID check is its own document result.
You can connect Track to start identity verification automatically
Section titled “You can connect Track to start identity verification automatically”There is a new integration: Track. Connect it once, choose the workflow it runs, and a qualifying reservation Track creates or updates starts a verification for the applicant, without adding them by hand or writing to the API yourself.
Track has no webhooks, so the integration checks Track for new and updated reservations on a schedule instead of reacting the instant one changes: a reservation is typically picked up within about 5 minutes. The integration only reads a reservation that changes after you connect, including one that already existed in Track; one you never touch again is not read, however old it is.
Track has no custom fields on reservations, so the integration writes the verification status back as a managed tag, and the applicant’s verification link and an internal review link back as two private notes, rather than custom fields.
Track also supplies all five session metadata keys (start_datetime, end_datetime, amount, currency, and location) when the reservation carries them in a form the platform can read, so a document a workflow signs can print the transaction details for a verification Track starts. No other integration supplies the amount, the currency, or the location. A later change to the reservation in Track does not change the values a verification already started with. See Documents applicants sign for what those variables do.
Full detail, including how to create a Track server key and what the status tag and notes mean, is at Track integration.
A signed document can print transaction details, and metadata gains reserved names and typed fields
Section titled “A signed document can print transaction details, and metadata gains reserved names and typed fields”A document a verification asks the applicant to sign can now print five facts about the transaction the verification covers: Start date and time, End date and time, Transaction amount, Currency, and Location. You supply them in the metadata object on POST /v1/sessions, under the keys start_datetime, end_datetime, amount, currency, and location. Full detail is at Verification metadata, and the authoring side is at Documents applicants sign.
This narrows a contract, so read the rest of this entry before you upgrade. The metadata object accepted any key with a scalar value. Seven key names that were accepted before this release are now reserved and always return 422 validation_error. Five other key names are still accepted, but they now carry a shape and are refused when the value does not match it. Everything you already stored is unchanged, and reads still return it. Only new writes are refused.
Five key names now carry a shape. They are optional, and a request that omits them is unaffected. A request that sends one in the wrong shape is refused.
start_datetimeandend_datetimeneed ISO 8601 with an explicit offset, such as2026-03-14T15:00:00-07:00. A date alone ("2026-03-14") and a bare local datetime ("2026-03-14T15:00:00") are both refused.end_datetimemust fall at or afterstart_datetime. The offset you send is the time zone the document prints. An unlabeled value would read as the wrong calendar date on either side of midnight.amountis a whole number in the minor units ofcurrency, so125000withusdprints $1,250.00. A decimal string ("1250.00") is refused. Sending major units is not refused and cannot be:1250withusdis a valid request that prints $12.50. Check this one against a real document before you ship it.currencyis a three-letter ISO 4217 code. A currency name ("US Dollars") is refused. Your capitalization is preserved and applied at render.locationis one display line of 500 characters or less. Angle brackets, control characters, and bidirectional marks are refused, because this value reaches a PDF and a rich-text editor."<b>Suite 4</b>"is therefore refused.amountrequirescurrency. Acurrencyon its own is fine.
Six key names are reserved for platform use, and one internal name joins them. notes, referenceDetails, operatorNotes, cancellationReason, manualReviewReason, and erroredReason are refused. Rename any of them, for example notes to your_notes. The platform stored these and read them afterward. A value you wrote under operatorNotes appeared to a reviewer as a note from your own team. A value you wrote under erroredReason reached the boundary that decides how a stalled verification is shown. That is why they are now refused rather than tolerated. The seventh reserved name, __requested_checks, was accepted and then discarded, so nothing sent under it was ever readable. Search your integration for all seven.
All three of those rules report error.param as metadata. param is derived from the first segment of the issue path, so it cannot tell them apart. Branch on details.issues[].path, which carries the full path such as ["metadata","currency"], and show the issue message, which names the key at fault.
A document that places one of the five makes the matching key required at create. A verification created without it is refused with 403 esign_requirements_not_met and details.reason: "esign_session_metadata_required", and the message names both the variable and the key to send. Nothing is created, and no applicant is invited. This is the one signing refusal a workflow save cannot catch, because the value comes from the individual create call and no saved template knows it, so a workflow that uses these variables saves normally.
A verification started by a connected system carries only part of the set. No integration supplies amount, currency, or location, so a document placing those refuses every verification an integration starts. Where the two datetimes are supplied they are calendar days at local midnight, so the document prints a bare date and no clock. Verification metadata covers what each integration supplies.
Screening rules are unchanged. session.metadata.<key> on a rule condition still resolves the workflow template’s metadata, not this object.
You can retrieve a verification’s ID and photo images over the API
Section titled “You can retrieve a verification’s ID and photo images over the API”There is a new endpoint that returns the images a verification collected, one at a time: GET /v1/sessions/{id}/idv-image/{artifact}. It serves three values. front is the front of the identity document as the applicant submitted it. selfie is the selfie or liveness frame, when the workflow ran a face check. portrait is the photograph of the document holder, cropped out of the document image. See Evidence images.
Keys you already created can do this now, and you do not have to change anything for that to be true. The endpoint is authorized by sessions:read_report, the same scope that downloads the PDF report. We did not mint a new scope, so any key already carrying that scope can retrieve these images as of this release, and so can any team member who can already open a verification in the console. If that is not what you want for a particular key, create a replacement without the reports scope and revoke the old one: see Rotate or revoke an API key. The scope is marked sensitive in the catalog at Create an API key, and the reason it is worth reading twice is that these images are biometric personal data in most jurisdictions, which is a different category from the text the report already contained.
Verifications recorded before this release have no cropped portrait yet. The crop is produced while the document is being read, so it is not created for a verification that was already recorded, and nothing reconstructs it from stored data. Asking for one that does not exist returns 404 with the code artifact_not_available, which is a normal answer and not a fault. Treat it as “use another image”: front is present for every verification that collected a document. Distinguish it from a plain 404, which means the verification id is wrong or belongs to another mode or region.
If you specifically need a portrait for an older verification, re-verify the identity step. A re-verification reads the document again, and that run produces a crop like any other, so available_images starts listing portrait for that verification once it completes. You can do this from the verification in the console, up to 20 times per verification. It asks the applicant for their document again, so it is worth doing when you need the crop, not as a bulk backfill.
Retrieving a verification now tells you which images exist before you ask for them. GET /v1/sessions/{id} gains an idv object carrying available_images, so you can request only what is there instead of probing for it. The same object carries capture_acquisition, which records whether the applicant photographed the document or uploaded a file. Three things about that field are worth stating plainly: it is absent as null for verifications recorded before we started recording it, which is not the same as “we do not know how this one was captured”; it is declared by the applicant’s own device and can be forged, so it is advisory; and it is never an input to a verification’s outcome. It is there to inform a human, not to gate anything.
Rate limit and billing. These reads are limited to 120 requests a minute on every plan, counted separately from other reads: see Rate limits. They are not charged per call, but they are subject to the same billing state as the rest of the API, so a wallet with no balance or a subscription past dunning returns 402 or 403 rather than the image.
Custom form rules now ignore capitals and accents
Section titled “Custom form rules now ignore capitals and accents”A rule on a custom form screen compares the applicant’s answer to the value you wrote. Every condition now ignores capital letters and accents when it does that: a rule looking for cafe matches an answer of CAFÉ, and a rule looking for alice@acme.test matches an answer of Alice@Acme.test.
Three conditions used to compare exactly and no longer do. equals, does not equal, and is one of treated urgent and URGENT as different answers, while contains, does not contain, and ends with already ignored the difference. Two rules on one question could disagree about the same answer for that reason, and they no longer can. The editor states it under the Condition list wherever it applies.
This applies to verifications already recorded, not only new ones. A form’s rules are re-run over the answers stored with it every time someone opens the verification or generates its report, so a rule that did not match when the applicant submitted can match now, and one that did match can stop. Nothing in your data was rewritten and no rule was edited.
What that changes on a recorded verification is the reason, not the outcome. A form that was routed to review stays routed to review, and one that passed stays passed. What can change is which questions are named as the trigger under Needs your review and in the report. Where a form was flagged and its rules now name nothing, the card says the question that triggered it cannot be determined from the stored record, rather than showing an empty list.
One limit worth knowing. Only an accent written as a mark over a plain letter is ignored. A letter that is its own letter is not: ø, ł, đ, and ß still do not match o, l, d, and ss, so write a rule for each spelling you want to match. Spaces are not ignored either: a stray space at either end of your value keeps a condition from ever matching, or makes a does not condition match every applicant.
Screening rules are unchanged, and nothing moved on the wire. A workflow template can carry both custom form rules and screening rules; this changed only the custom form ones, and a screening rule’s eq, contains, and in still compare exactly. No request or response shape changed and no operator value was added, so a generated client needs nothing.
A verification can now ask the applicant to sign a document
Section titled “A verification can now ask the applicant to sign a document”There is a seventh check type, esign. It has the applicant read a document you wrote and sign it by typing their name. The signed copy is sealed as a PDF with a certificate of completion, and everyone named on it receives a copy.
You author documents under Documents in the console, next to Workflows, and publish an immutable version. A published version is never edited, which is what lets a signed document be checked years later against exactly the text that was signed. A document is filled in from the details the verification already collected, using a fixed set of variables: your organization and authorized signatory, the applicant’s identity information, and the data read off their ID document. See Documents applicants sign.
Two new webhook events. kyc.session.document_signed fires when a document is signed and the sealed copy is ready to verify. kyc.session.document_declined fires when the applicant declines, which is a normal outcome and not an error. Both carry document identifiers, never applicant personal data. Payload shapes are at Create a webhook endpoint.
In the Web SDK, @checktiv/sdk-web/esign renders the step. The signing surface is a hosted frame, so it needs no stylesheet of yours, and esignCopy translates every string in it. See Modules.
Anyone holding a signed PDF can check it themselves, with no account and no API key, by submitting its SHA-256 to a public endpoint. The address is printed inside every certificate. What these signatures are, what we do not claim about them, and what rights a signer has are published at Electronic signatures.
Pricing. E-signature is available on every plan. It is charged per check on Basic and Pro and included at no per-check charge on Max. The charge lands when the applicant submits the step, whether they signed or declined; an applicant who opens the document and never submits is not charged.
Custom form rules can compare, not just match
Section titled “Custom form rules can compare, not just match”What this entry says about capitalization is no longer current. It records the behavior as it shipped in this release. Every condition now ignores capitalization and accents, including equals, does not equal, and is one of: see Custom form rules now ignore capitals and accents.
A rule on a custom form screen could ask three things of an answer: equals, does not equal, and is one of. It can now ask ten, though no single question offers all of them. A number or date question gains is greater than, is at least, is less than, and is at most. A text question gains contains and does not contain. An email address or phone number question also gains ends with, so a rule can match a company email domain or the last digits of a phone number.
Which conditions a question offers depends on its type, and the editor offers only those. An ordered comparison needs an order, which only a number and a date have; a substring test needs text, which a number, a date, and a choice do not have. A single choice, multiple choice, or yes/no question keeps the three conditions it always had. See Choose the right check types for the full table.
Conditions do not all treat capitalization the same way. contains, does not contain, and ends with ignore case, so a rule looking for urgent also matches an answer of URGENT. equals, does not equal, and is one of compare exactly, so the same rule written with equals does not match. Two rules on one question can disagree about case for that reason, which is easy to miss when you are reading them side by side. The editor states which behavior applies as you pick a condition.
If you read workflow templates over the API, check how your client handles op. A custom form rule’s when.op now accepts and returns seven more values: gt, gte, lt, lte, includes, not_includes, and ends_with. A client generated from an older spec may close that enum and reject a value it does not recognize, and a client that switches on op with no fallback branch will fall through on one. Regenerate against the current API reference, or add a fallback.
Custom form rules and screening rules are separate rule systems, and this changes neither one’s relationship to the other. A workflow template can carry both, and both travel in the same request body, but their operator vocabularies are independent and always have been. The substring test is includes on a custom form rule and contains on a screening rule, and they do not agree on capitalization: includes ignores case, contains matches exactly. gt, gte, lt, and lte differ too. On a custom form rule they compare numbers and YYYY-MM-DD dates; on a screening rule, numbers only. Read a rule’s operator against the rule system it belongs to rather than assuming one table covers both.
This will not surprise you spontaneously. Nothing was backfilled and no stored rule changed, so a workflow template you already read returns exactly what it returned before. A new value appears for the first time only after someone in your organization authors a rule that uses one, in the console or through the API. Writes are unaffected in either direction: eq, neq, and in behave exactly as they did.
Applicant names are sent in parts
Section titled “Applicant names are sent in parts”The applicant name is now one shape everywhere: family_name (the family name or surname), given_names (an ordered array), and name_suffix for a generational suffix such as Jr. or III.
A surname of several words stays ONE family_name value: family_name: "Garcia Lopez". A middle name is simply another entry in the array: given_names: ["Jose", "Maria"]. There is no middle-name field, because there is no fixed number of given names. A person with one name sends family_name with an empty given_names array, which is a complete answer rather than a missing one.
Breaking: five fields were removed from the applicant and verification REQUEST bodies. first_name, middle_name, last_name, legal_name and full_name are no longer accepted on POST /v1/applicants, PATCH /v1/applicants/{id}, or the inline applicant object of POST /v1/sessions. Sending one returns a 422 validation_error naming the replacement and its shape.
| Old field | Send instead |
|---|---|
first_name |
given_names, an array: given_names: ["Jose"] |
middle_name |
another entry in the same array: given_names: ["Jose", "Maria"] |
last_name |
family_name |
legal_name and full_name |
family_name with given_names |
Check your read path too. first_name, middle_name and last_name were also removed from the applicant RESPONSE. On the REST API a stale write is a loud 422, but a stale read is silently undefined, so a client that still reads applicant.last_name gets no error and no value.
Two fields are NEW on the applicant response, both read-only: legal_name, the verbatim name as captured from the applicant or from a scanned document, and localized_name, that name in its original script when a scanned document provides one. Neither is accepted on any request shape.
In the Web SDK, a stale name key is dropped with a console warning, not rejected. collectUserInfo().submit() puts only legalName, familyName, givenNames, nameSuffix, email, phone, dateOfBirth and address on the wire. firstName, middleName and lastName are not among them, so a JavaScript host that still passes one gets a console warning naming the discarded key. The submission still succeeds, no error is raised, and the value is never stored. TypeScript hosts get a compile error when the object is passed inline, but not when it reaches submit() through a variable or a spread. Search your integration for those three keys rather than relying on a failed request to find them.
Why one shape. A verification that runs a background check reads the family name and the given names as separate values. A whole name is never split into them, because guessing which words are the surname screens a different person. Offering a single undivided name as an alternative meant offering a shape that could not complete the checks you configured.
Breaking: a name is required in parts for background checks. In the Web SDK, collectUserInfo().describe() now reports nameComponents as 'required', 'requested' or 'optional' for the verification in front of you. Build your form from it, and never hide the family-name and given-names inputs. A submission carrying only legalName is rejected with name_components_required when the verification runs a background check.
Not affected: identity verification, custom forms, and sanctions screening, which reads a verbatim legal name directly. legalName on its own remains valid for all three. A person with one name is accepted everywhere.
describe().captureStructuredName is deprecated in favor of nameComponents and still ships unchanged for one release. Do not branch on it: it reports what the operator declared, never what the server enforces, and its default is false, so it reads false even on a workflow whose background check cannot run without the components.
The name fields also carry explicit bounds now. An empty family_name is a 422 rather than an accepted blank, and given_names has a per-entry length limit and a maximum number of entries.
Your metadata keys round-trip exactly as sent
Section titled “Your metadata keys round-trip exactly as sent”metadata is opaque customer data, and its keys are yours. A bag sent as {"orderId": "abc"} came back as {"order_id": "abc"}, and a bag carrying both spellings lost one of them. Both are fixed: keys are returned byte-identical to what you sent. If you built a workaround for the old behavior, remove it.
The same fix restores camelCase keys on the workflow-template document object across the template endpoints, and stops BCP-47 locale keys (en-GB) being mangled.
GET /openapi.json serves a valid document
Section titled “GET /openapi.json serves a valid document”The published OpenAPI document was structurally invalid: its own keywords were being rewritten (requestBody to request_body, securitySchemes to security_schemes) and every $ref dangled, so generated clients did not build. It is now valid and its references resolve.
Phone is normalized consistently on every applicant ingress
Section titled “Phone is normalized consistently on every applicant ingress”The inline applicant on POST /v1/sessions now validates and normalizes phone to E.164, matching POST /v1/applicants and PATCH /v1/applicants/{id}. Send an E.164 value (+12025550199), or send a national number together with an Accept-Language header so the country can be resolved. A bare national number with no country hint is rejected rather than stored under a guessed country.
API key permission scopes, simplified
Section titled “API key permission scopes, simplified”The permission scopes you grant an API key are now fewer and clearer, grouped by what they control (verification, session tokens, reports, adjudication, configuration, compliance and billing). Every scope now has a plain-language description in the console and in Create an API key, and scopes that carry extra risk (full applicant data, the full-evidence report, skipping a verification step) are called out where you grant them.
A handful of scopes were renamed or combined as part of this change, and a few unused ones were removed. If you use API keys, check the scope names you grant against the current list and re-issue any key that still references an old name; the removed scopes were not gating anything. See Choosing scopes for your key for guidance on common integration patterns.
Identity document verification - biometric face check
Section titled “Identity document verification - biometric face check”The id_verification check now supports an optional biometric face
check. Configure it per workflow template with the Face match control:
- Selfie: a single selfie matched 1:1 against the document photo.
- Active liveness: a guided face scan (movement-based, no flashing lights) that confirms a real, present person before matching to the document.
The biometric check is strictly opt-in and the modes are mutually exclusive; the default stays document-only. Active liveness is processed within your region, with EU streams pinned to the EU region.
Reviewers can now view the captured identity-document images (front, back, and the biometric image) on the applicant detail page. Every view is authorized and audited, images are streamed through the console with no shareable link, and a deleted image shows an “Image removed” state.
See Choose the right check types, What the applicant sees, and Biometric data.