ruvnet/ruflo · error

Pinata pin failed (HTTP )

Error message

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

What it means

POST /pinning/pinByHash returned non-200; the message embeds status and body. pinByHash asks Pinata to pin content already on IPFS by CID, so failures are typically 400 for a malformed CID, 401 for credential problems, or 402/403 plan limits. The CID is passed verbatim from the caller, so format errors upstream land here.

Solutions

  1. Normalize the CID first: strip ipfs:// and validate the format (Qm... base58 or bafy... base32)
  2. 401 → refresh the JWT
  3. 402/403 → free pin slots by unpinning old content or upgrade the plan
  4. Confirm the content is actually retrievable (gateway fetch) before pinning by hash

Example fix

// before
await publisher.pin(`ipfs://${cid}`); // prefix breaks CID parsing → HTTP 400

// after
const cleanCid = cid.replace(/^ipfs:\/\//, '');
await publisher.pin(cleanCid);
Defensive patterns

Strategy: retry

Validate before calling

const cleanCid = cid.replace(/^ipfs:\/\//, '');
const valid = /^(Qm[1-9A-HJ-NP-Za-km-z]{44}|baf[a-z0-9]+)$/.test(cleanCid);
if (!valid) throw new Error(`Invalid CID: ${cid}`);
await publisher.pin(cleanCid);

Type guard

function isValidCid(cid: string): boolean {
  const c = cid.replace(/^ipfs:\/\//, '');
  return /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/.test(c) || /^baf[a-z0-9]{20,}$/.test(c);
}

Try / catch

try {
  await publisher.pin(cleanCid);
} catch (e) {
  const msg = String(e);
  if (/HTTP 400/.test(msg)) throw new Error(`Pinata rejected CID "${cleanCid}" — malformed or unpinable`);
  if (/HTTP (429|5\d\d)/.test(msg)) { /* transient — retry with backoff */ }
  else throw e;
}

Prevention

When it happens

Trigger: `publisher.pin(cid)` with a CID carrying an `ipfs://` prefix, a typo, or wrong base encoding (400); an expired JWT (401); pin quota reached (402/403).

Common situations: Copying CIDs with the ipfs:// scheme attached; truncated base32 CIDs from logs; assuming content is reachable when no IPFS node actually holds it.

Related errors


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

Appendix: source

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

  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 ──────────────────────────────────────
export function createPublisher(config?: Partial<PublishConfig>): RvfaPublisher {
  return new RvfaPublisher({ pinataJwt: config?.pinataJwt, gatewayUrl: config?.gatewayUrl, apiUrl: config?.apiUrl });
}

export async function createAndVerifyPatch(
  options: CreatePatchOptions,
): Promise<{ patch: Buffer; verification: PatchVerifyResult }> {
  const patch = await RvfaPatcher.createPatch(options);
  const verification = await RvfaPatcher.verifyPatch(patch);
  return { patch, verification };
}

View on GitHub (pinned to fa13ee4ad6)