ruvnet/ruflo · error · Error

Pinata list failed (HTTP ${res.status}): ${JSON.stringify(re

Error message

Pinata list failed (HTTP ${res.status}): ${JSON.stringify(res.data)}

What it means

Thrown by RvfaPublisher.list() when Pinata's GET /data/pinList returns a non-200 status. The message includes the HTTP status and response body. list() enumerates already-pinned items for inventory/discovery and uses the same Bearer JWT as upload.

Source

Thrown at v3/@claude-flow/cli/src/appliance/rvfa-distribution.ts:377

  }

  async publishPatch(patchBuf: Buffer, meta?: PublishMetadata): Promise<PublishResult> {
    const name = meta?.name || `patch-${Date.now()}.rvfp`;
    return this.upload(name, patchBuf, {
      type: 'rvfp-patch', version: meta?.version || '', description: meta?.description || '',
    });
  }

  async fetch(cid: string, outputPath: string): Promise<void> {
    const data = await httpGet(`${this.gw}/ipfs/${cid}`);
    await mkdir(dirname(outputPath), { recursive: true });
    await writeFile(outputPath, data);
  }

  async list(): Promise<PublishedItem[]> {
    const res = await pinataReq('GET', `${this.api}/data/pinList?status=pinned&pageLimit=100`, this.jwt);
    if (res.status !== 200)
      throw new Error(`Pinata list failed (HTTP ${res.status}): ${JSON.stringify(res.data)}`);
    const d = res.data as { rows: Array<{
      ipfs_pin_hash: string; metadata?: { name?: string }; size: number; date_pinned: string;
    }> };
    return (d.rows || []).map((r) => ({
      cid: r.ipfs_pin_hash, name: r.metadata?.name || r.ipfs_pin_hash,
      size: r.size, date: r.date_pinned,
    }));
  }

  async pin(cid: string, name?: string): Promise<void> {
    const body = Buffer.from(JSON.stringify({ hashToPin: cid, pinataMetadata: { name: name || cid } }));
    const res = await pinataReq('POST', `${this.api}/pinning/pinByHash`, this.jwt, body, 'application/json');
    if (res.status !== 200)
      throw new Error(`Pinata pin failed (HTTP ${res.status}): ${JSON.stringify(res.data)}`);
  }
}

// ── Convenience exports ──────────────────────────────────────

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Check the HTTP status in the message; 401/403 means rotate the JWT with read+pin scopes.
  2. For 429, throttle list() calls or cache the result rather than polling.
  3. Confirm the Pinata API base URL (config.apiUrl) is correct and not behind a proxy that rewrites responses.
  4. Retry transient 5xx responses; treat persistent failure as an upstream incident.
Defensive patterns

Strategy: retry

Try / catch

async function safeList(pub: RvfaPublisher) {
  try {
    return await pub.list();
  } catch (e) {
    const msg = (e as Error).message;
    if (/HTTP 401|HTTP 403/.test(msg)) throw new Error('JWT lacks read scope or is expired');
    if (/HTTP 429|HTTP 5\d\d/.test(msg)) throw new Error('Transient Pinata error; retry later');
    throw e;
  }
}

Prevention

When it happens

Trigger: HTTP 401/403 (JWT invalid or lacks read scope), 429 (rate limit on the list endpoint), 400 (malformed query string), or 5xx (Pinata service degradation).

Common situations: JWT permissions were narrowed after construction; the listing query (pageLimit=100) exceeded an account filter; rate limiting from polling list() in a tight loop; Pinata API version drift changing the endpoint contract.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/8cd0df5ec09ba733. Report an issue: GitHub.