Security
The Retail Digitals Image API defends product photography — a substantial commercial asset — against scraping, redistribution, and unauthorized reuse. This page documents every layer of that defense so you can integrate confidently and audit our controls.
Threat model
We assume attackers have:
- Network visibility (they can see your requests going over the wire)
- Access to a legitimate API client's endpoint output (they run one of your customers' POSes)
- Access to your public-facing web pages that consume the API (they view source)
- Access to a signed image URL you've delivered to a browser
- Time and patience to enumerate barcodes
We do not assume attackers have your api_key (that's a credential leak, not a threat we
can architect around — see credential hygiene below).
The 10-layer defense
Layer 1 — HTTPS only, HSTS enforced
All API traffic is TLS 1.2+. HTTP requests get 301 Moved Permanently to HTTPS with
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Plain-text is not
an option — even for the health check.
Layer 2 — Two-step credential exchange
Your api_key — the long-lived secret — is only sent on POST /auth/token. Every other call
uses a short-lived (1-hour) JWT bearer token. A leaked bearer becomes useless in an hour;
a leaked api_key is a bigger deal but is only touched by your server, not your customers'
browsers or POSes.
Layer 3 — JWT signed with HS256
Access tokens are HMAC-SHA-256 signed. Rotation of the signing key invalidates all outstanding
tokens across the platform. Standard claims: iss, sub (=client_id), iat, exp, jti.
Custom claims: tier, scope_hash (proof of scope, checked against DB).
Layer 4 — Refresh token rotation + reuse detection
Refresh tokens are single-use. Every POST /auth/refresh returns a new refresh token; the
old one is marked consumed. If a consumed refresh token is presented again, we treat that as a
confirmed compromise: all refresh tokens for that client_id are revoked immediately,
your api_key is quarantined, and the client owner is notified via email.
This means one leaked refresh token cannot silently be used by an attacker in parallel with you — the moment either party refreshes, the other one's next call fails hard.
Layer 5 — Signed image URLs
Image bytes never come from a guessable path. Every URL returned in images.* is:
- HMAC-signed with a server-only secret
- Time-limited (15 minute TTL —
expclaim in token) - IP-bound — must be consumed from the same IP that received the URL
- Single-use — after the first successful
GET, thejtiis added to a blacklist and further requests returnHTTP 410 Gone - Client-tagged —
client_idis embedded in the token, so we can attribute any leaked image back to which client leaked it
Do not cache signed URLs on disk or in shared cache. They're designed to be one-shot.
Because the token encodes your client_id and a per-request jti, anywhere the
URL ends up is anywhere your client identity ends up. In practical terms:
- Never write signed URLs to application logs, error reports, or crash dumps
- Never send them to third-party analytics (they end up in query-string logs)
- Never include them in Sentry / Datadog / New Relic events — log the
barcodeandvariantinstead, then reconstruct the URL if needed for debugging - Never persist them past the request that generated them
The URL is safe as a one-shot fetch instruction to the caller who requested it, not as a durable reference. Treat it as a bearer token, not a link.
Layer 6 — Inline watermarking
Every image delivered through the API carries a two-layer watermark:
- Visible watermark — global brand overlay (semi-transparent, placed within central 30% of the image, ≤15% of image area) — same watermark shown in the standard download flow.
- Invisible traceable overlay — a per-
client_idsteganographic pattern encoded into the least-significant bits. If an image shows up publicly on a scraper site, we can decode it and identify the leaking client.
Both watermarks are applied at delivery time, not at storage time — so if a client is later revoked, previously delivered watermarked images still trace back to that client.
Layer 7 — Per-client IP allowlist (optional)
Enterprise and Standard clients can pin their API client to a set of source IPs / CIDRs.
Requests from other IPs return HTTP 403 client_ip_not_allowed. Recommended for
server-to-server integrations where the IP is stable.
Layer 8 — Scope-limited catalog access
Every client has a scope filter that constrains which barcodes it can see. Requests for
out-of-scope barcodes return HTTP 403 out_of_scope — the response never confirms whether the
barcode exists, so scope also prevents catalog enumeration.
Default scope for new Standard clients: full catalog. Admins can set brand-limited or category-limited scopes for tenants.
Layer 9 — Rate limits + anomaly detection
See Rate limits. Beyond static limits, our anomaly detector alerts us to unusual patterns: sequential barcode scans, unusual geographic sources, bursts after a long idle period. Confirmed abuse gets the client auto-disabled with owner notification.
Layer 10 — Audit log
Every request — successful or failed — is logged with request_id, client_id, endpoint,
barcode (if applicable), IP, and result. Logs are retained 90 days and are queryable by
account owners via /usage. Auth events (token issued, refreshed, revoked, reused, blocked
by IP) are retained longer and available on request for security investigations.
Credential hygiene
The single biggest risk to your API integration is a leaked api_key. To protect yours:
- Never embed
api_keyin mobile apps, browser JS, or anything shipped to end users - Never commit
api_keyto git. Use.env, Kubernetes secrets, HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, or equivalent - Never log
api_key— even in DEBUG mode - Rotate
api_keyat least yearly, or immediately if you suspect a leak - Prefer one client per service — if a leak happens, blast radius is contained to that service
- Prefer IP allowlisting where feasible
If you suspect a leak:
- Log into your account panel
- Go to API Keys → find the client → click Revoke
- Issue a new client, update your service, redeploy
- Email api@retaildigitals.com with the old
client_idso we can retain forensic logs for your investigation
Reporting a vulnerability
Found a security issue? Please do not open a public GitHub issue or post on Twitter/X. Email security@retaildigitals.com with:
- Description of the vulnerability
- Steps to reproduce
- Your
client_id(so we can whitelist you against rate limits during testing) - Whether you want public credit if we disclose
We commit to:
- Acknowledgement within 24 hours (business days, US Eastern)
- Assessment + fix ETA within 72 hours
- Coordinated disclosure timeline agreed with you
- Public credit in the changelog unless you prefer otherwise
- Credit balance rewards for confirmed vulnerabilities scaled by severity