NewThe detectors that scored perfect collapsed the hardest under attack.

Endpoints

GET /api/v1/data/benchmarks

The benchmarks your key can query, each with its id, product, title, and the dimension parameters it exposes. Use a benchmark id as the benchmark parameter on the other endpoints.

1benchmarks = client.list_benchmarks().data

GET /api/v1/data/catalog?benchmark=<id>

The filter dimensions a benchmark exposes, each with its allowed values (labeled where the raw value is opaque, e.g. conditions and layers), plus the total item count for your tier. The filters block maps each /api/v1/data/items query parameter to the values allowed for your key, so you can build a valid query without having to memorize the allowed identifiers. Omit benchmark only if your key sees a single benchmark.

1catalog = client.get_catalog(benchmark="synthetic-face-v1")

GET /api/v1/data/items

A filtered list of items for a benchmark (ids and attributes, no storage paths). The filterable dimensions are defined by the benchmark, so you select images with exactly the discrimination it supports, and the parameter set differs per benchmark (for example scene exists only on synthetic-face-v1). Pick a benchmark tab below to see its parameters and values; call /api/v1/data/catalog?benchmark=<id> for the live source of truth. Every parameter is optional; omit a parameter to include all values for that dimension, and each accepts a comma-separated list matching any of the given values (OR within the dimension), e.g. skin_tone=dark,brown or perturbation=jpeg_q70,jpeg_q80. Unknown values for a fixed dimension return 400 with the allowed set.

Parameters for synthetic-face-v1 (tiers: test, live). The filter dimensions and their values differ per benchmark; /catalog is the live source of truth.

Filter dimensions
ParameterMeaningAllowed values
skin_toneSkin-tone band on a 6-level light-to-dark scalevery_light, light, intermediate, tan, brown, dark
genderPerceived gender of the facefemale, male
kindReal (a genuine, unmodified photo) or fake (AI-generated)real, fake
generatorModel that produced the image (fake only; null for real)see /catalog generators
perturbationImage condition applied after generation. Alias: condition. jpeg_q* = JPEG at that quality; blur_*/noise_*/resize_* = that transform; *_pipeline = a re-encode through that platform's upload pipeline (fb=Facebook, ig=Instagram, tt=TikTok, x=X)clean, jpeg_q30/50/70/80/95, blur_1/2/4, noise_5/10, resize_0.5/0.75, fb_pipeline, ig_pipeline, tt_pipeline, x_pipeline
layerCoarse grouping of conditions: clean = no perturbation; layer1 = one lossy transform; layer2 = a platform pipeline; layer2_recropped = a platform pipeline then re-detected and re-croppedclean, layer1, layer2, layer2_recropped
sceneShot context. Synthetic-face onlyindoor, outdoor, selfie
ttp_tacticAdversarial tactic (the attacker's goal) behind the item; null for realimpersonation
ttp_techniqueTechnique that realizes the tactic; null for realidentity_conditioned_synthesis
Lineage and pagination (common to all benchmarks)
ParameterMeaningAllowed values
benchmarkWhich benchmark to query (see /api/v1/data/benchmarks). Accepts the versioned id or its stable code. Omit only if your key sees one benchmarksynthetic-face-v1 or MG-SYN-01
base_idPull every variant of ONE base image. Hold an item's base_id and add perturbation= to fetch another condition of the same imageany item's base_id
source_real_idPull the full lineage descended from one sourced real image (the real, its fakes, and their variants)any item's source_real_id
limitPage size (values above 500 are clamped; response sets limit_clamped:true)1-500 (default 100)
offsetPagination offset (order: created_at ascending)>=0
cursorStable keyset pagination over a growing table (use instead of offset). Pass the response next_cursor for the next pageopaque string from next_cursor
lineagePage over whole lineages: every row of each matched lineage is returned (limit/offset count lineages, not rows)true
exclude_ownedOffset mode only. Omit items you already own. Response adds remaining/owned/total_matching and subset_exhaustedtrue
distinct_identitiesReturn one representative item per person (dedupe by identity_id; opt-in, no default cap). Composes with filters. Response sets mode=distinct_identitiestrue
NOTE
Pulling everything from a selection is a normal outcome, not an error. With exclude_owned=true, once you own every item matching a filter, /items returns a normal 200 with data: [], remaining: 0, and subset_exhausted: true plus a message. Check remaining (or subset_exhausted), not a status code: it means there is nothing new to pull for that selection. Broaden the filter (another cell, generator, or perturbation) to get more. Likewise, downloading an item you already own is not an error, it returns the URL for free with charged: false, already_owned: true.
1# fake, dark or brown cell, JPEG q70 or q80, first 2
2page = client.list_items(
3 benchmark="synthetic-face-v1",
4 kind="fake",
5 skin_tone="dark,brown", # raw client: comma string (iter_items also takes a list)
6 perturbation="jpeg_q70,jpeg_q80",
7 limit=2,
8).result
9for item in page.data:
10 print(item.id, item.skin_tone, item.perturbation)
NOTE
To assemble matched real/fake sets, pull a lineage with source_real_id: it returns the sourced real image plus every fake and perturbed variant derived from it, all sharing that id.

GET /api/v1/data/download/:itemId

Returns a short-lived signed URL for one item. For live keys this debits one credit before the URL is returned. Sending an Idempotency-Key header is optional but recommended: it de-duplicates retries so a repeated request returns the original result without a second charge. Omit it and a retried download is not de-duplicated, so it could be charged again. Fetch the returned url directly (no auth header on that request).

1import urllib.request
2dl = client.download_item(item_id=item.id) # item from the /items call above; Idempotency-Key set for you
3# dl.url is a short-lived signed URL; fetch it with no auth header
4urllib.request.urlretrieve(dl.url, "image.jpg")

GET /api/v1/data/usage

Your current credit balance and tier. It is worth checking your balance before a large pull, so a run does not stop partway with a 402.

1usage = client.get_usage() # usage.tier, usage.balance