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.

OperationParameter Name
Uploadgroup_id
List Retrievalgroup

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

OperationMethodEndpoint
File UploadPOSThttps://uploads.pinata.cloud/v3/files
File ListGEThttps://api.pinata.cloud/v3/files/{network}
Create GroupPOSThttps://api.pinata.cloud/v3/groups/{network}
Add File to GroupPUThttps://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

  1. First retrieve the list without a group filter: Confirm that files exist
  2. Check the group_id in the response: Confirm files belong to the expected group
  3. Check the endpoint URL: Confirm it includes /public or /private

Summary

ItemLegacy APIV3 API
Uploadapi.pinata.cloud/pinning/pinFileToIPFSuploads.pinata.cloud/v3/files
Listapi.pinata.cloud/data/pinListapi.pinata.cloud/v3/files/{network}
Group Parameter (Upload)Nonegroup_id
Group Parameter (List)groupIdgroup
Getting CIDresponse.IpfsHashresponse.data.cid

When migrating to the V3 API, be mindful of these differences during implementation.