ruvnet/ruflo · error · Error

Pinata API credentials not found. Set PINATA_API_KEY and PIN

Error message

Pinata API credentials not found. Set PINATA_API_KEY and PINATA_API_SECRET.
Get credentials at: https://pinata.cloud

What it means

uploadToPinata() requires an API key pair: options.apiKey/options.apiSecret, falling back to PINATA_API_KEY and PINATA_API_SECRET env vars. If either is missing it throws before building the multipart request, pointing at https://pinata.cloud for credentials. Both values are required — one without the other still fails.

Source

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

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

/**
 * Upload to Pinata
 */
async function uploadToPinata(
  content: Buffer,
  options: IPFSUploadOptions
): Promise<IPFSUploadResult> {
  const apiKey = options.apiKey || process.env.PINATA_API_KEY;
  const apiSecret = options.apiSecret || process.env.PINATA_API_SECRET;

  if (!apiKey || !apiSecret) {
    throw new Error(
      'Pinata API credentials not found. Set PINATA_API_KEY and PINATA_API_SECRET.\n' +
      'Get credentials at: https://pinata.cloud'
    );
  }

  const name = options.name || 'pattern.cfp.json';
  console.log(`[IPFS] Uploading ${content.length} bytes to Pinata...`);

  const boundary = '----WebKitFormBoundary' + crypto.randomBytes(16).toString('hex');
  const metadata = JSON.stringify({ name });

  const body = Buffer.concat([
    Buffer.from(`--${boundary}\r\n`),
    Buffer.from(`Content-Disposition: form-data; name="pinataMetadata"\r\n\r\n`),
    Buffer.from(`${metadata}\r\n`),
    Buffer.from(`--${boundary}\r\n`),
    Buffer.from(`Content-Disposition: form-data; name="file"; filename="${name}"\r\n`),
    Buffer.from(`Content-Type: application/json\r\n\r\n`),

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Set both: export PINATA_API_KEY=... and export PINATA_API_SECRET=... (from https://pinata.cloud) and rerun
  2. Or supply both programmatically: uploadToIPFS(content, { provider: 'pinata', apiKey, apiSecret })
  3. Verify both variables are visible: node -e "console.log(!!process.env.PINATA_API_KEY, !!process.env.PINATA_API_SECRET)"
  4. Prefer the alternate provider set if you only have web3/local-node credentials configured

Example fix

# before
export PINATA_API_KEY=xyz # secret missing -> Pinata API credentials not found

# after
export PINATA_API_KEY=xyz
export PINATA_API_SECRET=abc
npx claude-flow hooks transfer store --provider pinata
Defensive patterns

Strategy: validation

Validate before calling

const apiKey = options.apiKey ?? process.env.PINATA_API_KEY;
const apiSecret = options.apiSecret ?? process.env.PINATA_API_SECRET;
if (!apiKey || !apiSecret) {
  throw new Error('pinata provider selected but credentials incomplete (need PINATA_API_KEY and PINATA_API_SECRET)');
}
await uploadToIPFS(content, { provider: 'pinata', ...options });

Prevention

When it happens

Trigger: Calling uploadToIPFS(content, { provider: 'pinata' }) with only PINATA_API_KEY set (secret missing); neither variable set in the process; passing options.apiKey without options.apiSecret programmatically.

Common situations: Partial credential setup (key copied, secret forgotten); env vars not exported to CI shells; rotating Pinata credentials and updating only one variable; switching from web3.storage to pinata without configuring the new pair.

Related errors


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