DEV Community

Cover image for Build a Fake-Profile Checker with a Reverse Face Search API (Python, ~50 lines)
Murat Can Kuru
Murat Can Kuru

Posted on

Build a Fake-Profile Checker with a Reverse Face Search API (Python, ~50 lines)

Disclosure: I'm the developer of Trace, the API used in this tutorial.

Dating apps, marketplaces, and community platforms all have the same problem: accounts built from someone else's photos.

A reverse face search API is one way to catch them. You send a profile photo, and it tells you where else that face appears on the public web.

In this tutorial, we'll build a small fake-profile checker in Python:

  1. Upload a profile photo.
  2. Wait for the face search to complete.
  3. Review the matches.
  4. Flag the profile if the same face appears under a different identity.

Face Recognition API vs. Reverse Image Search API vs. Reverse Face Search API

These three are often confused, but they solve different problems.

Face recognition and verification APIs

Services such as AWS Rekognition and Azure Face compare faces you already have.

For example:

"Is the person in photo A the same person as the person in photo B?"

They don't search the public web for other appearances of that face.

Reverse image search APIs

These look for copies or visually related versions of the same image.

If someone crops, mirrors, heavily edits, or replaces the image with another photo of the same person, traditional reverse image search may not find the connection.

Reverse face search APIs

These search for the face itself.

You provide one photo and the service searches the public web for other photos containing a similar face.

That's useful for detecting a common stolen-photo pattern:

The scammer uses photos A, B, and C.

The real person's Instagram contains photo D.

A traditional reverse image search may not connect those images.

A face search potentially can.

Get an API Key

Open the Trace API panel.

There's no traditional sign-up form. The panel generates an account for you, and you can create an API key under API.

Your key will look something like:

trk_live_xxxxxxxxxxxxxxxx




Store it as an environment variable:

`export TRACE_KEY=trk_live_xxxxxxxxxxxxxxxx`

Trace uses credit-based pricing rather than a monthly subscription.

One search uses one credit, packs start at $4 for five searches, and credits don't expire.

If you prefer managing API subscriptions through a marketplace, the same API is also available on RapidAPI.

How the API Works

Face search isn't instantaneous, so the API uses an asynchronous workflow.

The basic process looks like this:

`POST /v1/scans
       |
       v
202 Accepted
       |
       v
   scan ID
       |
       v
GET /v1/scans/{id}
       |
       v
 status = done
       |
       v
   matches[]`


More specifically:

POST /v1/scans with the image.
Receive a 202 Accepted response and a scan id.
Poll GET /v1/scans/{id} until the status becomes done or failed.
Read the results from matches[].

Each match can contain:

score
tier
platform
handle
url

There's also an important protection around credits.

If your account doesn't have enough credits, the scan is returned as locked: true.

Nothing is searched until you explicitly unlock it with:


`POST /v1/scans/{id}/reveal`

That means a script can't accidentally spend your credits simply by creating scans.

Build the Python Checker

We'll use Python and the requests library.

Install it if you don't already have it:

`pip install requests`

Enter fullscreen mode Exit fullscreen mode

import os
import sys
import time
import hashlib
import requests

BASE = "https://traceaifacescan.app/api/v1"

HEADERS = {
"Authorization": f"Bearer {os.environ['TRACE_KEY']}"
}

def start_scan(path: str) -> dict:
data = open(path, "rb").read()

# Same photo -> same key.
# A retry after a timeout won't accidentally create another charge.
idem = hashlib.sha256(data).hexdigest()

response = requests.post(
    f"{BASE}/scans",
    headers={
        **HEADERS,
        "Idempotency-Key": idem,
    },
    files={
        "image": (os.path.basename(path), data)
    },
    timeout=60,
)

response.raise_for_status()
return response.json()
Enter fullscreen mode Exit fullscreen mode

def wait_for_scan(scan_id: str) -> dict:
while True:
response = requests.get(
f"{BASE}/scans/{scan_id}",
headers=HEADERS,
timeout=30,
)

    response.raise_for_status()
    scan = response.json()

    if scan["status"] in ("done", "failed"):
        return scan

    time.sleep(2)
Enter fullscreen mode Exit fullscreen mode

def check(path: str, claimed_handle: str | None = None) -> None:
scan = start_scan(path)

if scan["locked"]:
    sys.exit(
        "No credits: the scan is locked. "
        "Top up, then POST /scans/{id}/reveal."
    )

scan = wait_for_scan(scan["id"])

if scan["status"] == "failed":
    sys.exit(
        f"Scan failed: {scan['error_code']}"
    )

strong = [
    match
    for match in scan["matches"]
    if match["tier"] in ("strong", "near_certain")
]

for match in strong:
    print(
        f"{match['score']:>3}  "
        f"{match['platform']:<10} "
        f"{match['handle'] or '-':<24} "
        f"{match['url']}"
    )

