List generations
Page through the generations on an account, filter it down, and download them.
On this page
The library is the generations on an account. List it to browse what is there, narrow it with filters, and download individual items.
https://api2.rundiffusion.com/api/v2/libraryRequest
Headers
AuthorizationstringRequired- Bearer token. This endpoint accepts an OAuth device flow token, a Personal API Access Token, or a Company API Access Token. See Authentication.
Authorization: Bearer eyJhbGciOi…
Every query parameter is optional. With none of them you get your own generations on your personal account, newest first.
Which pool of generations you are reading follows team_id, the same way it
does on every endpoint: omit it for your personal account, name a team for that
team's. Here it also takes several team IDs separated by commas, to read across
teams in one page.
curl "https://api2.rundiffusion.com/api/v2/library?limit=24" \
-H "Authorization: Bearer $RUNDIFFUSION_TOKEN"const page = await fetch(
'https://api2.rundiffusion.com/api/v2/library?limit=24',
{ headers: { Authorization: `Bearer ${process.env.RUNDIFFUSION_TOKEN}` } },
).then(r => r.json());page = requests.get(
"https://api2.rundiffusion.com/api/v2/library",
headers={"Authorization": f"Bearer {os.environ['RUNDIFFUSION_TOKEN']}"},
params={"limit": 24},
).json()Paging
Query
cursorstringoptional- Opaque pagination token. When a response has has_more true, pass its next_cursor here to get the following page. Do not construct or parse one.
https://api2.rundiffusion.com/api/v2/library?cursor=eyJhIjoiMjAyNi0… limitintegeroptionaldefault24- Page size, from 1 to 24.
https://api2.rundiffusion.com/api/v2/library?limit=10
Scope
The account selection names the pool; scope decides reach within it. mine,
the default, is your own generations. all is every member's, and it takes the
Can manage everyone's generations and boards (MANAGE_ALL_GENERATIONS)
permission.
Your own rows on a team, the default, need no permission at all:
curl "https://api2.rundiffusion.com/api/v2/library?team_id=Tq8vNc…" \
-H "Authorization: Bearer $RUNDIFFUSION_TOKEN"Whole-team reads take scope=all, and can span teams by naming several. The
permission is checked on every team named: a partly-authorized list is
refused with the failing teams in the error, never quietly narrowed to the ones
that passed, because a page from the narrowed list would be indistinguishable
from the full answer.
curl "https://api2.rundiffusion.com/api/v2/library?team_id=Tq8vNc…,Wm2xLp…&scope=all" \
-H "Authorization: Bearer $RUNDIFFUSION_TOKEN"Query
team_idstringoptionalAccepts multiple- The team or teams to read. Several match any of them. Omit it to read your personal account. Part of the account selection rather than a filter, so it behaves identically on every endpoint.
https://api2.rundiffusion.com/api/v2/library?team_id=Tq8vNc…,Rn4tKp… scopestringoptionaldefaultmine- Whose rows within the selected account. mine is the caller's own generations. all is every member's; it takes a team account and requires MANAGE_ALL_GENERATIONS on every team named in team_id.
https://api2.rundiffusion.com/api/v2/library?team_id=Tq8vNc…&scope=all user_idstringoptionalAccepts multiple- Narrow scope=all to named members. Several match any of them. On the ordinary listing it selects whose generations; with sort=favorited it selects whose favorites. Under scope=mine you are already the answer, so sending it there is a 400 rather than being quietly overridden.
https://api2.rundiffusion.com/api/v2/library?team_id=Tq8vNc…&scope=all&user_id=k3PqV9…
Filters
Query
board_idstringoptionalAccepts multiple- Narrow to a board. Applies to both the ordinary library and the favorites view. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?board_id=Rn4tKp…,Tq8vNc… node_idstringoptionalAccepts multiple- Narrow to generations produced by a board node. Several match any of them. A board can hold the same tool more than once, so this is finer than board_id.
https://api2.rundiffusion.com/api/v2/library?node_id=Nd8pKw…,Nd4kWs… tool_idstringoptionalAccepts multiple- Narrow to generations produced by a tool. IDs come from List tools. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?tool_id=1kCVin…,Tl9xBv… aspect_ratiostringoptionalAccepts multiple- Aspect-ratio bucket, not an exact ratio. Anything that does not land in a listed bucket is OTHER. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?aspect_ratio=16:9,1:1 resolutionstringoptionalAccepts multiple- Resolution bucket measured on the long edge. Percent-encode the plus in 4K+ as 4K%2B, since a bare + means a space in a query string. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?resolution=2K,4K%2B model_family_tool_tag_idstringoptionalAccepts multiple- Narrow to a model-family tool tag. IDs come from List tool tags. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?model_family_tool_tag_id=Mf3xQ8…,Mf7dPq… use_case_tool_tag_idstringoptionalAccepts multiple- Narrow to a use-case tool tag. IDs come from List tool tags. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?use_case_tool_tag_id=Uc9wLm…,Uc2hTn… media_tool_tag_idstringoptionalAccepts multiple- Narrow to a media tool tag. IDs come from List tool tags. Several match any of them.
https://api2.rundiffusion.com/api/v2/library?media_tool_tag_id=Md5kR8…,Md1nVj… start_utcstring (ISO 8601)optional- Start of the window, inclusive, in UTC. A generation created at exactly this instant is included.
https://api2.rundiffusion.com/api/v2/library?start_utc=2026-05-01T00:00:00Z end_utcstring (ISO 8601)optional- End of the window, exclusive, in UTC. A generation created at exactly this instant is NOT included, so a whole month is start_utc=2026-05-01T00:00:00Z with end_utc=2026-06-01T00:00:00Z. Must be after start_utc, or the request is rejected with 400.
https://api2.rundiffusion.com/api/v2/library?start_utc=2026-05-01T00:00:00Z&end_utc=2026-06-01T00:00:00Z
Favorites
Favorites are two parameters because they are two independent questions.
favorited decides which generations come back. sort decides what
order they come back in.
Query
favoritedbooleanoptionaldefaultfalse- When true, return only generations the caller has favorited. It is an ordinary filter, so it stacks with every other one and works at either scope: pair it with scope=all to see your own stars inside a whole team library.
https://api2.rundiffusion.com/api/v2/library?favorited=true sortstringoptionaldefaultcreated- Result order. created is newest-generated first, the same order as the rest of the library. favorited is most-recently-starred first, and requires favorited=true because ordering by when something was favorited is meaningless for things that are not.
https://api2.rundiffusion.com/api/v2/library?favorited=true&sort=favorited
Pick sort by the question you are answering. "What did I star lately" is
sort=favorited. "Show my library, starred only" is the default sort=created,
which leaves the usual ordering alone.
Response
200 OK with one cursor page of generations.
{
"data": [
{
"id": "Kp7mZq…:3f2a9c14-…",
"run_id": "Kp7mZq…",
"result_id": "3f2a9c14-…",
"type": "VID",
"url": "https://rundiffusion.com/...",
"thumb_url": "https://rundiffusion.com/...",
"mime_type": "video/mp4",
"width": 1080,
"height": 1920,
"duration_seconds": 15.1,
"size_bytes": 7729000,
"created": "2026-05-28T22:33:52Z",
"tool_id": "1kCVin…"
}
],
"has_more": true,
"next_cursor": "eyJhIjoiMjAyNi0…",
"last_cursor": null
}Top level
dataarray- The generations on this page, newest first. Empty when nothing matches.
has_moreboolean- Whether more pages exist beyond this one. This is the flag to branch on when paging.
next_cursorstring | null- Pass this back as cursor to fetch the next page. Null on the last page. This is the one to page with.
last_cursorstring | null- Echoes the cursor you sent on this request, so a response can be matched to what produced it. Null on the first page. It points backwards, so paging with it loops.
Generation object
idstring- run_id and result_id joined by a colon. This is how a single item is addressed, and you can pass it back whole as a LIBRARY_REF input without splitting it. Example: Kp7mZq…:3f2a9c14-….
run_idstring- The run that produced this item, as a 20-character opaque string. One run can produce several items.
result_idstring- This item within its run.
typestring- What kind of generation this is. The library serves renderable generations.
urlstring | null- Signed download URL, valid for roughly seven days. Treat it as opaque and re-list to refresh it rather than storing it.
thumb_urlstring | null- Signed URL to a 512x512 WebP thumbnail, same seven-day expiry. Falls back to the same value as url in the short window after a run completes but before the thumbnail is generated, so there is always something displayable.
mime_typestring | null- MIME type of the stored file, for example image/jpeg or video/mp4.
widthinteger | null- Pixel width. Null for formats without one.
heightinteger | null- Pixel height. Null for formats without one.
duration_secondsnumber | null- Runtime in seconds for video. Null for still generations.
size_bytesinteger | null- Size of the file at url, in bytes.
createdstring (ISO 8601)- When it was created, in UTC.
tool_idstring | null- The tool that produced the item. Pass it back as tool_id to find everything else from the same tool.
Paging through everything
let cursor = null;
const items = [];
do {
const url = new URL('https://api2.rundiffusion.com/api/v2/library');
url.searchParams.set('limit', '24');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.RUNDIFFUSION_TOKEN}` },
}).then(r => r.json());
items.push(...page.data);
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);cursor, items = None, []
while True:
params = {"limit": 24}
if cursor:
params["cursor"] = cursor
page = requests.get(
"https://api2.rundiffusion.com/api/v2/library",
headers={"Authorization": f"Bearer {os.environ['RUNDIFFUSION_TOKEN']}"},
params=params,
).json()
items.extend(page["data"])
if not page["has_more"]:
break
cursor = page["next_cursor"]A page always holds as many items as you asked for until the library runs out. Filtering happens before the page is assembled, so a page never comes back short because of generations that were withheld or deleted.
Identifying an item
id is the run_id and result_id joined by a colon, and that pair is how an
item is addressed anywhere else it comes up. Treat it as opaque and pass it
whole: the two halves are also returned separately, so there is never a reason
to split one yourself. Get a generation fetches one by
it, and Delete a generation removes one.
Errors
400VALIDATION_ERROR- A parameter failed validation. The common causes are a malformed team ID list in scope, an end_utc that is not after start_utc, a limit outside 1 to 24, and a value outside an enum. Sending a retired parameter lands here too, with the replacement named in the message.
401UNAUTHENTICATED- Missing, malformed, or expired token. Refresh it and retry.
403PERMISSION_DENIED- The caller cannot read the requested scope: scope=all without MANAGE_ALL_GENERATIONS on every team named in team_id. The error names the teams missing it.
429RATE_LIMITED- Too many requests. Back off and retry per the Retry-After header. See Rate limits.
See Errors for the full envelope and the code list.
