Skip to main content

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 — exp claim in token)
  • IP-bound — must be consumed from the same IP that received the URL
  • Single-use — after the first successful GET, the jti is added to a blacklist and further requests return HTTP 410 Gone
  • Client-taggedclient_id is 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.

Never log or export signed URLs

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 barcode and variant instead, 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:

  1. 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.
  2. Invisible traceable overlay — a per-client_id steganographic 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_key in mobile apps, browser JS, or anything shipped to end users
  • Never commit api_key to 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_key at 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:

  1. Log into your account panel
  2. Go to API Keys → find the client → click Revoke
  3. Issue a new client, update your service, redeploy
  4. Email api@retaildigitals.com with the old client_id so 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