We Built an AI Screenshot-to-Report System. The Hard Part Was Knowing When Not to Trust the AI.
How we're building a fraud-reporting experience with React, TypeScript, Supabase, and Gemini — and why human review matters more than a perfect-looking AI demo. Imagine receiving this message: "Your package couldn't b
How we're building a fraud-reporting experience with React, TypeScript, Supabase, and Gemini — and why human review matters more than a perfect-looking AI demo.
Imagine receiving this message:
"Your package couldn't be delivered. Pay €2.95 to arrange another delivery."
There's a link underneath it.
You don't recognize the sender. The website looks legitimate, but something feels wrong.
Now imagine opening a fraud-reporting website and seeing a form asking you to identify the incident category, communication channel, suspicious domain, financial impact, relevant evidence, and contact details.
For a developer, these fields probably make sense.
For someone who is worried about losing money, they might not.
That difference became an important part of what we're building at Axon OSINT.
We wanted people to report suspicious activity without first having to understand the vocabulary of fraud investigations.
And we started with something almost everyone knows how to create:
A screenshot.
But we discovered that converting a screenshot into structured information is only one part of the problem.
The more important question is what happens when the AI makes a mistake.
1. The Problem Isn't Just Extracting Text
Let's start with a simple example.
A screenshot of a suspicious message may contain several different kinds of information:
- The original message.
- A phone number or username.
- A website address.
- A payment amount.
- The name of a company someone is impersonating.
- A date or timestamp.
Those details are not interchangeable.
Consider the difference between these two statements:
Observed fact:
"The screenshot contains a link to parcel-check.example."
Interpretation:
"The link belongs to a fraudulent delivery service."
The first statement describes something visible in the image.
The second makes a judgment about what it means.
That distinction is critical.
An AI system might correctly extract a URL while being wrong about the intentions of the sender. It might also misunderstand part of a message, confuse two numbers, or suggest the wrong incident category.
In an ordinary productivity application, a slightly incorrect summary can be inconvenient.
In a fraud report, an incorrect phone number or website address can make the information significantly less useful.
So we designed the screenshot feature around a simple principle:
AI should help organize what the user provides, not decide what happened on the user's behalf.
2. Our Architecture: A Draft Before a Report
Our frontend uses React and TypeScript. Supabase handles parts of the backend, and Gemini is used for optional screenshot analysis.
The simplified flow looks like this:
User chooses screenshot upload
|
v
File and age checks
|
v
Screenshot analysis request
|
v
Gemini
Structured extraction
|
v
Normalize the result
|
v
Editable incident draft
|
v
User reviews details
|
v
Consent and submission
|
v
Report confirmation
|
v
Restricted report processing
One of the most important decisions in this flow is that the AI result does not automatically become a submitted incident.
It becomes a draft.
The user can review the extracted information, correct errors, add missing details, and decide whether to submit the report.
This keeps two very different operations separate:
Assistance: helping someone prepare information.
Submission: recording information that the person has reviewed and chosen to provide.
That separation also makes the system easier to reason about from a security and privacy perspective.
3. A Screenshot Isn't Trusted Input
The first boundary is surprisingly ordinary: file validation.
Our screenshot analysis endpoint accepts JPEG, PNG, and WebP images, with a 5 MB limit.
We validate the file type and size before sending it for analysis.
Why does this matter?
Because an upload feature is not just an image picker. It is a boundary between data supplied by an unknown user and a service that consumes external resources.
We also have to remember that everything inside the screenshot is untrusted.
A suspicious message might contain a URL, a payment instruction, or text claiming to come from a trusted organization.
We don't want the analysis process to treat those instructions as commands.
For example, if the screenshot says:
"Visit this website immediately to verify your identity."
The AI's job is to extract that message, not follow its instructions.
Our extraction prompt explicitly tells the model to work only with visible information, not invent missing values, and not open or check links.
This is an important architectural boundary.
Analyzing a suspicious link is not the same as visiting it.
For a reporting workflow, those should be separate capabilities with separate controls.
4. Structured Output Is Useful. It Isn't Proof of Accuracy.
Instead of asking Gemini to return an essay, we request structured information.
The response can include:
| Field | Purpose |
|---|---|
message_text |
Original message from the screenshot |
platform_used |
Where the message appeared |
suggested_scenario |
Possible incident category |
entities |
URLs, phone numbers, accounts, and other identifiers |
description |
Suggested explanation |
confidence |
Confidence category |
warnings |
Information the user should verify |
This structure makes it possible to map the extracted result into fields in the React form.
It also makes handling missing or uncertain information more predictable.
But a JSON response is not automatically a correct response.
A model can return perfectly valid JSON containing incorrect information.
That's why we distinguish between structural validity and factual accuracy.
A valid schema tells us the result is in the expected format. It doesn't tell us that the model understood the screenshot correctly.
What happens when confidence is low?
Our analysis layer supports three confidence categories:
highmediumlow
When the result has low confidence, the system falls back to a generic incident category rather than presenting a specific classification as reliable.
It also asks the user to verify the extracted details.
Here's a simplified TypeScript example illustrating the principle. It is not a copy of our production implementation.
type Confidence = "high" | "medium" | "low";
type Extraction = {
confidence: Confidence;
message_text: string | null;
suggested_scenario: string;
entities: Array<{
type: string;
value: string;
}>;
};
function createEditableDraft(result: Extraction) {
return {
message: result.message_text,
scenario:
result.confidence === "low"
? "other_fraud_message"
: result.suggested_scenario,
identifiers: result.entities.map(({ type, value }) => ({
type,
value,
})),
requiresUserReview: true,
};
}
Notice the last field.
requiresUserReview: true
It doesn't become false when confidence is high.
That's intentional.
A confidence label is useful information, but it should not remove the user's ability to inspect and correct the result.
5. Never Rewrite the Evidence to Make It Look Cleaner
One of the most interesting challenges in this kind of application is the difference between translating an explanation and changing the original evidence.
Suppose a suspicious message contains:
Send 250 EUR to this account.
Contact @payment_support_247.
The user might be completing the report in German, French, Ukrainian, or English.
We want the interface and generated explanations to use the selected language.
But the original message and identifiers must retain their original meaning and characters.
Imagine an automatic translation changing a username, modifying a website address, or replacing a financial identifier.
Even a tiny modification could undermine a later comparison.
Our extraction instructions therefore distinguish between:
Human-readable explanation: may be generated in the user's selected language.
Original message and identifiers: should be preserved as they appear.
The same principle applies to URLs, domains, phone numbers, and usernames.
Sometimes, what appears to be a typo is the most important detail in the entire report.
For example:
trusted-service.example
trusted-servlce.example
These strings look similar, but they are not identical.
Automatically "correcting" one into the other would destroy information that might matter.
This led us to a broader design principle:
Normalize data for comparison without silently rewriting the original observation.
That distinction is useful far beyond fraud reporting — especially in logging, compliance, investigations, and document-processing systems.
6. We Added a Path That Doesn't Require AI
It's tempting to make AI the center of every modern application.
We deliberately avoided making it mandatory.
Our reporting interface provides three entry points:
Manual entry. The user describes the incident and enters the information themselves.
Screenshot assistance. The user uploads an image, and the system prepares an editable draft.
Voice assistance. The user records or uploads audio, and the system helps turn their explanation into a draft.
These are different ways to reach the same goal.
Not everyone is comfortable uploading screenshots. Some users prefer typing. Others find it easier to explain what happened verbally.
And some people simply don't want their material processed by an external AI provider.
For that reason, the interface explains that optional screenshot and voice tools send the selected material to Google Gemini for draft extraction before the final report is submitted.
Users who want to avoid that AI processing can choose manual entry.
There is also an important distinction between screenshots and voice recordings.
A submitted screenshot can be retained as report evidence in restricted storage. The voice recording, by contrast, is used to prepare the draft and is not stored as an evidence recording in this workflow.
These differences matter.
A privacy notice shouldn't be a decorative element that appears after the important decisions have already been made.
People need to understand what happens to their information at the point where they choose to provide it.
7. The Unexpected UX Problem: The Screenshot Is on the Wrong Device
Another practical problem has very little to do with machine learning.
Imagine someone reading a fraud-reporting form on a laptop.
The suspicious message is on their phone.
How should they upload it?
A developer might download the screenshot from cloud storage, email it to themselves, or use a nearby-device sharing feature.
But requiring those extra steps creates friction.
So we built a QR-based upload flow.
The computer displays a QR code. The user scans it with their phone and uploads the screenshot from there.
The desktop then receives the analysis result and continues the report.
The user doesn't need to manually transfer the image to their computer first.
There are several interesting engineering considerations behind this relatively simple interaction.
Temporary sessions
The upload flow uses a time-limited session rather than a permanently reusable upload address.
The interface shows the remaining time and handles expiration.
Asynchronous state
The desktop doesn't assume the upload happens immediately.
The client tracks states such as:
creating
qr_ready
waiting_for_upload
analyzing
analyzed
expired
failed
These states let us communicate what is happening without leaving the user guessing.
Failure recovery
The user can retry or choose another reporting method if the session expires or the analysis fails.
This is a small feature, but it taught us an important lesson:
Reducing friction in a real user's workflow can be just as valuable as improving the AI model itself.
The best model doesn't help if the user can't conveniently provide the input.
8. Privacy Isn't a Checkbox at the End
Fraud reports may contain sensitive personal information.
They can include messages, payment details, contact information, and descriptions of potentially harmful events.
That creates responsibilities beyond ordinary form validation.
In Axon OSINT, submitted incident information is not intended to become a public social feed or a searchable collection of personal reports.
The reporting workflow uses restricted storage and backend access controls. Access to sensitive information depends on authorization and applicable access rules, not simply on whether someone has created an account.
This separation becomes especially important when information from different reports is compared.
Imagine three reports mentioning the same suspicious domain.
That repetition may be useful.
But it doesn't mean everyone searching for the domain should be able to read those three reports.
We separate the idea of a matching warning signal from access to the underlying statements and evidence.
Our public checking feature can return limited information about matches in previously reported data without opening those personal statements to the person performing the search.
And even when several reports contain the same number or website, that does not automatically prove a crime or identify the person responsible.
A repeated identifier is a signal worth examining, not a verdict.
For applications handling sensitive user submissions, this separation between source records and limited public results is an important design pattern.
9. The Submission Boundary Is Deliberately Separate
Another detail we care about is confirmation.
The system supports people submitting reports without first creating a full user account.
An email confirmation flow helps establish that a report was submitted using the stated address.
The backend also validates required acknowledgements, consent information, and report details during submission.
This matters because the application should not treat an AI-generated draft, a user-submitted report, and a confirmed report as identical.
They represent different levels of user action and confirmation.
The distinction is also relevant to downstream analysis.
For example, the documented cross-incident pattern-processing path requires the appropriate consent and confirmed report status.
This doesn't eliminate the need for careful access control or data validation.
But it reduces the risk of treating every unverified input as equally authoritative.
Good data processing starts with knowing where the data came from and what has actually been confirmed.
10. What We'd Tell Developers Building Something Similar
Several principles from this work apply to other applications using AI to turn unstructured content into structured records.
Keep extraction separate from action
A model can suggest information without being allowed to submit, publish, or act on it.
This is particularly important when errors could affect another person.
Preserve the original input
A clean summary is convenient. The original words and identifiers may be essential.
Don't make it impossible to recover what the user actually provided.
Treat confidence as a warning, not a guarantee
Confidence should influence how the result is presented, not replace verification.
A high-confidence mistake is still a mistake.
Design for failure before celebrating success
What happens if the upload fails? The AI returns incomplete information? The QR session expires?
A graceful manual fallback is part of the feature, not an afterthought.
Explain third-party processing before it happens
If user-provided content will be sent to an AI provider, make that clear before the user chooses the feature.
Privacy should be understandable, not merely technically documented.
Separate useful signals from sensitive records
Some information can help identify repeated patterns without exposing the people who provided it.
Design those boundaries explicitly.
11. The Questions We're Still Working On
Building this kind of system doesn't mean all the difficult questions are solved.
We continue to think about issues such as:
- How can we better communicate uncertainty without overwhelming nontechnical users?
- How should an interface highlight fields that deserve additional review?
- How can we make screenshot-assisted reporting easier for people with limited digital experience?
- How should systems prevent repeated but misleading reports from creating excessive confidence in a suspicious indicator?
- How can cross-incident analysis remain useful while respecting privacy and authorization boundaries?
These are product questions, security questions, and engineering questions at the same time.
And they deserve more attention than simply choosing a new model.
The Most Important Part of Our AI Feature Is Still the Human
We started with a seemingly straightforward idea:
Take a screenshot. Extract the relevant information. Help the user complete a report.
But the deeper we got into the workflow, the clearer the priorities became.
It wasn't enough to make AI capable of reading a suspicious message.
We also needed to make the result understandable, editable, and appropriately restricted.
The system needed a way to express uncertainty.
It needed a fallback when automatic processing failed.
And it needed a clear boundary between an AI suggestion and an action taken by the user.
We are building this as part of Axon OSINT, a platform focused on digital investigations, open-source intelligence, and responsible fraud reporting.
For developers, the most useful takeaway is one that applies well beyond our project:
Don't measure an AI-assisted workflow only by how impressive its output looks. Measure it by how safely and clearly it behaves when the output is wrong.
That's where an interesting demo begins to become a dependable product.
Discussion: If you're building an application that turns screenshots, documents, or audio into structured data, how do you handle uncertainty and human review? Which mistakes are unacceptable in your domain?
Project context: Axon OSINT · Public fraud-reporting workflow
Editorial disclosure: This article was prepared with AI writing assistance, with technical details checked against the project's implementation and documentation.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.