Developer Image API · v1

Code examples

End-to-end, production-shaped samples for catalog search, metadata, tags, stats, original downloads, and rate-limit backoff. Every snippet loads secrets from the environment—never hard-coded live keys. Base URL used on this site: https://epochalstock.com/api/v1.

1. Setup / environment variables

Store the Bearer secret only on trusted servers or serverless runtimes. Recommended names: EPOCHAL_API_KEY for the secret and optional EPOCHAL_API_BASE if you need to override the default base. Catalog calls require either a free-trial key (instant via POST /trial/signup — 2,000 requests, 2/minute, 1 month) or an active $50 USD/month subscription (first month $40 with the 20% upgrade discount) with a named key created after PayPal reports the plan ACTIVE.

Never ship keys in browsers, mobile apps, SPA bundles, or git. Query-string keys (?api_key=…) are rejected. Use Authorization: Bearer … only from your backend.

cURL
# Export for the current shell session (prefer your host's secret store in production)
export EPOCHAL_API_BASE="https://epochalstock.com/api/v1"
export EPOCHAL_API_KEY="es_live_your_secret_here"

# Quick connectivity check (no key)
curl -sS -i "$EPOCHAL_API_BASE/health"
  • Use a separate named key per project/environment (up to 20 keys; shared 30 req/min budget).
  • Always set connect and read timeouts on HTTP clients.
  • Log X-Request-ID / meta.request_id—never log Authorization headers or download URLs.

3. Get one image (GET /images/{image_id})

Returns ID, slug, title, description, collection, dimensions, file size, extension, tags, creation time, thumbnail URL, and watermarked preview URL. Original filesystem paths are never exposed.

cURL
curl -sS "https://epochalstock.com/api/v1/images/es_001_example" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Accept: application/json"

Field notes and examples: Detail & batch.

4. Batch metadata (POST /images/batch)

Resolve 1–100 unique image IDs in a single request (counts as one API call against the 30/min budget). Missing IDs are omitted; compare found to requested for partial matches.

cURL
curl -sS -X POST "https://epochalstock.com/api/v1/images/batch" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"ids":["es_001_example","es_002_example"]}'

5. List tags with counts (GET /tags)

Browse normalized tags and image counts. Query with optional q; sort is name or count; order is asc or desc; paginate with limit and cursor. Each item exposes tag and image_count.

cURL
curl -sS "https://epochalstock.com/api/v1/tags?q=forest&sort=count&order=desc&limit=50" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Accept: application/json"

# Single tag detail (path-encode the tag segment)
curl -sS "https://epochalstock.com/api/v1/tags/forest" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Accept: application/json"

More tag/stats detail: Tags & stats.

6. Catalog stats (GET /stats)

Returns total_images, total_tags, and catalog_built_at. Cache this on your backend for tens of seconds to a few minutes—polling every second wastes the shared 30 req/min budget.

cURL
curl -sS "https://epochalstock.com/api/v1/stats" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Accept: application/json"

7. Two-step download (create token + save file)

Originals never put the API key on the binary URL. First mint a five-minute, single-use token with POST /downloads, then GET download_url without Authorization. Only one original may transfer at a time per account (409 download_already_active if another transfer is in flight).

Two-step original delivery

1. Mint POST /downloads + Bearer key
2. Transfer GET download_url · no API key
3. Discard URL Single-use · 5-minute expiry
cURL
# 1) Create single-use download token (Bearer required)
curl -sS -X POST "https://epochalstock.com/api/v1/downloads" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"image_id":"es_001_example"}'

# 2) Download binary WITHOUT the API key
# Replace $DOWNLOAD_URL with data.download_url from the mint response
curl -fL "$DOWNLOAD_URL" -o image.jpg

Treat download_url as a short-lived secret. Do not log it to analytics, paste it into tickets, or put it in browser-visible HTML when you can stream bytes through your backend instead. Details: Downloads.

8. Handling 429 with backoff

The account limit is 30 authenticated requests per minute (shared across all keys). On over-limit, the API returns HTTP 429, rate_limit_exceeded, and usually Retry-After. Retry only 429, 502, and 503 with bounded exponential backoff and jitter—do not blind-retry other 4xx.

cURL
# Inspect rate-limit headers on any authenticated call
curl -sS -D - -o /tmp/epochal-body.json \
  "https://epochalstock.com/api/v1/images?limit=5" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY" \
  -H "Accept: application/json" | grep -iE 'HTTP/|x-ratelimit|retry-after|x-request-id'

# On 429, sleep at least Retry-After seconds, then retry once (shell sketch)
# sleep "$RETRY_AFTER"; curl ...

Strategy tables and error codes: Rate limits & errors.

9. Tips for design apps

Design tools, CMS plugins, and Canva-class editors should treat Epochal as a server-side dependency. End-user browsers and desktop renderer processes must never hold live API keys.

Server proxy

Your backend validates the signed-in designer, applies product-level authz, then calls Epochal with the server key. Return only fields the UI needs (title, thumb, id)—not full signed download URLs unless required.

Cache metadata

Cache search pages, tag lists, and GET /stats briefly. Prefer POST /images/batch when rehydrating many known IDs (one request ≤ 100 IDs).

Don’t ship keys

No keys in Electron renderer configs, browser extensions, mobile binaries, or public env files shipped with installers. Revoke and rotate if a build may have leaked a secret.

Serialize exports

Original downloads allow unlimited volume but only one concurrent transfer per account. Queue exports and handle 409 download_already_active by waiting, not fan-out.

Recommended architecture

Design UI No API key · session only
Your backend Authz · cache · EPOCHAL_API_KEY
Epochal API Bearer catalog · two-step originals
  • Use one named key per product environment (e.g. design-app-prod, design-app-staging).
  • Throttle client search boxes—debounce UI input so one keystroke storm does not burn 30 req/min.
  • Stream originals through your backend when end users must not see signed download URLs.
  • Follow the commercial license; do not redistribute originals as a competing stock marketplace.
  • Monitor shared rate-limit headers across workers; multiple pods still share one account budget.

Common mistake: embedding es_live_… in a frontend env file (VITE_, NEXT_PUBLIC_, Expo public config). Those values ship to every user. Keep keys server-only and proxy required catalog fields.

Next steps

Authentication

Bearer format, key lifecycle, and why query-string keys fail.

Images

Full search parameter and cursor pagination reference.

Downloads

Token expiry, concurrency lease, and transfer errors.