Skip to main content
Four endpoints, all JSON. This page walks each one with real shapes, then the patterns agents get the most from.
Every response body below is verbatim from the live API on 2026-09-13, serving the full 10.2M-image index. title is null when the source’s own title was a camera filename or similar junk; the file is still downloadable and the rights are complete.
POST /v1/search. Describe what you need. Two retrieval lanes run in parallel — a keyword lane over the asset’s normalized metadata and a visual lane over the image itself — and a multimodal reranker orders the merged candidates by looking at the actual pixels.
Response:
Every response also carries timing_ms (embed, retrieve, rerank in milliseconds), mode (what you asked for), ranking (what actually ordered the results: the mode, or none (degraded) if the reranker was unavailable), pool_size (distinct candidates from both lanes) and reranked (how many the reranker scored). Use them to tune the fields below per query type instead of guessing. Measured quality per mode, 200 hard eval queries judged by a vision model on the top-5 (2026-09-13): Measured trade-offs of the reranker parameters, 60 hard queries each, same judge (2026-09-13, three queries in flight so latencies are higher than a lone query): Sixty queries per row means differences under about 6 points are noise. rerank_k: 60 is the notable one: same satisfaction as the default at a third of the latency. The parameters are independent of each other. mode picks the reranker; k picks how many results come back; ann_k and bm25_k shape the candidate pool (ann_ef is accepted for compatibility and currently ignored); rerank_k and rerank_text bound the reranker’s work. GET /v1/health reports the server’s current defaults under defaults and the available modes under modes. Rough cost model, measured 2026-09-14: retrieval ~0.1 s regardless of pool depth up to a few hundred; the visual reranker ~8 ms per candidate on its GPU plus the thumbnail fetch (0.2–0.6 s for 150 candidates, less for fewer); rerank_text: false cuts reranker tokens by about 60%.

Request fields

Filters

Every filter is a hard constraint applied before ranking; omitted filters do not constrain.

2. Similar images

POST /v1/similar. Expand from any asset you already have — usually the best hit from a search. Same filters, same response envelope.
Response:
Returns 404 {"detail": "asset has no embedding"} for an asset_id that isn’t in the index.

3. One asset’s metadata and rights

GET /v1/asset/{asset_id}. The same rights object as a search result plus format, without running a search. Use it to re-check rights before you publish, or to resolve an id you stored earlier.

4. Getting the file

file and thumb are tracked URLs. A GET on either answers 302 Found with a signed download link that is valid for one hour. Follow redirects; don’t store the signed link, store asset_id and the tracked URL.
The query_id segment is how a download is attributed to the query that produced it. URLs from /v1/asset/{asset_id} use direct in that position instead.

5. Warming the GPU services

The embedding and reranking models run on GPUs that shut down after an hour without requests. The first request after that restarts them from a memory snapshot, about 7 seconds, occasionally two to three minutes during beta when no snapshot exists yet for the machine handed to us. GET /v1/warm (with your key) starts both warming in the background and reports their state; call it when an agent session begins and poll every 10 s until ready is true.
state is one of warm, warming, error, unknown. A search sent while a service is warming waits for it rather than failing.

6. Health

GET /v1/health needs no key and reports the serving stack plus the last-known state of the GPU services (upstream), so you can pin a result to the exact configuration that produced it.

Errors

A 200 may also carry "degraded": "reranker unavailable; lane-order results". The results are valid and in retrieval order; score is null. Treat it as a soft signal, not an error.

Rate limits and tiers

Limits are per key and come from the account’s tier, which is set by the total credit you have purchased (welcome credit does not count, and spending never lowers a tier). Every authenticated response carries the current numbers: A 429 names the limit you hit and carries Retry-After. Pace on X-RateLimit-Remaining rather than retrying blindly. GET /v1/health lists the tier table the server is running.

Latency, and what to expect

Measured 2026-09-14 on the full 10.2M-image index, through the public API, on queries the system had never seen:
  • Typical new query, reranked: 1.8 to 2.3 seconds end to end. About 0.02 s to embed, 0.1 s to retrieve both lanes, and 1.5 to 1.9 s to fetch 150 thumbnails and score them on the GPU. timing_ms reports embed, retrieve, rerank (which includes the thumbnail fetch) and hop (the single round trip to the GPU service).
  • Retrieval variance: the retrieval store keeps our index warm on one of its nodes; occasionally a request is routed to a node that has not loaded it yet, and retrieval runs 0.5 to 1.3 s for a stretch of queries until that node warms. Bounded, and it does not affect results.
  • Cold thumbnails: when most of the 150 candidates are images nobody has fetched recently, the fetch can add several seconds (worst seen: 8 s).
  • Cold start: the GPU service shuts down after an hour without requests and restores from a memory snapshot. Beta note: the first search after that can take 15 to 40 seconds; every later search is normal. Calling /v1/warm at session start (section 5) moves this out of your first real query. Give the first call a 120-second client timeout and do not retry inside that window; a search sent while the service is restoring waits rather than fails.
  • Tuning: mode: "none" skips the reranker (about 0.3 s end to end, lane-order results, noticeably lower quality). The default rerank_k is 60 (judged equal to scoring all 150 candidates, at half the time); raise it toward 150 for a deeper rerank, or lower it for speed. rerank_text: false shaves a little more; ann_ef currently has no effect. k itself barely matters.

Patterns that work

Set filters.commercial: true on every request rather than inspecting results. When rights.attribution_required is true, copy rights.attribution into the artifact you produce — it is already formatted.
Agents can drive each stage. A cheap first pass with mode: "none" and explain: true returns lane order with every candidate’s lane ranks in ~0.6 s; if the top results look right, stop there. If not, re-query with mode: "visual", or widen the pool (ann_k: 300, bm25_k: 100, rerank_k: 150) for a hard query. timing_ms on every response shows where the time went.
For a species, a place, a named object, or anything with an exact term, bm25_k: 200 gives the keyword lane more candidates to hand the reranker. The visual lane alone can miss exact facts a photo doesn’t show.
“moody coastal fog at dawn” has no keyword to match. ann_k: 300, bm25_k: 0 leans entirely on the image embedding.
Take the best asset_id from a search and call /v1/similar with the same filters. This is usually better than rephrasing the query.
Assets under cc-by-nc* licenses are indexed and come back with commercial: false. If your use is noncommercial you get the larger corpus by leaving the filter unset; if it isn’t, set it and they never appear.