List Account Projects
September 1, 2026
Table of contents
- What the counts include
- Request Headers
- Path Parameters
- Query Parameters
- Pagination
- Responses
- Model
- Examples
- Try It
List every project that holds media on one Google Flow account, with a media count, a type breakdown and a date range for each.
A Google Flow account accumulates projects over time, and only one of them is the project your useapi.net configuration currently writes to. The others stay in the account, keep their media, and count against whatever storage the account is subject to — but nothing in the API surfaces them, so they are easy to forget. This endpoint finds them.
Use it as the first step of a cleanup: list the projects here, then read one project’s contents with GET /assets/media/email, then remove what you no longer want with DELETE /assets/email.
What the counts include
The counts here come from your account’s generation history, so they cover generated images and videos only. Uploaded assets are not in that history and are not counted — read those from GET /assets/media/email, which reports them as likelyUploads.
That distinction matters if you are chasing a storage problem: an account can look modest here and still hold hundreds of uploads.
https://api.useapi.net/v1/google-flow/assets/projects/
Request Headers
Authorization: Bearer {API token}
API tokenis required, see Setup useapi.net for details.
Path Parameters
emailis required, URL-encoded. The Google Flow account to scan. Unlike POST /assets, the account is never chosen for you — listing “whichever account the load balancer picked” would not answer any question you could act on.
Query Parameters
cursoris optional. Pass thecursorfrom a previous truncated response to continue scanning from where it stopped. Omit it to start at the newest media.
Pagination
One call walks up to 50 pages of 20 media (1,000 media), and stops early if the scan reaches 45 seconds. Either way the response sets truncated: true and carries a cursor. A stoppedOn: "timeBudget" alongside it tells you the clock ended the scan rather than the page cap.
Keep calling with the returned cursor until truncated is false. A full walk takes a handful of calls — an account holding 4,776 media finished in five. Also stop if a call returns scanned: 0, which ends the walk on the histories where Google’s last page comes back empty.
Each response counts only the media that call scanned. The projects array is built from scratch per request, so continuing with a cursor gives you the counts for the next chunk of history — not a running total. Merge the pages yourself by summing total and byType per projectId and keeping the outermost oldest / newest. The examples below do this.
Responses
-
Projects are sorted by
total, largest first.{ "email": "[email protected]", "projects": [ { "projectId": "6db64a76-90a9-4ecd-99f0-cc524ec2b2af", "isCurrent": false, "total": 616, "byType": { "IMAGE": 616 }, "oldest": "2026-04-26T06:37:30.645104Z", "newest": "2026-05-02T07:38:19.981602Z" }, { "projectId": "3a18acb1-2c6e-478b-bcec-2df19cb8edab", "isCurrent": true, "total": 144, "byType": { "IMAGE": 142, "VIDEO": 2 }, "oldest": "2026-05-21T20:49:27.272324Z", "newest": "2026-07-26T21:07:56.856451Z" } ], "scanned": 840, "truncated": true, "cursor": "CJq...redacted", "stoppedOn": "timeBudget" }A complete scan omits both
cursorandstoppedOn.{ "email": "[email protected]", "projects": [ { "projectId": "54a1808e-fc68-466d-a163-dc0993fb84df", "isCurrent": true, "total": 40, "byType": { "IMAGE": 40 }, "oldest": "2026-03-17T00:08:43.372665Z", "newest": "2026-04-02T02:12:46.103452Z" } ], "scanned": 40, "truncated": false } -
Invalid API token.
{ "error": "Unauthorized" } -
The account named in the path is not configured for this token. List what is configured with GET /accounts.
{ "error": "Google Flow account [email protected] not found" } -
596 Session Error
Google session refresh failed. The account needs to be reconfigured. Delete the account using DELETE /accounts/
emailand add it again by strictly following the procedure in Setup Google Flow.{ "error": "Failed to refresh session: 500 Internal Server Error" }
Model
{
email: string // Echo of the path parameter
projects: [{
projectId: string // Google's project UUID
isCurrent: boolean // True for the project this account currently writes to
total: number // Media counted for this project in THIS response
byType: { // Counts per media type, e.g. { IMAGE: 142, VIDEO: 2 }
[type: string]: number
}
oldest?: string // Earliest createTime seen in this response (ISO 8601)
newest?: string // Latest createTime seen in this response (ISO 8601)
}]
scanned: number // Media examined by this call, including any with no project
truncated: boolean // True when history remains unread
cursor?: string // Present only when truncated — pass back to continue
stoppedOn?: 'timeBudget' // Present only when the 45s budget ended the scan
error?: string // Present only on error responses
}
scanned counts every media the call examined, while the projects totals count only those Google attributed to a project. The two can differ, and scanned being the larger number is normal.
Examples
-
TOKEN='YOUR_API_TOKEN' EMAIL='[email protected]' ENCODED=$(printf %s "$EMAIL" | jq -sRr @uri) BASE="https://api.useapi.net/v1/google-flow/assets/projects/$ENCODED" # Walk every page, then merge the per-page counts into one total per project CURSOR='' echo '[]' > /tmp/pages.json while : ; do URL="$BASE" [ -n "$CURSOR" ] && URL="$BASE?cursor=$(printf %s "$CURSOR" | jq -sRr @uri)" RESP=$(curl -s -H "Authorization: Bearer $TOKEN" "$URL") jq --argjson p "$(jq -c .projects <<<"$RESP")" '. + $p' /tmp/pages.json > /tmp/pages.tmp \ && mv /tmp/pages.tmp /tmp/pages.json [ "$(jq -r .truncated <<<"$RESP")" = 'true' ] || break [ "$(jq -r .scanned <<<"$RESP")" = '0' ] && break CURSOR=$(jq -r .cursor <<<"$RESP") echo "scanned so far, continuing..." done jq 'group_by(.projectId) | map({ projectId: .[0].projectId, isCurrent: .[0].isCurrent, total: map(.total) | add, oldest: map(.oldest) | min, newest: map(.newest) | max }) | sort_by(-.total)' /tmp/pages.json -
const token = 'YOUR_API_TOKEN'; const email = '[email protected]'; const base = `https://api.useapi.net/v1/google-flow/assets/projects/${encodeURIComponent(email)}`; const merged = new Map(); let cursor; let scanned = 0; do { const url = cursor ? `${base}?${new URLSearchParams({ cursor })}` : base; const resp = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }); if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`); const page = await resp.json(); scanned += page.scanned; for (const p of page.projects) { const acc = merged.get(p.projectId) ?? { ...p, total: 0, byType: {} }; acc.total += p.total; for (const [type, n] of Object.entries(p.byType)) acc.byType[type] = (acc.byType[type] ?? 0) + n; if (p.oldest && (!acc.oldest || p.oldest < acc.oldest)) acc.oldest = p.oldest; if (p.newest && (!acc.newest || p.newest > acc.newest)) acc.newest = p.newest; merged.set(p.projectId, acc); } cursor = page.scanned ? page.cursor : undefined; } while (cursor); const projects = [...merged.values()].sort((a, b) => b.total - a.total); console.log(`${scanned} media across ${projects.length} projects`); for (const p of projects) console.log(`${p.total.toString().padStart(5)} ${p.projectId}${p.isCurrent ? ' (current)' : ''}`); -
import requests from urllib.parse import quote token = 'YOUR_API_TOKEN' email = '[email protected]' base = f'https://api.useapi.net/v1/google-flow/assets/projects/{quote(email, safe="")}' headers = {'Authorization': f'Bearer {token}'} merged, cursor, scanned = {}, None, 0 while True: resp = requests.get(base, headers=headers, params={'cursor': cursor} if cursor else None) resp.raise_for_status() page = resp.json() scanned += page['scanned'] for p in page['projects']: acc = merged.setdefault(p['projectId'], {**p, 'total': 0, 'byType': {}}) acc['total'] += p['total'] for media_type, n in p['byType'].items(): acc['byType'][media_type] = acc['byType'].get(media_type, 0) + n if p.get('oldest'): acc['oldest'] = min(acc.get('oldest') or p['oldest'], p['oldest']) if p.get('newest'): acc['newest'] = max(acc.get('newest') or p['newest'], p['newest']) cursor = page.get('cursor') if page['scanned'] else None if not cursor: break projects = sorted(merged.values(), key=lambda p: -p['total']) print(f'{scanned} media across {len(projects)} projects') for p in projects: print(f"{p['total']:>5} {p['projectId']}{' (current)' if p['isCurrent'] else ''}")