ruvnet/ruflo · error · Error

Pinata upload failed: ${response.status} ${error}

Error message

Pinata upload failed: ${response.status} ${error}

What it means

uploadToPinata() POSTs the multipart body to https://api.pinata.cloud/pinning/pinFileToIPFS with pinata_api_key / pinata_secret_api_key headers (the legacy v1 key scheme). A non-ok response is converted into this error carrying the status and Pinata's response text. Typical statuses: 401 invalid/expired key pair, 403 key not authorized for pinning, 429 rate/quota limits, 5xx Pinata outages.

Source

Thrown at v3/@claude-flow/cli/src/transfer/ipfs/upload.ts:180

    Buffer.from(`Content-Disposition: form-data; name="file"; filename="${name}"\r\n`),
    Buffer.from(`Content-Type: application/json\r\n\r\n`),
    content,
    Buffer.from(`\r\n--${boundary}--\r\n`),
  ]);

  const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
    method: 'POST',
    headers: {
      'pinata_api_key': apiKey,
      'pinata_secret_api_key': apiSecret,
      'Content-Type': `multipart/form-data; boundary=${boundary}`,
    },
    body,
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Pinata upload failed: ${response.status} ${error}`);
  }

  const result = await response.json() as { IpfsHash: string; PinSize: number };
  const cid = result.IpfsHash;
  const gateway = options.gateway || 'https://gateway.pinata.cloud';

  console.log(`[IPFS] Upload complete!`);
  console.log(`[IPFS] CID: ${cid}`);

  return {
    cid,
    size: content.length,
    gateway,
    pinnedAt: new Date().toISOString(),
    url: `${gateway}/ipfs/${cid}`,
  };
}

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Match the status: 401/403 -> regenerate the key pair in the Pinata console and update both env vars; 429 -> back off and retry with fewer concurrent uploads
  2. Verify the pair is consistent (old key with new secret fails as 401)
  3. For 5xx, retry with exponential backoff — Pinata outages are usually brief
  4. Fall back to another configured provider (web3.storage token or local IPFS_API_URL node) if Pinata keeps rejecting

Example fix

// before
await uploadToIPFS(content, { provider: 'pinata' }); // 401 -> Pinata upload failed: 401 ...

// after
const isRetryable = (msg: string) => /Pinata upload failed: (429|5\d\d)/.test(msg);
for (let attempt = 0; ; attempt++) {
  try { return await uploadToIPFS(content, { provider: 'pinata' }); }
  catch (e) {
    if (attempt < 3 && isRetryable(String(e?.message))) {
      await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); continue;
    }
    throw e;
  }
}
Defensive patterns

Strategy: fallback

Try / catch

const isPinataFailure = (e: unknown) => /Pinata upload failed: (\d+)/.test(String((e as Error)?.message));

try {
  result = await uploadToIPFS(content, { provider: 'pinata' });
} catch (e) {
  if (!isPinataFailure(e)) throw e;
  const status = Number(/: (\d+)/.exec(String((e as Error).message))?.[1]);
  if (status === 429 || status >= 500) {
    await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); // retryable
    continue;
  }
  result = await uploadToIPFS(content, { provider: 'web3' }); // 401/403: fall back, then fix keys

Prevention

When it happens

Trigger: Uploading with a revoked or mistyped PINATA_API_KEY/SECRET pair; hitting free-tier rate limits during bulk pattern transfers; using legacy v1 API keys on endpoints that now demand JWT signatures; transient Pinata 5xx.

Common situations: Regenerated keys in the Pinata console but stale env vars still loaded; bulk uploads of many CFP files tripping 429; corporate egress filtering returning 403; legacy key auth deprecated in favor of Pinata JWT gateways.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/8457aea5d6b471c7. Report an issue: GitHub.