Skip to main content

FutureQuest Marketing, New York, USA

AI Identity Verification & Classification

An API-driven verification service that grades a selfie-with-ID against ten independent checks, returns a structured decision with a reason for every step, and classifies user-generated content at scale.

Status
Ongoing
Industry
Marketing Technology
Year
2025
Categories
AI, Automation, APIs
Operations dashboard showing identity verification totals, widget classification counts and AI spend broken down by day, month and year.

Turning a judgement call into a decision that can be read back

An AI verification and classification service built around strict schemas, per-step reasoning and a cost ledger, replacing manual identity review on a marketing platform.

FutureQuest runs a platform where users create and publish widgets, and where a user's age and identity genuinely matter — some of the content is age-gated, and the rules differ by state and by country. Both problems were being handled by people: staff looked at a selfie and an ID and made a judgement call, and staff read a widget and decided which category it belonged in.

Neither scales, and neither is consistent. Two reviewers looking at the same blurred licence reach different conclusions, and nobody can explain a rejection three weeks later.

What we built

An API service the main application calls. It accepts a verification request, runs the images and the extracted data through a purpose-built prompt and a strict JSON schema, and returns a decision that is broken into ten separately scored steps — grip and possession, ID legibility, date of birth, face match, the 18+ check, document type, tampering, expiry, the presence of a photo, and whether the ID was digitally inserted into the selfie.

Every step carries two explanations: one written for an operator reading the review queue, and one written for the user who is about to be told the result. That second field is the one that stops a rejection from being a dead end.

The same service handles a separate face-comparison call — profile photo against ID photo, scored across facial structure, eyes and brows, mouth and nose, and hair — and a classification endpoint that reads a widget's title, description and its author's profile and recommends the categories it actually belongs in, with a match score against the one the user picked.

Verification steps
10, independently scored
Output contract
Fixed JSON schema
AI spend
Metered per operation

The problem

Manual identity review has three failure modes and the client had all of them.

It does not scale: every submission costs a person several minutes, and the queue grows faster than the team. It is inconsistent: the same document gets different verdicts depending on who opened it and how long their day has been. And it leaves no trace worth reading — a row marked "rejected" does not tell you which of the ten things that could have been wrong actually was.

Widget categorisation had the same shape. Users pick their own category, users pick wrong, and an incorrectly filed widget is invisible to the people looking for it.

Requirements

  • Accept verification requests from the main application over an API, not a UI
  • Score each verification step independently rather than returning a single verdict
  • Return a machine-readable decision under a fixed schema, every time
  • Write one explanation for the operator and a second for the end user
  • Extract the document's data fields alongside the decision
  • Compare a profile photo against an ID photo as a separate scored call
  • Recommend widget categories with a match score against the user's own choice
  • Track AI spend per operation type, so the cost of a decision is a known number

The solution

The decision is a schema, not a sentence. Every response comes back as JSON with a fixed shape: a result block holding the ten steps, a data block holding the fields read off the document, a red_flags array, and a verification_checks summary. Consumers parse one structure whatever the outcome, and a missing field is a bug rather than a variation in phrasing.

Each step returns a status, an explanation for the operator and a user_explanation written in plain second person — "Your ID is clear and readable." The user-facing text exists so that a rejected person can be told which specific thing to fix, which is the difference between a retry and a support ticket.

Extraction runs in the same pass as the decision. Name, date of birth, document number, type, issue and expiry dates, issuing country, and the physical descriptors printed on the licence come back as structured fields, so the platform never needs a second call to learn who it just verified.

Face comparison is deliberately not a boolean. It scores facial structure, eyes and brows, mouth and nose, and hair separately, sums them, and converts that to a certainty percentage with a written conclusion. A reviewer can see which component dragged the score down.

Classification works the same way: the model is given the full category tree, the author's username and profile description, and the widget's own title and description, then returns the current category's match score plus ranked alternatives with their own scores and category IDs. A low score is a flag for review, not an automatic move.

Architecture

The platform is a Yii2 application; the verification service sits behind an API endpoint it calls, with Python handling the image and model work and PostgreSQL holding the decisions.

Requests are queued rather than answered inline. A verification is not a request-response operation — image handling and model latency put it well outside a web request's budget — so the endpoint accepts the submission and the result is written back when it lands.

Model choice is configuration, not a hard-coded dependency. The service has run against multiple model generations during its life, and the schema is what kept that cheap: as long as a model can be held to the output contract, swapping it does not touch the consuming code.

Everything runs on Google Compute Engine — Ubuntu 24.04, Apache virtual hosts — with the verification service and the main platform separated so model dependencies never enter the application's runtime.

Implementation

Most of the engineering effort went into the prompt and the schema rather than the plumbing, because that pairing is what makes the output trustworthy. A schema that permits a free-text verdict will eventually receive one.

The ten steps were chosen to be individually falsifiable. "Is this ID real" is not a question a model should be asked in one shot; "is the expiry date in the future", "is there a photo on the card", "does the ID show signs of digital insertion into the selfie" each are.

The operations dashboard was built alongside the service rather than after it. It reports verifications and classifications by day, month, year and all time, broken out by outcome — including the ones that are neither pass nor fail, such as cannot determine, manual review and low quality, each of which needs a different operational response.

Cost is tracked as a first-class metric per operation type: ID verification, profile match, widget classification, language detection and translation are each metered separately, so the unit economics of an automated decision are visible rather than inferred from a monthly bill.

Results

The service is live and has been in continuous production use, processing identity checks and widget classifications at a volume no manual queue would have absorbed — the operations dashboard shown above passed 109,000 verifications and 277,000 widget classifications in total by the time these screenshots were taken.

The outcome that matters more than the volume is the shape of the record. Every decision is stored as ten scored steps with written reasoning, so a rejection can be explained to the person it affected, reviewed by staff, or re-examined months later.

What we learned

A strict output schema is the single highest-leverage decision in an AI integration. It is what makes the model replaceable, the output testable, and the failure mode a validation error rather than a subtly wrong string.

Splitting one verdict into ten scored steps cost more tokens and repaid it immediately. It turned "verification failed" into "your date of birth is not readable", which is a message a user can act on.

Writing the user-facing explanation as a separate field from the operator's was worth doing from the start. Trying to serve both audiences with one sentence produces text that is either too vague to act on or too revealing about how the checks work.

Metering spend per operation from day one meant the cost conversation was always about a real number.

Gallery

Verification summary table with rows for passed, failed, genuine, manual review, impersonation, fictional and low quality, across today, month, year and all time.
Outcomes are counted by classification, not just pass and fail — manual review, impersonation and low quality are separate columns because they need separate responses.

Have something you want built?

Tell us what you are trying to ship. We will come back with an approach, a plan and an honest view of what it takes.