List Account Projects

September 1, 2026

Table of contents

  1. What the counts include
  2. Request Headers
  3. Path Parameters
  4. Query Parameters
  5. Pagination
  6. Responses
  7. Model
  8. Examples
  9. 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/email?cursor=

Request Headers

Authorization: Bearer {API token}

Path Parameters

  • email is 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

  • cursor is optional. Pass the cursor from 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

  • 200 OK

    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 cursor and stoppedOn.

    {
      "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
    }
    
  • 401 Unauthorized

    Invalid API token.

    {
      "error": "Unauthorized"
    }
    
  • 404 Not Found

    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/email and 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 ''}")
    

Try It