This article summarizes the pitfalls and solutions when using the group feature with Pinata’s Files API v3.
Background
There are cases where you want to manage files uploaded to Pinata by groups and retrieve only files belonging to a specific group. For example, storing input images used in an NFT registration form in an “input” group and allowing image selection only from that group.
Pitfalls
1. Legacy API and V3 API File Management Are Separate
Problem: Files uploaded with the legacy API (pinFileToIPFS) cannot be retrieved with the V3 API (/v3/files). The reverse is also true.
Legacy API (pinList) → Only shows files uploaded via legacy
V3 API (/v3/files) → Only shows files uploaded via V3
Solution: Unify on one API. When migrating to V3 API, use V3 for new uploads and consider manually adding existing files to groups or performing a migration.
2. V3 API Endpoints Require the {network} Parameter
Problem: V3 API endpoints require the {network} parameter (public or private).
❌ GET /v3/files?group={group_id}
✅ GET /v3/files/public?group={group_id}
Solution: Use public for regular IPFS files.
3. Group Filter Parameter Names Differ
Problem: Parameter names differ between upload and list retrieval.
| Operation | Parameter Name |
|---|---|
| Upload | group_id |
| List Retrieval | group |
4. Authentication Differences by JWT Type
Problem: JWTs generated with Scoped Keys may not work with the V3 API.
Solution: Generate a new API Key with Admin permissions from the Pinata dashboard.
Implementation Examples
Environment Variables
# Pinata JWT (Admin permissions recommended)
PINATA_JWT=eyJhbGciOiJIUzI1NiIs...
# Group ID (check in Pinata dashboard)
PINATA_INPUT_GROUP_ID=b6543372-dadd-482d-9409-98e9c2e079ff
Upload API (V3)
// app/api/pinata/upload/route.ts
import { NextRequest, NextResponse } from "next/server";
const INPUT_GROUP_ID = process.env.PINATA_INPUT_GROUP_ID;
export async function POST(request: NextRequest) {
const formData = await request.formData();
const file = formData.get("file") as File;
const pinataFormData = new FormData();
pinataFormData.append("file", file);
pinataFormData.append("name", file.name);
pinataFormData.append("network", "public"); // Required
pinataFormData.append("group_id", INPUT_GROUP_ID); // Add to group
const response = await fetch(
"https://uploads.pinata.cloud/v3/files", // V3 upload endpoint
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINATA_JWT}`,
},
body: pinataFormData,
}
);
const data = await response.json();
return NextResponse.json({
success: true,
ipfsHash: data.data.cid, // In V3, it's data.data.cid
});
}
List Retrieval API (V3)
// app/api/pinata/list/route.ts
import { NextRequest, NextResponse } from "next/server";
const INPUT_GROUP_ID = process.env.PINATA_INPUT_GROUP_ID;
export async function GET(request: NextRequest) {
const limit = request.nextUrl.searchParams.get("limit") || "50";
// V3 endpoint (/public is required)
const url = new URL("https://api.pinata.cloud/v3/files/public");
url.searchParams.set("group", INPUT_GROUP_ID); // "group", not "group_id"
url.searchParams.set("limit", limit);
const response = await fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.PINATA_JWT}`,
},
});
const data = await response.json();
// V3 response structure
const files = data.data?.files || [];
return NextResponse.json({
success: true,
images: files.map((file) => ({
ipfsHash: file.cid,
name: file.name,
datePinned: file.created_at,
size: file.size,
})),
});
}
V3 API Endpoint List
| Operation | Method | Endpoint |
|---|---|---|
| File Upload | POST | https://uploads.pinata.cloud/v3/files |
| File List | GET | https://api.pinata.cloud/v3/files/{network} |
| Create Group | POST | https://api.pinata.cloud/v3/groups/{network} |
| Add File to Group | PUT | https://api.pinata.cloud/v3/groups/{network}/{group_id}/ids/{file_id} |
How to Find the Group ID
When you select a group in the Pinata dashboard, the group ID is displayed in the URL:
https://app.pinata.cloud/ipfs/groups/b6543372-dadd-482d-9409-98e9c2e079ff
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the group ID
Alternatively, when retrieving files via the V3 API, you can find it in the group_id field of each file:
{
"data": {
"files": [
{
"id": "019b5f80-c298-7d6f-9ff0-58b2e16bfc38",
"name": "example.jpg",
"cid": "bafybei...",
"group_id": "b6543372-dadd-482d-9409-98e9c2e079ff"
}
]
}
}
Debugging Tips
- First retrieve the list without a group filter: Confirm that files exist
- Check the
group_idin the response: Confirm files belong to the expected group - Check the endpoint URL: Confirm it includes
/publicor/private
Summary
| Item | Legacy API | V3 API |
|---|---|---|
| Upload | api.pinata.cloud/pinning/pinFileToIPFS | uploads.pinata.cloud/v3/files |
| List | api.pinata.cloud/data/pinList | api.pinata.cloud/v3/files/{network} |
| Group Parameter (Upload) | None | group_id |
| Group Parameter (List) | groupId | group |
| Getting CID | response.IpfsHash | response.data.cid |
When migrating to the V3 API, be mindful of these differences during implementation.