if claimed_handle:
    others = [
        match
        for match in strong
        if (
            match["handle"]
            and match["handle"].lstrip("@").lower()
            != claimed_handle.lower()
        )
    ]

    if others:
        print(
            f"\n⚠ Same face appears under "
            f"{len(others)} other account(s). "
            "Review manually."
        )
Enter fullscreen mode Exit fullscreen mode

if name == "main":
check(
sys.argv[1],
sys.argv[2] if len(sys.argv) > 2 else None
)

Run the Checker

Give it a profile photo and, optionally, the username the person claims to use:

`python check.py profile.jpg sarah.mitchell`

A result might look like:

`88  instagram  @sarahm_photo             https://instagram.com/sarahm_photo
81  x           @smitchell                https://x.com/smitchell

⚠ Same face appears under 2 other account(s). Review manually.`

The important part isn't simply that a face was found.

The important part is the context of the match.

If the same face appears under a completely different username, that may be worth investigating.

Understanding the Scores

Every match includes a score between 50 and 100 and a corresponding tier.

Tier    Score   Meaning
weak    < 70    Probably noise
possible    70+ Worth investigating
strong  80+ Very likely the same face
near_certain    90+ Extremely strong match

These scores should be treated as signals for investigation, not automatic decisions.

A strong match under a different name is a classic stolen-photo pattern.

But there are legitimate reasons why the same person may appear under multiple usernames.

Someone might:

Have multiple social accounts
Have changed usernames
Use a nickname
Appear in a friend's photos
Have an old account under a different name

So the correct workflow is:

Search → review the result → verify the context → make the decision.

Not:

Search → automatic ban.

Handling Errors

The API returns errors using application/problem+json with a stable error code.

Some common responses include:

HTTP status Code    Meaning
401 unauthenticated Missing or incorrect API key
402 insufficient_credits    Not enough credits to reveal the scan
422 invalid_image   Unsupported image, file too large, or dimensions too small
404 not_found   Invalid scan ID or deleted scan

Supported images include JPEG, PNG, and WebP.

Images must be no larger than 8 MB and at least 200 × 200 pixels.

Why the API Accepts File Uploads Instead of Image URLs

You might notice that the API doesn't accept an arbitrary image URL.

That's intentional.

An endpoint that fetches any URL supplied by a user can potentially become an SSRF (Server-Side Request Forgery) proxy.

For example, an attacker could attempt to make the server request internal infrastructure instead of a public image.

Requiring the application to upload the actual image avoids that particular class of problem.

So instead of:

`{
  "image_url": "https://example.com/profile.jpg"
}`

you upload the file directly.

Node.js Version

If you're building with JavaScript or Node.js, the same workflow is straightforward:

Enter fullscreen mode Exit fullscreen mode

import fs from "node:fs";

const BASE = "https://traceaifacescan.app/api/v1";

const auth = {
Authorization: Bearer ${process.env.TRACE_KEY}
};

const form = new FormData();

form.append(
"image",
new Blob([
fs.readFileSync("profile.jpg")
]),
"profile.jpg"
);

let scan = await (
await fetch(${BASE}/scans, {
method: "POST",
headers: auth,
body: form
})
).json();

while (
scan.status === "queued" ||
scan.status === "running"
) {
await new Promise((resolve) =>
setTimeout(resolve, 2000)
);

scan = await (
await fetch(
${BASE}/scans/${scan.id},
{
headers: auth
}
)
).json();
}

console.table(
(scan.matches ?? []).map(
({ score, tier, platform, url }) => ({
score,
tier,
platform,
url
})
)
);





The workflow is the same:

Upload → poll → retrieve matches.

Privacy and Acceptable Use

Face search involves real people, so how you use the API matters.

Trace is designed to return public URLs and matching information rather than private identity records.

According to the service's policies:

Results contain public URLs and scores, not private addresses or personal records.
Query photos are deleted after 30 days, or can be deleted immediately with DELETE /v1/scans/{id}.
Using the API to identify, locate, monitor, or harass people is prohibited.
API keys used for prohibited activity can be revoked.
People can request removal from the search index without creating an account.

Good use cases include:

Trust and safety checks on your own platform
Detecting stolen profile photos
Checking whether your own photos have been reposted
Verifying a potential match before meeting someone

A request like:

"Find out who this random stranger is."

is a fundamentally different use case and isn't what this API is designed for.

What You Could Build With This

Once you have the basic API integration working, you can take the same workflow in several directions.

For a dating platform, you could run a search when a profile is reported and send strong matches to a moderation queue.

For a marketplace, you could check whether seller profile photos appear on unrelated accounts.

For a community platform, you could let users verify that their own profile images haven't been copied elsewhere.

The architectural pattern stays simple:

`Profile photo
      |
      v
Reverse face search
      |
      v
Potential matches
      |
      v
Similarity score
      |
      v
Human review
      |
      v
Action`


The API provides the signal.

Your application should make the final decision.

Useful Links
Trace Face Search API
API Panel
API Documentation
OpenAPI Specification
Trace on RapidAPI

If you're integrating the API into a dating app, marketplace, moderation system, or another platform and run into an edge case, leave it in the comments.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)