Bearer only
Keys must travel in the Authorization header. Query-string keys are rejected with query_key_rejected.
Developer Image API · v1
Every catalog request to the Epochal Stock Developer Image API must carry a secret API key
in the Authorization header using the Bearer scheme. Keys are shown once at
creation, stored as keyed hashes server-side, and must never appear in query strings, browser
bundles, or public clients.
Catalog endpoints under https://epochalstock.com/api/v1 require authentication.
Send your key on every request alongside an explicit JSON accept header. Keys are prefixed
es_test_ (free trial, issued instantly by POST /trial/signup) or
es_live_ (paid) — both use the same header and endpoints:
Authorization: Bearer es_live_…your_secret…
Accept: application/json
Keys must travel in the Authorization header. Query-string keys are rejected with query_key_rejected.
The full secret is displayed a single time when the key is created. Epochal Stock stores a keyed hash, not the original secret.
Every response includes X-Request-ID. Keep that value when reporting problems or correlating logs.
Create up to 20 named keys per account. Revoke any key immediately through the management API when a project is compromised.
Passing secrets as query parameters is convenient and wrong. URLs leak into browser history, reverse proxies, CDN access logs, analytics beacons, crash reports, and support screenshots. Bearer headers stay out of the request line and are far less likely to be retained by intermediate systems that log full URLs.
The API therefore rejects patterns such as
?api_key=… or ?key=… and returns
query_key_rejected (HTTP 400). Always place the secret in the header:
Authorization: Bearer <key> on every authenticated call.Accept: application/json so error bodies stay machine-readable.CORS is not supported for browser key use. Requests that look like browser-origin catalog calls with a key are rejected with cors_not_supported (HTTP 403). Your design app’s backend should proxy only the fields the client needs.
How an authenticated catalog request is processed from client to handler.
Authenticated request path
Failed validation never reaches catalog handlers. Invalid or revoked keys return
invalid_api_key; inactive billing returns subscription errors; browser/CORS
misuse returns cors_not_supported. Successful responses always include
X-Request-ID and rate-limit headers.
The snippets below show the minimum headers for a simple
GET https://epochalstock.com/api/v1/images call. Replace the environment variable with your
secret store of choice. Never commit real keys.
curl -sS "https://epochalstock.com/api/v1/images?limit=5" \
-H "Authorization: Bearer $EPOCHAL_API_KEY" \
-H "Accept: application/json" \
-D -
const response = await fetch(
"https://epochalstock.com/api/v1/images?limit=5",
{
headers: {
Authorization: `Bearer ${process.env.EPOCHAL_API_KEY}`,
Accept: "application/json",
},
}
);
const requestId = response.headers.get("x-request-id");
if (!response.ok) {
throw new Error(`HTTP ${response.status} request_id=${requestId}`);
}
const body = await response.json();
$ch = curl_init('https://epochalstock.com/api/v1/images?limit=5');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EPOCHAL_API_KEY'),
'Accept: application/json',
],
CURLOPT_TIMEOUT => 20,
]);
$raw = curl_exec($ch);
// Split headers/body, read X-Request-ID, then json_decode the body.
import os
import requests
r = requests.get(
"https://epochalstock.com/api/v1/images",
params={"limit": 5},
headers={
"Authorization": f"Bearer {os.environ['EPOCHAL_API_KEY']}",
"Accept": "application/json",
},
timeout=20,
)
request_id = r.headers.get("X-Request-ID")
r.raise_for_status()
print(request_id, r.json())
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func main() {
req, _ := http.NewRequest("GET",
"https://epochalstock.com/api/v1/images?limit=5", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("EPOCHAL_API_KEY"))
req.Header.Set("Accept", "application/json")
client := &http.Client{Timeout: 20 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
fmt.Println("X-Request-ID:", res.Header.Get("X-Request-ID"))
}
Tip: Log X-Request-ID on both success and failure paths.
Support can locate the exact request without you pasting secrets or full response bodies.
Each account may hold up to 20 named keys. Create a separate key per website, backend, or environment so you can revoke one project without rotating everything. All keys on an account share the same rate limit and concurrent-download lease.
POST /keys) with a human-readable name such as
design-app-prod. Copy the secret immediately; it is not shown again.
DELETE /keys/{key_id} invalidates a key
immediately. In-flight clients will start receiving invalid_api_key.
Creating more than the account allows returns key_limit_reached (HTTP 409). Revoke unused keys first.
Epochal Stock stores a keyed hash of the secret. If you lose a key, create a new one and revoke the old ID if it is still listed.
Extra keys do not multiply the 30 requests/minute account budget. Unexpected multi-key traffic still consumes the shared limit.
Management calls (list/create/revoke keys, billing, usage) live behind the portal session and CSRF protection—not the Bearer catalog key. See Security & billing for portal flows.
Every API response includes an X-Request-ID header (and often a matching
request_id field inside error JSON). Treat it as the primary correlation token
when something fails in production.
429, 502, or 503 responses.
Successful JSON bodies also carry meta.request_id. Prefer logging both the
header and body ID when available so retries and multi-hop proxies stay aligned.
Do not paste API keys into tickets or chat. A Request-ID plus approximate time is enough for server-side lookup. Rotate any key that may have been exposed.
Auth-related failures return structured JSON with error.code,
error.message, and error.request_id. The table below lists the
codes you will see most often while wiring credentials.
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
401 |
invalid_api_key |
Missing, malformed, revoked, or unknown Bearer key. | Confirm the env var, header spelling, and that the key was not revoked. |
402 |
subscription_required |
No active paid plan on the account. | Complete PayPal subscription activation before catalog calls. |
402 |
subscription_past_due |
Billing failed or the subscription was suspended. | Fix payment method; access returns after the account is active again. |
403 |
cors_not_supported |
Browser-style cross-origin use of a secret key is blocked. | Move the call to your backend and proxy only safe fields to the client. |
400 |
query_key_rejected |
An API key was supplied in the query string. | Remove ?api_key= (and similar) and use the Bearer header only. |
For rate limits, validation errors, and the full error catalog, see Rate limits & errors.
Never call authenticated endpoints from a public browser with a live key. Frontend bundles, mobile apps, browser extensions, and untrusted clients will leak secrets. Your server should validate user input and proxy only required catalog fields.
429, 502, and 503 with bounded exponential backoff and jitter.