App blueprint · Moodboard

Moodboard & collection builder

Build a multi-user moodboard product where teams pin licensed stock into boards, sections, and comments. Collections live in your application database as lists of Epochal image IDs. You discover assets with GET /images and tags, then reopen boards efficiently with POST /images/batch. There is no Epochal /collections endpoint to call— and you should not invent one on their side. Plan: $50/month, 30 req/min shared, max 20 keys, unlimited serial originals, one concurrent transfer.

Who this blueprint is for

Product teams

Shipping “brand boards,” campaign walls, or creative brief spaces that need real stock metadata—not static image dumps.

Agencies & studios

Internal tools where art directors pin references and designers open the same board later with fresh titles and previews.

Design SaaS

Multi-tenant apps that already have projects, memberships, and permissions—and need a stock layer underneath.

Not for

Expecting Epochal to store board membership, comments, or folder trees. Those are your domain objects. Also not for browser-held API keys or free-tier trials (none in v1).

Product features

  • Boards & sections — owned in your DB (name, privacy, members, order).
  • Pins — rows of image_id (+ optional note, position, added_by).
  • Discover — in-app stock browser: GET /images with q, tags, sort, cursor.
  • Tag-driven curation — chip lists from GET /tags, then filter search.
  • Open board — load pin IDs from your DB, hydrate with POST /images/batch (chunk >100).
  • Single pin focus — optional GET /images/{id} for a large preview modal.
  • Export board package — serial POST /downloads on the backend when users need originals.
  • Realtime (optional) — use your websockets for pin add/remove; Epochal stays request/response catalog only.

Critical distinction: “Collection” in your product UI is application data. Epochal’s catalog may expose a collection field on an image record as metadata; that is not a CRUD collections API for your boards. Do not call non-existent /collections, GraphQL, or upload endpoints.

Architecture

Multi-user clients never talk to Epochal with your secret. They talk to your API under your auth. Your API owns boards and calls the catalog with a Bearer key.

Browser / app UI → Your backend → Epochal API

1. Team UI Boards, pins, search modal · session auth
2. Your backend Postgres/MySQL boards · permissions · EPOCHAL_API_KEY
3. Epochal API https://epochalstock.com/api/v1 search · tags · batch · downloads
4. Hydrated board Merge your pin rows with catalog cards

Bearer key only on the backend. Front-end bundles, mobile apps, and shared “demo” keys in client env files will leak. Catalog access with secret keys from public browsers is not the supported integration path.

Suggested data model (yours)

Keep Epochal IDs as opaque strings. Prefer not to mirror full catalog JSON long-term—rehydrate on open so titles, tags, and preview URLs stay current.

Table Key fields Notes
boards id, org_id, title, visibility Your tenancy & ACL
board_members board_id, user_id, role viewer / editor
board_pins board_id, image_id, note, sort_order, added_by image_id = Epochal id
board_sections (optional) id, board_id, name, sort_order UI grouping only
  • Unique constraint on (board_id, image_id) unless you intentionally allow duplicates
  • Store user notes and positions yourself—Epochal has no pin metadata API
  • On pin add, optionally verify the ID once via detail or a tiny batch before insert
  • Soft-delete pins so historical comments remain meaningful if an asset later omits from batch

Endpoint mapping

Product action Your system Epochal (documented)
Create / rename / share board Your DB + ACL
Search stock to pin Proxy route GET /images
Browse themes Proxy route GET /tags, GET /tags/{tag}
Add pin Insert image_id in your DB Optional verify: GET /images/{id}
Open board (many pins) Load IDs from DB POST /images/batch (1–100 per call)
Pin detail modal Proxy GET /images/{id}
Export board originals Job queue POST /downloads + signed GET (serial)
Inventory badge Ops/admin GET /stats
“Epochal collections CRUD” Does not exist Do not invent /collections

Discover via search and tags

Discovery is the same catalog surface as the design plugin: free-text and tag filters on GET https://epochalstock.com/api/v1/images. Seed tag chips from GET /tags sorted by count so curators see large themes first.

