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
- Set both: export PINATA_API_KEY=... and export PINATA_API_SECRET=... (from https://pinata.cloud) and rerun
- Or supply both programmatically: uploadToIPFS(content, { provider: 'pinata', apiKey, apiSecret })
- Verify both variables are visible: node -e "console.log(!!process.env.PINATA_API_KEY, !!process.env.PINATA_API_SECRET)"
- 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
- Check both variables together — the API needs the pair, one alone always fails
- Add a startup assertion listing every provider env var your deployment requires
- After rotating Pinata keys, update both variables in every environment that runs transfers
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
- Web3.storage token not found. Set WEB3_STORAGE_TOKEN environ
- Pinata upload failed: ${response.status} ${error}
- Pinata JWT required (config.pinataJwt or PINATA_API_JWT)
- Pinata upload failed (HTTP ${res.status}): ${JSON.stringify(
- Pinata list failed (HTTP ${res.status}): ${JSON.stringify(re
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/070496c5348b70f7.
Report an issue: GitHub.