All articles

Guides

How to build a face recognition photo-sharing platform

The full stack behind 'upload a selfie, get your photos' — ingestion to delivery, built privacy-first.

By SightRadar EngineeringUpdated 13 min read Markdown

TL;DR

A face-search photo-sharing platform has five stages: ingest photos (ideally camera-to-cloud during the event), index each event's faces into an isolated collection, let guests retrieve by selfie, assemble a personal gallery from the matches, and deliver it (link, WhatsApp, or email) — then delete on a retention schedule. The face API handles detection and matching; you own storage, the event/guest data model, delivery, and consent. This guide lays out the whole architecture with the data model and the hot-path code.

Platforms like this power weddings, conferences, marathons, and festivals — the pitch is "guests find themselves in seconds instead of scrolling thousands of photos." The face recognition is one call; the platform is everything around it. Here's the whole thing.

The five stages

  1. Ingest — photographers push photos to your storage during the event (camera-to-cloud) or upload in batches after.
  2. Index — each new photo is indexed into that event's collection so its faces are searchable.
  3. Retrieve — a guest scans a QR code, uploads a selfie, and you search the event collection.
  4. Assemble — map the matched photo ids back to your stored originals and build that guest's personal gallery.
  5. Deliver — send the gallery via link, WhatsApp, or email; offer HD downloads, prints, albums.

Data model

create table event (
  id           text primary key,       -- also the collection id
  name         text not null,
  retention_at timestamptz not null,   -- delete faces + photos after this
  created_at   timestamptz not null default now()
);

create table photo (
  id          text primary key,        -- your id; passed as photoId at index
  event_id    text not null references event(id),
  storage_url text not null,
  indexed     boolean not null default false,
  face_count  int
);

create table guest (
  id           text primary key,
  event_id     text not null references event(id),
  contact      text,                   -- phone/email for delivery
  consented_at timestamptz not null    -- opted in by uploading a selfie
);
One collection per event; guests and their consent are first-class.

Stage 1-2 — ingest and index

The win everyone wants is speed: photos should be searchable within minutes of the shutter. Index as photos land rather than in one big post-event job. Use the batch endpoint for throughput and let per-photo results arrive on a webhook so indexing never blocks the guest-facing path.

import os, requests
BASE = "https://api.sightradar.com"
H = {"Authorization": f"Bearer {os.environ['SR_API_KEY']}"}

def ensure_event(event_id):
    requests.post(f"{BASE}/v1/collections", headers=H,
                  json={"collection_id": event_id})

def index_new_photos(event_id, photos):     # [(photo_id, url), ...]
    for i in range(0, len(photos), 1000):    # up to 1000 per batch
        chunk = photos[i:i+1000]
        requests.post(f"{BASE}/v1/batches",
            headers={**H, "Idempotency-Key": f"{event_id}-{i}"},
            json={"collection_id": event_id, "op": "index",
                  "photos": [{"external_id": p, "url": u} for p, u in chunk]}
        ).raise_for_status()
Index newly-ingested photos into the event's collection.

Tip: Guests upload selfies from phones, so they're HEIC; event photos from a CDN are often WebP. An API that decodes both natively (rather than JPEG/PNG only) removes a transcode stage from your hot path — see the image-formats guide.

def guest_gallery(event_id, guest_id, selfie_url):
    record_consent(guest_id, event_id)       # store opt-in before the biometric op
    r = requests.post(f"{BASE}/v1/collections/{event_id}/search",
                      headers=H, json={"url": selfie_url, "limit": 200})
    r.raise_for_status()
    data = r.json()
    if data.get("reason"):                    # no_face / low_quality — ask to retry
        return {"error": "Please upload a clearer selfie."}
    # 'auto' matches show now; 'review' matches behind a 'confirm it's you' step
    photo_ids = [(m["photo_id"], m.get("tier", "auto")) for m in data["matches"]]
    return build_gallery(guest_id, photo_ids)  # map ids -> your storage_urls
Guest uploads a selfie (consent), you search, then assemble their gallery.

Reveal high-confidence (auto) matches immediately; put borderline (review) ones behind a lightweight "is this you?" confirm so a stranger's photos are never auto-shown to the wrong guest. The score is on a 0-1 scale — calibrate your reveal cutoff on real data (how to choose it).

Stage 5 — delivery

A personal gallery is a delivery surface: send a private link, push over WhatsApp or email, and offer HD downloads and print upsells. Because each gallery is scoped to one guest, it's also your monetisation and re-engagement channel — the thing a public folder can never be.

Privacy & retention (build it in, don't bolt it on)

  • Per-event isolation — search only inside the event's collection; never across events.
  • No public index — a photo is returned only to a matching selfie, never at a guessable URL.
  • Two consents — disclose face-tagging to attendees (indexing basis) and capture the guest's opt-in at selfie upload (search).
  • Retention job — at event.retention_at, delete the collection (faces) and the stored photos; keep only an audit record of the deletion.
  • Removal on request — a person can ask to be removed before retention expires.
def run_retention(event_id):
    r = requests.delete(f"{BASE}/v1/collections/{event_id}", headers=H)
    r.raise_for_status()                       # abort + retry if API delete fails
    delete_stored_photos(event_id)
    audit(event_id, "event_data_deleted")
Scheduled cleanup — biometric data deleted first, confirmed, then photos.

What to reuse vs build

ConcernUse the APIYou build
Face detection + matching✅ index / search
Photo storage & CDN✅ S3/R2 + your URLs
Event/guest data model✅ your DB
Personal gallery + delivery✅ UI + WhatsApp/email
Isolation & scoring primitives✅ collections + tiers✅ your thresholds

Index an event and run your first selfie retrieval on trial credits.

Start the quickstart

Frequently asked questions

How do I build a photo-sharing platform with face recognition?

Five stages: ingest photos (camera-to-cloud during the event is ideal), index each event's faces into its own isolated collection, let guests retrieve by uploading a selfie, assemble a personal gallery by mapping matched photo ids back to your stored originals, and deliver it by link, WhatsApp, or email. The face API handles detection and matching; you own storage, the event/guest data model, delivery, consent, and a retention schedule that deletes data afterward.

How fast can guests get their photos?

If you index photos as they're ingested (rather than in one post-event batch) and retrieve by selfie, guests can get their personal gallery within minutes of a photo being taken. Use the batch index endpoint for throughput with per-photo results delivered to a webhook so indexing never blocks the guest-facing search path.

How do I keep a photo-sharing platform private?

Build privacy into the architecture: search only within a single event's collection (per-event isolation), never expose photos at a guessable public URL (a photo is returned only to a matching selfie), capture consent both for indexing attendees and for each guest's selfie search, and run a retention job that deletes faces and photos on a schedule or on request while keeping an audit record of the deletion.

What does it cost to run per event?

Cost is per photo processed. Indexing is a one-time cost per photo at the batch rate, and each guest's selfie search is a per-search cost — both fractions of a cent, so even a large free event is inexpensive. See the cost breakdown article for worked per-event scenarios and the calculator on the pricing page.

Keep reading