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
- Check the HTTP status in the message; 401/403 means rotate the JWT with read+pin scopes.
- For 429, throttle list() calls or cache the result rather than polling.
- Confirm the Pinata API base URL (config.apiUrl) is correct and not behind a proxy that rewrites responses.
- 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
- Ensure the JWT has the read/pinList scope.
- Cache list() results rather than polling in a tight loop.
- Confirm config.apiUrl points to the correct Pinata API base.
- Retry transient 429/5xx with exponential backoff.
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
- Pinata upload failed (HTTP ${res.status}): ${JSON.stringify(
- Pinata pin failed (HTTP ${res.status}): ${JSON.stringify(res
- Pinata JWT required (config.pinataJwt or PINATA_API_JWT)
- SSRF guard: private/loopback host rejected — ${host}
- SSRF guard: private/loopback host rejected — ${host}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/8cd0df5ec09ba733.
Report an issue: GitHub.