Files API
buildingList and filter, or fetch one by id. Every result carries its URLs already.
Get files
Two calls, one job: reading files back out. Every result carries its url,
variants[] and srcset inline, so a whole gallery is one request rather than
one per file.
Confirmed files, newest first — or ordered by position when you filter by
entity, so position 0 is the primary image and the array renders as it stands.
1curl "$BASE/v1/files?entityType=product&entityId=prod_abc123&role=gallery" \2 -H "authorization: Bearer $KEY"All parameters are optional and combine with AND.
| Parameter | Matches |
|---|---|
entityType | The attached object's type |
entityId | The attached object's id |
role | The file's role on that object |
ownerUserId | The end user who uploaded it |
visibility | public or private |
variantVisibility | public or private |
page | 1-based; 20 per page |
1{2 "files": [3 {4 "id": "E7iVnWA1LiEpu3u8dRTVK",5 "originalFilename": "sneaker front.jpg",6 "contentType": "image/jpeg",7 "size": 204800,8 "visibility": "public",9 "url": "https://cdn…/files/originals/public/…",10 "variants": [{ "width": 100, "url": "…" }],11 "srcset": "…100w, …300w, …",12 "entity": { "type": "product", "id": "prod_abc123", "role": "gallery", "position": 0 }13 }14 ],15 "total": 260,16 "page": 1,17 "totalPages": 1318}Twenty per page, fixed. It is not a parameter, which is deliberate: a caller
cannot ask for ten thousand rows and neither can a bug. A page past the end
returns an empty array with the real total, not a 404.
Walking a large project
page counts rows to skip, which the database has to do one at a time — fine at
page 2, expensive at page 500. For anything that walks a whole project, use the
cursor instead:
1curl "$BASE/v1/files?cursor=$CURSOR" -H "authorization: Bearer $KEY"Each response carries nextCursor, and its absence means you have reached the
end — so "keep going while there is a cursor" is the whole loop:
1{2 "files": [ … ],3 "total": 259,4 "nextCursor": "MjAyNi0wOC0xN1QxMTowMDo0Ni4wMDBafGFiYzEyMw"5}Treat it as opaque. It happens to be base64 today; anything you decode from it is not a promise.
Filtering by role alone is a scan across every entity in the project rather
than a lookup — legitimate when you want every avatar, but pair it with
entityType when you mean one kind of thing.
One file, when you kept the id yourself.
1{2 "file": { "…": "the file object" },3 "url": "https://cdn…/files/originals/public/…",4 "variants": [{ "width": 100, "url": "…" }],5 "srcset": "…100w, …300w, …"6}The URLs appear both inside file and at the top level; read them from file,
the outer copies are the same strings kept for older callers.
Do not loop this over a listing. If you have the list, you have these URLs already — that is the N+1 the inline URLs exist to prevent.
Errors
1{ "error": "File not found" }The same 404 covers a file that does not exist and one belonging to another
project. Telling you which would confirm that someone else's id is real.