Searching
POST /api/v1/search searches the key owner's team library. It requires the
search scope and accepts a JSON object; every field is optional.
Request
| Field | Type | Default | Description |
|---|---|---|---|
q | string | none | Non-empty free-text query. Without one, files are newest-first. |
mode | string | hybrid | hybrid, text, or vector; used when q is present. |
filters | object | {} | Structured filters described below. |
limit | number | 50 | Integer from 1 through 100. |
cursor | string | none | Opaque cursor returned by the previous page. |
hybrid combines BM25 full-text relevance with semantic similarity. text
uses BM25 only, and vector uses semantic similarity only.
{
"q": "sunset at the beach",
"mode": "hybrid",
"filters": {
"media_type": { "image": "include" },
"rating": { "3:4": "include", "3:5": "include" }
},
"limit": 25
}curl --fail-with-body \
-X POST "$NEW_ARCHIVE_URL/api/v1/search" \
-H "Authorization: Bearer $NEW_ARCHIVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "sunset at the beach",
"mode": "hybrid",
"filters": {"media_type": {"image": "include"}},
"limit": 10
}'Response
{
"items": [
{
"id": "0198c9a0-7d3e-7c41-b7a2-3f9d1c2e4a5b",
"status": "ready",
"original_name": "sunset.jpg",
"mime_type": "image/jpeg",
"size_bytes": 2481931,
"width": 4000,
"height": 3000,
"taken_at": "2026-06-30T18:41:02.000Z",
"created_at": "2026-07-11T09:12:44.201Z",
"thumbnail_url": "https://storage.example/signed-thumbnail"
}
],
"next_cursor": "eyJvIjoyNX0"
}Items use the same schema as
GET /api/v1/files/:fileId.
Ready files include a signed small-thumbnail URL. next_cursor is omitted
when the result set is exhausted.
Pagination
Pass next_cursor back unchanged and keep the query, mode, filters, and limit
the same. Cursors are opaque and must not be constructed or decoded by clients.
async function searchAll(request) {
const items = []
let cursor
do {
const response = await fetch(
`${process.env.NEW_ARCHIVE_URL}/api/v1/search`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NEW_ARCHIVE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ...request, cursor }),
}
)
const page = await response.json()
if (!response.ok)
throw new Error(`${page.error.code}: ${page.error.message}`)
items.push(...page.items)
cursor = page.next_cursor
} while (cursor)
return items
}Filter behavior
Most filter categories are tristate maps. Each value maps to include or
exclude; a missing value is neutral. Includes within one category are ORed,
active categories are ANDed, and exclusions always subtract.
{
"filters": {
"media_type": { "image": "include" },
"date": { "2026-06": "include" },
"camera_make": { "Canon": "exclude" }
}
}This selects images taken in June 2026 that were not made by a Canon camera.
Filter reference
| Category | Accepted keys |
|---|---|
date, import_date | YYYY, YYYY-MM, or YYYY-MM-DD. |
media_type | image, video, other. |
orientation | landscape, portrait, square. |
format | 1:1, 4:3, 3:2, 16:9, other. |
iso | low, medium, high, or an exact integer such as 400. |
focal_length, focal_length_35mm | super-wide, wide-angle, normal, tele, super-tele, or exact integer millimetres. |
aperture | very-wide, wide, medium, narrow, very-narrow, or an exact f-number such as 1.8. |
shutter_speed | very-fast, fast, normal, slow, long, or exact seconds such as 0.002. |
resolution | sd, hd, fhd, 4k, 6k, 8k. The long edge determines the bucket. |
file_size | tiny, small, medium, large, xlarge, huge. |
person_count | none, single, two, three, small-group, big-group, crowd. |
lens | Exact lens string from metadata. |
color_space | srgb, display-p3, adobe-rgb, prophoto-rgb, uncalibrated. |
bit_depth | String value such as 8, 10, 12, 14, 16, or 32. |
file_type | Lowercase extension such as jpg, cr3, or mp4. |
camera_make | Exact manufacturer string. |
camera_model | Make:Model, for example Canon:EOS R5. |
geography | has_location or no_location. |
versions | has_versions (more than the original version) or no_versions. |
country, state, city, suburb | Stringified location IDs. |
categories | Stringified category IDs; a parent includes its subtree. |
labels | Stringified label IDs. |
collections | Collection UUIDs. |
rating | <criterionId>:<stars>, <criterionId>:unrated, avg:<stars>, any:unrated, or any:incomplete. |
custom | <fieldId>:<value>, <fieldId>:__has__, or <fieldId>:__empty__. |
File-size buckets use decimal bytes: tiny is below 1 MB, small is 1–10 MB,
medium is 10–50 MB, large is 50–250 MB, xlarge is 250 MB–1 GB, and
huge is at least 1 GB.
avg:<stars> buckets each file on the rounded mean of its per-criterion
average ratings. Only system and team-scoped criteria the file has ratings on
count; ratings on collection-scoped criteria are excluded from the API's
average.
ID-based values come from your own records or the app. API v1 does not provide facet or taxonomy-listing endpoints.
Numeric custom fields
custom_ranges is not a tristate map. Its keys are custom-field IDs; either
bound may be omitted.
{
"filters": {
"custom_ranges": {
"7": { "min": 10, "max": 500 }
}
}
}Semantic query controls
Descriptive phrases work well with semantic search. Prefix an individual word
with - to exclude that concept, for example dog on a beach -people.
When q or filters.similar_to is present, filters.max_distance sets a
cosine-distance cutoff from 0 through 1. Lower values are stricter; 1
disables the cutoff. Omitting it uses the server default (0.55).
{
"q": "golden hour",
"filters": { "max_distance": 0.4 }
}Search by image
filters.similar_to takes the id of one of your files and ranks the results
by visual similarity to it: closest first, the seed itself excluded, every
other filter narrowing the ranked set. When similar_to is present, q and
mode are ignored. A seed that has been deleted or has no image embedding
yet (videos and documents never get one) returns an empty list.
{
"filters": {
"similar_to": "019b1a2c-3d4e-7f80-9a1b-2c3d4e5f6a7b",
"labels": { "12": "include" }
}
}