See Asset search and Tag curation for parameter-level guidance.

Hydrate boards with POST /images/batch

Opening a board with 80 pins must not fire 80 detail requests. Batch accepts 1–100 unique IDs and counts as one authenticated request. Missing IDs are omitted; compare found vs requested and show soft placeholders for gaps.

≤ 100 pins

One batch call. Merge returned images onto pin rows by id.

> 100 pins

Chunk serially (or with low concurrency). Leave headroom for search traffic.

Partial matches

Flag unavailable assets; do not fail the whole board render.

vs N × GET

80 detail calls burn most of a minute; one batch leaves budget for the team.

Full batch semantics: Image detail & batch · Bulk metadata use case.

Multi-user architecture notes

Concurrency in a moodboard app is mostly your problem (who can edit, optimistic UI, presence). Epochal only constrains catalog rate and download leases at the account level.

  • Authorization: enforce board membership before any search proxy or pin write.
  • Shared rate budget: all users of your product share the same Epochal account’s 30 req/min unless you operate separate Epochal accounts (unusual). Debounce search globally per org if needed.
  • Server-side cache: short TTL (e.g. 60s) on identical search queries and batch results reduces duplicate team traffic.
  • Realtime pins: broadcast pin add/remove over your channel; each client still receives catalog cards from your API, not from Epochal websockets (none in v1 catalog docs).
  • Export: one account-wide original transfer at a time—queue exports per org so two designers do not stampede into 409 download_already_active.

Code samples

Pattern: load pin IDs from your database, rehydrate with batch, merge for the UI. Keys only on the server.

Hydrate pin IDs (batch)

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","es_003_example"]}'

Discover then pin (search proxy sketch)

cURL
# Discover
curl -sS "https://epochalstock.com/api/v1/images?q=editorial+portrait&tags=people&tag_mode=all&limit=24" \
  -H "Authorization: Bearer $EPOCHAL_API_KEY"

# Your app then INSERT board_pins(board_id, image_id, ...) — not an Epochal write

Security callouts

Never share one Epochal key with end-user browsers. Multi-tenant moodboard frontends must use your session; your servers hold EPOCHAL_API_KEY.

Board privacy is your job. Epochal image metadata is catalog data. If a board is private, only your ACL may return hydrated cards for those pin IDs—even though the same IDs are discoverable via public search on an authorized account.

  • Authorize every search, batch, and export proxy under the user’s board/org role
  • Do not return signed download_url values to untrusted clients if you can stream instead
  • Rate-limit your own API per user to protect the shared 30/min Epochal budget
  • No video, GraphQL, /upload, or /collections CRUD on Epochal v1 (free 1-month trial: 2,000 requests at 2/min via POST /trial/signup)

Rate limits & downloads

Account budget: 30 authenticated req/min, shared by all keys (max 20). Batch is the efficiency tool for reopen; search still costs one unit per page. Original downloads are unlimited in count but one concurrent transfer per account—coordinate export jobs across the team.

Scenario Approx. catalog cost
Open board with 40 pins (one batch) 1 request
Open board with 250 pins (3 batches) 3 requests
Search 3 pages while pinning 3 requests
40 × GET detail instead of batch 40 requests (avoid)
Export 10 originals 10 × POST /downloads (+ serial transfers)

Rate limits & errors · Downloads

Build checklist

  • Model boards/pins in your database with opaque Epochal image_id values
  • Proxy search and tags; never call Epochal from the browser with a secret key
  • Rehydrate with POST /images/batch; handle missing IDs softly
  • Enforce membership before read/write of any board
  • Debounce multi-user search; optional short-TTL server cache
  • Serial export queue with 409 handling for originals
  • Document that “collections” are app data—not an Epochal collections API

Next steps

Tag curation

Drive discovery with GET /tags and tag filters.

Design plugin

In-editor search, place preview, export originals.

Agency library

Named keys per client project under one shared rate limit.