Complete metadata
ID, slug, title, description, collection, dimensions, size, extension, tags, timestamps, and safe preview URLs.
Catalog · GET /images/{id} · POST /images/batch
Resolve a single asset with
GET https://epochalstock.com/api/v1/images/{image_id}
or load up to 100 unique IDs with
POST https://epochalstock.com/api/v1/images/batch
in one authenticated request. Responses include catalog fields plus
thumbnail_url and watermarked preview_url—original filesystem paths are never returned.
After discovery via GET /images, design apps typically store image IDs and fetch full records when a user focuses a card, opens an inspector, or prepares an export. Use the single-image endpoint for one-off lookups; use batch when a selection, cart, or layout holds many IDs.
ID, slug, title, description, collection, dimensions, size, extension, tags, timestamps, and safe preview URLs.
1–100 unique IDs count as a single API call toward the 30 requests/minute account limit.
Missing IDs are omitted. requested and found make incomplete sets explicit.
Originals are delivered only through the two-step download flow—never as static file paths in metadata.
Requires a valid Bearer key. See Authentication for header format and key lifecycle.
Returns the complete catalog record for one image. Unknown IDs return HTTP 404 with
image_not_found.
| Item | Value |
|---|---|
| Method / path | GET /images/{image_id} |
| Full URL example | https://epochalstock.com/api/v1/images/es_001_example |
| Auth | Authorization: Bearer … + Accept: application/json |
| Path parameter | image_id — stable catalog identifier (string) |
| Success | HTTP 200 · image object under data |
| Not found | HTTP 404 · image_not_found |
The response includes ID, slug, title, description, collection, width, height, file size, file extension, tags, creation time, thumbnail URL, and watermarked preview URL. Original filesystem paths are never exposed.
Field names below match the shape used across list, detail, and batch responses. Treat any additional fields as optional and ignore unknown keys for forward compatibility.
| Field | Type | Description |
|---|---|---|
id |
string | Stable image identifier. Use this for batch, downloads, and local caches. |
slug |
string | URL-friendly label for human-readable UI routes (not a substitute for id). |
title |
string | Display title. |
description |
string | Longer catalog description suitable for inspectors and accessibility copy. |
collection |
string | Collection or category label within the catalog. |
width |
integer | Original pixel width. |
height |
integer | Original pixel height. |
file_size |
integer | Original file size in bytes (when provided). |
file_extension |
string | File type extension such as jpg or png. |
tags |
string[] | Normalized tags attached to the image. |
created_at |
string (ISO-8601) | Catalog creation / index timestamp for the asset. |
thumbnail_url |
string (URL) | Small preview safe for grids and pickers. |
preview_url |
string (URL) | Larger watermarked preview for canvas/inspector UIs. |
Security: Do not expect or request original disk paths. Fetch binaries only via
POST /downloads (mint a short-lived token) then
GET /downloads/{token}.
Accepts 1–100 unique image IDs and counts as one API request.
Missing IDs are omitted from the result set; use requested and found to detect partial matches.
| Item | Value |
|---|---|
| Method / path | POST /images/batch |
| Content-Type | application/json |
| Body | {"ids":["es_001_example","es_002_example"]} |
ids |
Array of strings · min 1 · max 100 · unique items required |
| Rate limit impact | Counts as ONE authenticated request (not one per ID) |
| Missing IDs | Omitted from the returned images; not fatal for the whole call |
| Field | Type | Description |
|---|---|---|
images |
object[] | Found image records (same field shape as GET one). |
requested |
integer | Number of IDs submitted after validation. |
found |
integer | Number of records returned. If found < requested, some IDs were missing. |
At the account limit of 30 requests per minute, N individual GET detail calls cost N requests. One batch call resolves up to 100 IDs for the price of a single request—critical for selection panels and multi-asset layouts.
Request budget comparison (example: 80 IDs)
Deep-link open, inspector refresh, cache miss for a single asset, webhook-style callbacks with one ID.
Multi-select toolbars, board/layout hydration, shopping-cart style export lists, rehydrate local caches after offline work.
List with GET /images, store IDs, then batch-fill details only when the UI needs them.
Mint tokens serially via downloads—one concurrent original transfer per account.
Load complete metadata for es_001_example. Read the API key from the environment only.
curl -sS "https://epochalstock.com/api/v1/images/es_001_example" \
-H "Authorization: Bearer $EPOCHAL_API_KEY" \
-H "Accept: application/json"
const imageId = "es_001_example";
const response = await fetch(
`https://epochalstock.com/api/v1/images/${encodeURIComponent(imageId)}`,
{
headers: {
Authorization: `Bearer ${process.env.EPOCHAL_API_KEY}`,
Accept: "application/json",
},
}
);
if (!response.ok) throw new Error(await response.text());
const { data, meta } = await response.json();
console.log(data.id, data.title, data.thumbnail_url, meta.request_id);
$imageId = 'es_001_example';
$url = 'https://epochalstock.com/api/v1/images/' . rawurlencode($imageId);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EPOCHAL_API_KEY'),
'Accept: application/json',
],
CURLOPT_TIMEOUT => 20,
]);
$response = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException((string) $response);
}
$payload = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
$image = $payload['data'];
// $image['id'], title, tags, thumbnail_url, preview_url, …
import os
import requests
image_id = "es_001_example"
r = requests.get(
f"https://epochalstock.com/api/v1/images/{image_id}",
headers={
"Authorization": f"Bearer {os.environ['EPOCHAL_API_KEY']}",
"Accept": "application/json",
},
timeout=20,
)
r.raise_for_status()
data = r.json()["data"]
print(data["id"], data["title"], data.get("preview_url"))
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
func main() {
imageID := "es_001_example"
endpoint := "https://epochalstock.com/api/v1/images/" + url.PathEscape(imageID)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
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()
body, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, body))
}
var payload struct {
Data map[string]any `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
panic(err)
}
fmt.Println(payload.Data["id"], payload.Data["title"])
}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
String imageId = "es_001_example";
String path = URLEncoder.encode(imageId, StandardCharsets.UTF_8).replace("+", "%20");
String url = "https://epochalstock.com/api/v1/images/" + path;
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + System.getenv("EPOCHAL_API_KEY"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException(response.statusCode() + " " + response.body());
}
// Parse JSON data.id, data.title, data.thumbnail_url, data.preview_url
System.out.println(response.body());
Resolve multiple IDs in one request. Compare found to requested before assuming every ID exists.
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"]}'
const ids = ["es_001_example", "es_002_example"];
const response = await fetch("https://epochalstock.com/api/v1/images/batch", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EPOCHAL_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ ids }),
});
if (!response.ok) throw new Error(await response.text());
const { data } = await response.json();
console.log(`found ${data.found} of ${data.requested}`);
for (const image of data.images) {
console.log(image.id, image.title);
}
$ids = ['es_001_example', 'es_002_example'];
$ch = curl_init('https://epochalstock.com/api/v1/images/batch');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('EPOCHAL_API_KEY'),
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['ids' => $ids], JSON_THROW_ON_ERROR),
CURLOPT_TIMEOUT => 20,
]);
$response = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException((string) $response);
}
$data = json_decode($response, true, 512, JSON_THROW_ON_ERROR)['data'];
// $data['images'], $data['requested'], $data['found']
import os
import requests
ids = ["es_001_example", "es_002_example"]
r = requests.post(
"https://epochalstock.com/api/v1/images/batch",
json={"ids": ids},
headers={
"Authorization": f"Bearer {os.environ['EPOCHAL_API_KEY']}",
"Accept": "application/json",
},
timeout=20,
)
r.raise_for_status()
data = r.json()["data"]
print(f"found {data['found']} of {data['requested']}")
for image in data["images"]:
print(image["id"], image["title"])
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
body, _ := json.Marshal(map[string]any{
"ids": []string{"es_001_example", "es_002_example"},
})
req, err := http.NewRequest(http.MethodPost,
"https://epochalstock.com/api/v1/images/batch",
bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("EPOCHAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
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()
raw, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%d %s", res.StatusCode, raw))
}
var payload struct {
Data struct {
Images []map[string]any `json:"images"`
Requested int `json:"requested"`
Found int `json:"found"`
} `json:"data"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
panic(err)
}
fmt.Printf("found %d of %d\n", payload.Data.Found, payload.Data.Requested)
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
String json = """
{"ids":["es_001_example","es_002_example"]}
""";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://epochalstock.com/api/v1/images/batch"))
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + System.getenv("EPOCHAL_API_KEY"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException(response.statusCode() + " " + response.body());
}
// Parse data.images, data.requested, data.found
System.out.println(response.body());
Successful detail payload. Original paths are never included.
{
"data": {
"id": "es_001_example",
"slug": "golden-forest-canopy",
"title": "Golden Forest Canopy",
"description": "Sunlit woodland canopy with warm gold highlights.",
"collection": "nature",
"width": 6000,
"height": 4000,
"file_size": 4821934,
"file_extension": "jpg",
"tags": ["nature", "trees", "forest", "gold"],
"created_at": "2025-11-02T14:22:10Z",
"thumbnail_url": "https://epochalstock.com/…/thumb.jpg",
"preview_url": "https://epochalstock.com/…/preview.jpg"
},
"meta": {
"request_id": "req_01HZX…"
}
}
Two IDs requested; one missing. found is 1 and only the existing record appears in images.
{
"data": {
"images": [
{
"id": "es_001_example",
"slug": "golden-forest-canopy",
"title": "Golden Forest Canopy",
"description": "Sunlit woodland canopy with warm gold highlights.",
"collection": "nature",
"width": 6000,
"height": 4000,
"file_size": 4821934,
"file_extension": "jpg",
"tags": ["nature", "trees", "forest", "gold"],
"created_at": "2025-11-02T14:22:10Z",
"thumbnail_url": "https://epochalstock.com/…/thumb.jpg",
"preview_url": "https://epochalstock.com/…/preview.jpg"
}
],
"requested": 2,
"found": 1
},
"meta": {
"request_id": "req_01HZY…"
}
}
POST /images/batch instead of N detail calls.thumbnail_url / watermarked preview_url in editors; never assume originals are public URLs.meta.request_id / X-Request-ID when debugging 404s or partial batches.