# API Keys
Source: https://docs.pinata.cloud/account-management/api-keys
This page is where you can create, record, and delete API keys for the [Pinata API](/api-reference/introduction). Creating an API key is very simple! Just visit the page to start by click on the API Keys button in the left sidebar, then click "New Key" in the top right.
In the New Key modal you can choose if you want the key to be an Admin key and have full access over every endpoint, or scope the keys by selecting which endpoints you want to use. You can also give it a limited number of uses, and be sure to give it a name to keep track of it. Once you have that filled out click "Create Key" and it will show you the `pinata_api_key`, `pinata_api_secret_key`, and the `JWT`. It's best to click "Copy All" and keep the API key data safe and secure.
Once API keys have been created, you will not be able to see the secret or JWT again
Once you have created your keys you can go ahead and try testing them! You can even use them in our [API Reference section](/api-reference/endpoint/ipfs/test-authentication) :eyes: Or feel free to paste this into your terminal with your `JWT`
```bash cURL
curl --request GET \
--url https://api.pinata.cloud/data/testAuthentication \
--header 'accept: application/json' \
--header 'authorization: Bearer YOUR_PINATA_JWT'
```
If successful you should see this!
```shell bash
{
"message": "Congratulations! You are communicating with the Pinata API!"
}
```
## Managing Keys
From the Keys Page you can see the name of a key, the public key, when it was issues, how many max uses it has, and what permissions it was given.
At any point you can delete an API key by clicking on the Revoke button
# Billing
Source: https://docs.pinata.cloud/account-management/billing
The billing page is where you can upgrade your account, view your current usage, or make changes to your billing info.
## Usage
Heading over to the "Usage" tab, this is where you can view how much of your plan has been used in the month. Gateway Bandwidth and Requests are reset each month on your billing cycle date.
If you reach 80% percent of your usage available, then you will start to
receive emails and warnings that you are close to running out of space. If you
are on the Free plan, then your account will no longer be able to upload or
use the Dedicated Gateway once your account has gone above the limit by 25%.
## Payment Info
Clicking the 'Manage Billing' button will show you the current card in use and if it's the default. If you want to remove a card, then you will need to add a new one first and set it as default before removing the old one.
Pinata currently only accepts standard debit and credit cards
## Plan Selection
From the plan selection you can choose a plan that fits your need the most, whether that be upgrading or downgrading.
If you upgrade in the middle of a billing cycle, then you will only be charged
the prorated amount
# Limits
Source: https://docs.pinata.cloud/account-management/limits
The Private IPFS API and IPFS API have variying limits that users should be aware of.
## API Limits
API rate limits on both the Private IPFS API and IPFS API are currently determined by plan type:
| Plan | Rate Limit |
| ------ | ----------------------- |
| Free | 60 requests per minute |
| Picnic | 250 requests per minute |
| Fiesta | 500 requests per minute |
### Exceptions
The following API calls have increased rate limits:
* Endpoints under `api.pinata.cloud/data/` have a rate limit of 30 requests per minute
* The [Pinning Services API endpoint for listing content](/api-reference/pinning-service-api) has a rate limit of 30 requests per minute
## File Restrictions
HTML files can be uploaded on any plan, but can only be retrieved through a Dedicated Gateway with a [Custom Domain](gateways/dedicated-ipfs-gateways).
Binary files are only allowed on a case by case basis, please contact [team@pinata.cloud](mailto:team@pinata.cloud) for assistance.
## Gateway Rate Limits
At this time there are currently no rate limits for users retrieving content from a dedicated gateway.
## Upload Size Limits
There differing limits on file sizes between the Private IPFS API and IPFS API
### Private IPFS API
Files that are over **100MB** will require using [resumable uploads](/files/uploading-files#resumable-uploads) to complete. If you are using the SDK and the method `upload.file()` this will be handled automatically.
Beyond 100MB the max file size is **25GB** at this time.
### IPFS API
While the upload limit is 25GB we would recommend only uploading up to 15GB per file/folder for reliability reasons. We can try to assist uploads 15GB-25GB but we cannot guarantee success at this time.
There is no aggregate limit for uploads, but each individual upload (whether it is a file or a folder) is limited to **25 GB**.
There is also a file limit size of **10MB** for the pinJSONToIPFS API endpoint.
# Webhooks
Source: https://docs.pinata.cloud/account-management/webhooks
Subscribe to Pinata API events using Webhooks
Through the Pinata App you can create Webhooks for particular events fired from the Pinata API for your account specifically, like uploading or deleting a file.
## Setup
Navigate to the [Webhooks Tab]() inside the Pinata App and click "Add Endpoint" in the top right.

On the New Endpoint page you can put in a URL for your server endpoint that will receive the events. Additionally you can give it a description, which events it will subscribe to, and more advance options like rate limiting. Once you have your options selected click "Create" at the bottom.
If you don't have an endpoint for your app or server yet, you can create a test one by clicking on the `Svix Play` link below the URL input.

Once your Webhook is created you will be taken to the dashboard where you can see incoming logs and events that are fired. Try triggering one of the events that you've selected for your webhook either from the Pinata App or from the API. Then come back to the dashboard to see the result!

## Event Catalog
Check out the link below to browse all the available webhook events you can subscribe to!
Visit our Svix page for all Pinata Webhook events!
## Signature Verification
Webhook signatures let you verify that webhook messages are actually sent by Pinata and not a malicious actor.
For a more detailed explanation, check out this article on [why you should verify webhooks](https://docs.svix.com/receiving/verifying-payloads/why).
To grab the secret for your Webhook locate it on the Webhook Dashboard on the right sidebar. Click on the reveal icon to view the secret and copy it.

Our webhook partner Svix offers a set of useful libraries that make verifying webhooks very simple. Here is a an example using Javascript:
```typescript
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
const headers = {
"webhook-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"webhook-timestamp": "1614265330",
"webhook-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';
const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const payload = wh.verify(payload, headers);
```
For more instructions and examples of how to verify signatures, check out their [webhook verification documentation](https://docs.svix.com/receiving/verifying-payloads/how).
# Workspaces
Source: https://docs.pinata.cloud/account-management/workspaces
Workspaces is only available on the [Picnic and Fiesta plans](https://pinata.cloud/pricing)
Workspaces is a feature that allows you to add multiple people to your account and collaborate in a natural way. With the Picnic plan, you'll get 3 seats to invite your teammates, and with Fiesta you'll get 5 seats, plus the ability to add more at an extra fee.
## Inviting Members
At this time only Workspace Owners can invite members
To get started, login with a paid account and click on the profile button in the top right, then select "Workspaces."
Once at the Workspaces screen, you can type in the email for the person you want to invite. They could already have a Pinata account or could be someone who hasn't signed up yet. Once they sign into their account, they will be prompted to accept the invite on the Workspaces page.
## Switching Workspaces
By default, when you login, you will be put in your account with your Workspace, and you can switch to another Workspace you are member of by clicking on the drop-down menu in the top left corner.
## Removing Members
At this time only Workspace Owners can remove members
If you ever need to remove someone from a Workspace, you can do so from the Workspaces page. Click on the three small dots next to the user's email and click "remove member." You can invite them back at any time!
# Fetch all casts authored by an FID.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/casts/fetch-all-casts-authored-by-an-fid
get /v1/castsByFid
# Fetch all casts by parent cast's FID and Hash OR by the parent's URL
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/casts/fetch-all-casts-by-parent-casts-fid-and-hash-or-by-the-parents-url
get /v1/castsByParent
# Fetch all casts that mention an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/casts/fetch-all-casts-that-mention-an-fid
get /v1/castsByMention
# Get a cast by its FID and Hash.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/casts/get-a-cast-by-its-fid-and-hash
get /v1/castById
# Get a list of all the FIDs
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/fids/get-a-list-of-all-the-fids
get /v1/fids
# Info
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/info/info
get /v1/info
# Get a link by its FID and target FID.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/links/get-a-link-by-its-fid-and-target-fid
get /v1/linkById
# Get all links from a source FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/links/get-all-links-from-a-source-fid
get /v1/linksByFid
# Get all links to a target FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/links/get-all-links-to-a-target-fid
get /v1/linksByTargetFid
# Get a list of on-chain events provided by an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/onchainevents/get-a-list-of-on-chain-events-provided-by-an-fid
get /v1/onChainEventsByFid
# Get a list of signers provided by an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/onchainevents/get-a-list-of-signers-provided-by-an-fid
get /v1/onChainSignersByFid
**Note:** one of two different response schemas is returned based on whether the caller provides the `signer` parameter. If included, a single `OnChainEventSigner` message is returned (or a `not_found` error). If omitted, a non-paginated list of `OnChainEventSigner` messages is returned instead
# Get an on chain ID Registry Event for a given Address
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/onchainevents/get-an-on-chain-id-registry-event-for-a-given-address
get /v1/onChainIdRegistryEventByAddress
# Get a reaction by its created FID and target Cast.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/reactions/get-a-reaction-by-its-created-fid-and-target-cast
get /v1/reactionById
# Get all reactions by an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/reactions/get-all-reactions-by-an-fid
get /v1/reactionsByFid
# Get all reactions to a cast
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/reactions/get-all-reactions-to-a-cast
get /v1/reactionsByCast
# Get all reactions to a target URL
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/reactions/get-all-reactions-to-a-target-url
get /v1/reactionsByTarget
# Get an FID's storage limits.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/storage/get-an-fids-storage-limits
get /v1/storageLimitsByFid
# Get UserData for a FID.
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/userdata/get-userdata-for-a-fid
get /v1/userDataByFid
**Note:** one of two different response schemas is returned based on whether the caller provides the `user_data_type` parameter. If included, a single `UserDataAdd` message is returned (or a `not_found` error). If omitted, a paginated list of `UserDataAdd` messages is returned instead
# Get a list of proofs provided by an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/usernames/get-a-list-of-proofs-provided-by-an-fid
get /v1/userNameProofsByFid
# Get an proof for a username by the Farcaster username
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/usernames/get-an-proof-for-a-username-by-the-farcaster-username
get /v1/userNameProofByName
# Get a list of verifications provided by an FID
Source: https://docs.pinata.cloud/farcaster/hub-api-reference/endpoint/verifications/get-a-list-of-verifications-provided-by-an-fid
get /v1/verificationsByFid
# Hubs
Source: https://docs.pinata.cloud/farcaster/hubs
Hubs are peer to peer servers that work together to download and serve both onchain and offchain data for the Farcaster network. They are key to keeping the network running and for allowing developers to build apps on top of Farcaster. Any time you need to access data from Farcaster such as messages, users, or events, you'll need to access it in the form of a Hub. Sometimes this might take place in the raw [Hub API](/farcaster/hub-api-reference) or through an abstraction.
Pinata offers free to use Hubs that can be accessed via **API**:
```
hub.pinata.cloud
```
Or through **gRPC**:
```
hub-grpc.pinata.cloud
```
Pinata runs its Hubs through a proxy so the standard 2281 port is not required
### Hub API
The Hub API can be used like any other API to fetch data like so:
```bash
curl --request GET \
--url 'https://hub.pinata.cloud/v1/castsByFid?fid=6023&pageSize=10&reverse=true'
```
For more info on how to use the Hub API, check out our [Hub API Reference](/farcaster/api-reference).
### Hub gRPC
The gRPC endpoint can be used inside the [hub-nodejs](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs) library and provides a level of abstraction.
```javascript
import { getSSLHubRpcClient } from '@farcaster/hub-nodejs';
const hubRpcEndpoint = 'hub-grpc.pinata.cloud';
const client = getSSLHubRpcClient(hubRpcEndpoint);
client.$.waitForReady(Date.now() + 5000, (e) => {
if (e) {
console.error(`Failed to connect to ${hubRpcEndpoint}:`, e);
process.exit(1);
} else {
console.log(`Connected to ${hubRpcEndpoint}`);
client.getCastsByFid({ fid: 6023 }).then((castsResult) => {
castsResult.map((casts) => console.log(casts.messages));
client.close();
});
}
});
```
Please consult the [hub-nodejs documentation](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/docs) for more info on how to use it.
# Introduction
Source: https://docs.pinata.cloud/farcaster/introduction
Farcaster is a decentralized social media protocol that runs on a combination of smart contracts on a blockchain and peer-to-peer servers called “Hubs.” It's similar to other social media where you can have an account, follow other people, make posts, reply, and react to other’s posts. The primary difference is that it is a protocol and decentralized, meaning anyone can build their own interface/client on top of Farcaster. For instance, the most popular is [Warpcast](https://warpcast.com) and acts as the default, but there is also [Supercast](https://supercast.xyz) which has special paid features and [Tiles](https://v0.tiles.cool) that is image focused.
Pinata has been watching and [writing](https://medium.com/pinata/how-to-build-a-video-app-on-farcaster-7e1943fcabe1) about Farcaster for a while now, but more recently has [dedicated more resources](https://www.pinata.cloud/blog) to supporting the Farcaster network in the form of free and open Hubs that developers can use, as well as other tools to accelerate apps and Frames. We believe IPFS has the ability to increase the potential of media rich clients building on Farcaster, and we have the goal to make that seamless as possible.
For more information on the Farcaster protocol, check out their docs [here](https://docs.farcaster.xyz). If you’re ready to start building check out our [Hubs page](/farcaster/hubs) or our [Hub API Reference](/farcaster/hub-api-reference)
# Deleting Files
Source: https://docs.pinata.cloud/files/deleting-files
Deleting files from Pinata is simple and easy!
## Deleting Programatically
The SDK has a very simple [delete](/sdk/files/public/delete) method that will allow you to delete an array of files by `id`. Alternatively you can delete a single file with the [API](/api-reference/introduction).
```typescript SDK
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const unpin = await pinata.files.public.delete([
"3c52f1b8-11b1-40d9-849d-5f05a4bbd76d",
"b72886db-9dd4-434c-a1b2-f9d36781ecee"
])
```
```typescript API {6,9,11}
const JWT = "YOUR_PINATA_JWT";
async function delete() {
try {
const fileId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const request = await fetch(
`https://api.pinata.cloud/v3/files/public/${fileId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Deleting All Files
If you find yourself in a position where you need to delete most or all of your files you can use the [Auto Paginate](/sdk/files/public/list#auto-paginate) feature on the SDK to fetch all the IDs of your files and delete them in a few lines of code!
```typescript
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "dweb.mypinata.cloud",
});
async function main() {
try {
let files = [];
for await (const item of pinata.files.public.list()) {
files.push(item.id);
}
const res = await pinata.files.public.delete(files);
} catch (error) {
console.log(error);
}
}
main();
```
## Deleting by Web App
If you are trying to delete files, then you can do so by clicking on the on the 3 dots at the end of the row and selecting "Delete"
Additionally, with our Bulk File Actions tool, you can select and manage multiple files at once - up to 100!
# Groups
Source: https://docs.pinata.cloud/files/file-groups
Private Groups allow you to organize your Pinata content through the Pinata App, SDK, or API, giving you a clearer picture of what your files are being used for.
## SDK and API
With the [SDK](/sdk/groups/public), you can create groups, add files to groups, list details about a group, and more! You can also manage groups using the [API](/api-reference/endpoint/create-group).
### Create a Group
To create a group you can use the [create](/sdk/groups/public/create) method and passing in the `name` you want to give a group.
```typescript SDK
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const group = await pinata.groups.public.create({
name: "My New Group",
});
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const payload = JSON.stringify({
name: "My New Group",
})
const request = await fetch("https://api.pinata.cloud/v3/groups/public", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${JWT}`,
},
body: payload,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This will return the Group info
```typescript SDK
{
id: "01919976-955f-7d06-bd59-72e80743fb95",
name: "Test Private Group",
created_at: "2024-08-28T14:49:31.246596Z"
}
```
```json API
{
"data": {
"id": "01919976-955f-7d06-bd59-72e80743fb95",
"name": "Test Private Group",
"created_at": "2024-08-28T14:49:31.246596Z"
}
}
```
### Add or Remove Files from a Group
There are two ways you can add files to a group. The first is to add the file to a group on [upload](/sdk/upload/public/file).
```typescript SDK {3}
const upload = await pinata.upload.public
.file(file)
.group("b07da1ff-efa4-49af-bdea-9d95d8881103")
```
```typescript API {13}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public")
formData.append("group", "b07da1ff-efa4-49af-bdea-9d95d8881103")
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
Another option is to add files after the fact using the [addFiles](/sdk/groups/public/add-files) method.
```typescript SDK
const upload = await pinata.groups.public.addFiles({
groupId: "b07da1ff-efa4-49af-bdea-9d95d8881103",
files: [
"0ed5738f-07e7-4587-81fb-f04f8be15d77",
"a277dc29-2ca3-4dfb-aeb9-3f2b23e956f7"
]
})
```
```typescript API {6,8,11}
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const groupId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const fileId = "521f23f3-2749-4611-b757-3155b40ff570"
const request = await fetch(
`https://api.pinata.cloud/v3/groups/public/${groupId}/ids/${fileId}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
Removing files can be done the exact same way with the [removeFiles](/sdk/groups/public/remove-files) method.
```typescript SDK
const upload = await pinata.groups.public.removeFiles({
groupId: "b07da1ff-efa4-49af-bdea-9d95d8881103",
files: [
"0ed5738f-07e7-4587-81fb-f04f8be15d77",
"a277dc29-2ca3-4dfb-aeb9-3f2b23e956f7"
]
})
```
```typescript API {6,8,11}
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const groupId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const fileId = "521f23f3-2749-4611-b757-3155b40ff570"
const request = await fetch(
`https://api.pinata.cloud/v3/groups/public/${groupId}/ids/${fileId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Get a Group
To fetch details of an already existing group you can use the [get](/sdk/groups/public/get) and pass in the `groupId`.
```typescript SDK
const groups = await pinata.groups.public.get({
groupId: "3778c10d-452e-4def-8299-ee6bc548bdb0",
});
```
```typescript API {6,8,11}
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const groupId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const request = await fetch(
`https://api.pinata.cloud/v3/groups/public/${groupId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This will return the same group info received upon creation.
```typescript SDK
{
id: "0191997b-ca28-79e8-9dbc-a8044ad3e547",
name: "My New Group 5",
created_at: "2024-08-28T14:55:12.448504Z",
}
```
```json APi
{
"data": {
"id": "0191997b-ca28-79e8-9dbc-a8044ad3e547",
"name": "My New Group 5",
"created_at": "2024-08-28T14:55:12.448504Z"
}
}
```
### List All Groups
If you want to get all Groups or filter through them, you can use the [list](/sdk/groups/public/list) method.
```typescript SDK
const groups = await pinata.groups.public.list()
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const url = "https://api.pinata.cloud/v3/groups/public"
const request = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
Results can be filtered with the following queries.
#### name
* Type: `boolean`
Filters groups based on the group name
```typescript SDK
const groups = await pinata.groups.public
.list()
.name("SDK")
```
```typescript API
const url = "https://api.pinata.cloud/v3/groups/public?name=SDK"
```
#### limit
* Type: `number`
Limits the number of results
```typescript SDK
const groups = await pinata.groups.public
.list()
.limit(10)
```
```typescript API
const url = "https://api.pinata.cloud/v3/groups/public?limit=10"
```
This will return an array of Groups and their respective info:
```typescript SDK
{
groups: [
{
id: "0191997b-ca28-79e8-9dbc-a8044ad3e547",
name: "My New Group 5",
created_at: "2024-08-28T14:55:12.448504Z",
}
],
next_page_token: "MDE5MWIzNGMtMWNmNy03MzExLThmMjYtZmZlZDMzYTVlY"
}
```
```json API
{
"groups": [
{
"id": "0191997b-ca28-79e8-9dbc-a8044ad3e547",
"name": "My New Group 5",
"created_at": "2024-08-28T14:55:12.448504Z"
}
],
"next_page_token": "MDE5MWIzNGMtMWNmNy03MzExLThmMjYtZmZlZDMzYTVlY"
}
```
### Updating a Group
You can update the name of a group using either the SDK or the API.
```typescript SDK
const groups = await pinata.groups.public.update({
groupId: "3778c10d-452e-4def-8299-ee6bc548bdb0",
name: "My New Group 2",
});
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const groupId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const payload = JSON.stringify({
name: "My New Group 2",
})
const request = await fetch(
`https://api.pinata.cloud/v3/groups/public/${groupId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${JWT}`,
},
body: payload
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This will return the updated Group info.
```typescript SDK
{
id: "3778c10d-452e-4def-8299-ee6bc548bdb0",
name: "My New Group 2",
created_at: "2024-08-28T20:58:46.96779Z"
}
```
```json API
{
"data": {
"id": "01919ac8-a6f5-7e8e-a8a2-6cfe00122b90",
"name": "Updated Name",
"created_at": "2024-08-28T20:58:46.96779Z"
}
}
```
### Delete a Group
Deleting a Group that has CIDs inside of it will not unpin/delete the files. Please use the [delete](/sdk/files/public/delete) method to actually delete a file from your account
To delete a Group you can use the [delete](/sdk/groups/public/delete) method and pass in the `groupId`.
```typescript SDK
const groups = await pinata.groups.public.delete({
groupId: "3778c10d-452e-4def-8299-ee6bc548bdb0",
});
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const groupId = "e0b102e9-d481-4192-ab44-b8f7ff010e9a"
const request = await fetch(
`https://api.pinata.cloud/v3/groups/public/${groupId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
If successful the endpoint will return an `OK` response.
# Key-Values
Source: https://docs.pinata.cloud/files/key-values
A unique and powerful feature included with the IPFS API and Private IPFS API is the key-value store. Anytime you upload or update a file you can store up to 10 key-value pairs.
```typescript SDK {3-6}
const upload = await pinata.upload.public
.file(file)
.keyvalues({
env: "prod",
userId: "abc123"
})
```
```typescript API {13-18,20}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public")
const keyvalues = JSON.stringify({
keyvalues: {
env: "prod",
userId: "abc123"
}
})
formData.append("keyvalues", keyvalues)
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This small yet powerful feature allows you to remove the need for an external database in most cases. We like to call this paradigm **[File-Centric Architecture](https://pinata.cloud/blog/using-file-centric-architecture-to-build-simple-and-capable-apps/)**, where apps and their structure revolves around the files themselves. This creates a molecule like structure and keeps the data related to the file close by.

## Creating
Creating a new key-value for a file can be done in two ways:
### Uploading a File
By including the key-values as part of the upload [method](/sdk/upload/public/file) or [endpoint](/api-reference/endpoint/upload-a-file) and the file and the key-values will be created at the same time.
```typescript SDK {3-8}
const upload = await pinata.upload.public
.file(file)
.keyvalues({
env: "prod",
userId: "abc123"
})
```
```typescript API {13-18,20}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public");
const keyvalues = JSON.stringify({
keyvalues: {
env: "prod",
userId: "abc123"
}
})
formData.append("keyvalues", keyvalues)
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Updating an Existing File
If you've already uploaded a file and want to add a key-value you can do so with the update [method](/sdk/files/public/update) or [endpoint](/api-reference/endpoint/update-file).
```typescript SDK {3-7}
const update = await pinata.files.public.update({
id: "2b4ee88d-1032-4e4e-a373-97d1ab127f16", // Target File ID
keyvalues: {
env: "prod",
userId: "abc123"
}
})
```
```typescript API {6,8-13,15-21}
const JWT = "YOUR_PINATA_JWT";
async function update() {
try {
const fileId = "2b4ee88d-1032-4e4e-a373-97d1ab127f16"
const data = JSON.stringify({
keyvalues: {
env: "prod",
userId: "abc123"
}
})
const request = await fetch(`https://api.pinata.cloud/v3/files/public/${fileId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: data,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Retrieving
Since key-values exist with files, you can retrieve them by listing files either through the SDK [method](/sdk/files/public/list) or API [endpoint](/api-reference/endpoint/list-files), and filtering results by key-value. The operator will always be `===`.
You can chain multiple key-value queries together and it will only return files that meet both values.
```typescript SDK {3-5}
const files = await pinata.files.public
.list()
.keyvalues({
user: "abc123"
})
```
```typescript API {5}
const JWT = "YOUR_PINATA_JWT";
async function list() {
try {
const request = await fetch(`https://api.pinata.cloud/v3/files/public?metadata[user]=123`, {
method: "GET",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Updating
The key-value system will automatically detect if you are replacing an existing value for a given key. For example, if you have a key of `env` with a value of `prod`, if you make an update of `env: "dev"` it will replace the old value. If the key does not exist then it will make a new key-value entry.
```typescript SDK {4}
const update = await pinata.files.public.update({
id: "2b4ee88d-1032-4e4e-a373-97d1ab127f16", // Target File ID
keyvalues: {
env: "dev", // Previously `prod`
}
})
```
```typescript API {10}
const JWT = "YOUR_PINATA_JWT";
async function update() {
try {
const fileId = "2b4ee88d-1032-4e4e-a373-97d1ab127f16"
const data = JSON.stringify({
keyvalues: {
env: "dev", // Previously `prod`
}
})
const request = await fetch(`https://api.pinata.cloud/v3/files/public/${fileId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: data,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Deleting
You can remove a key-value entry by making the value `null`.
```typescript SDK {4}
const update = await pinata.files.public.update({
id: "2b4ee88d-1032-4e4e-a373-97d1ab127f16", // Target File ID
keyvalues: {
env: null, // Deletes the `env` key-value entry
}
})
```
```typescript API {6,8-13,15-21}
const JWT = "YOUR_PINATA_JWT";
async function update() {
try {
const fileId = "2b4ee88d-1032-4e4e-a373-97d1ab127f16"
const data = JSON.stringify({
keyvalues: {
env: null, // Deletes the `env` key-value entry
}
})
const request = await fetch(`https://api.pinata.cloud/v3/files/public/${fileId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: data,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Further Reading
Check out some of our reading material on some of the possibilities of key-values and file-centric architecture!
# List & Query Files
Source: https://docs.pinata.cloud/files/listing-files
Pinata gives you the ability to query uploaded files based on different filters and attributes such as name, [key-values](/files/key-values), date, and more. This is different from retrieving the actual contents of a file, which you can learn more about [here](/gateways/retrieving-files).
## Basic Usage
You can either use the [SDK](/sdk/files/public/list) or the [API](/api-reference/endpoint/list-files) as see in the examples below.
```typescript SDK
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const files = await pinata.files.public.list()
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function files() {
try {
const url = "https://api.pinata.cloud/v3/files/public",
const request = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This will return an array of file objects
```typescript SDK
{
files: [
{
id: "dd5f8888-bf15-4559-b8a2-6c626869507f",
name: "Hello Files API",
cid: "bafybeifq444z4b7yqzcyz4a5gspb2rpyfcdxp3mrfpigmllh52ld5tyzwm",
size: 4861678,
number_of_files: 1,
mime_type: "TODO",
group_id: null,
created_at: "2024-08-27T14:57:51.485934Z",
},
{
id: "e2057aa3-7b6c-4a45-b785-12ba297bcbd0",
name: "Quickstart.png",
cid: "bafkreiebavn2jzkqh3ehy4pkqkdi2otnho6gbcffkeqnunk2lw5nmnwaea",
size: 223548,
number_of_files: 1,
mime_type: "TODO",
group_id: "5f8adce6-7312-46e0-90f7-13896bed297d",
created_at: "2024-08-28T23:46:07.823118Z",
},
{
id: "ac5308a1-de49-40a3-9f5c-d20f1bb6206d",
name: "hello.txt",
cid: "bafkreiffsgtnic7uebaeuaixgph3pmmq2ywglpylzwrswv5so7m23hyuny",
size: 11,
number_of_files: 1,
mime_type: "TODO",
group_id: null,
created_at: "2024-08-29T02:23:02.735018Z",
}
],
next_page_token: "MDE5MWIzNGMtMWNmNy03MzExLThmMjYtZmZlZDMzYTVlY"
}
```
```json API
{
"files": [
{
"id": "dd5f8888-bf15-4559-b8a2-6c626869507f",
"name": "Hello Files API",
"cid": "bafybeifq444z4b7yqzcyz4a5gspb2rpyfcdxp3mrfpigmllh52ld5tyzwm",
"size": 4861678,
"number_of_files": 1,
"mime_type": "TODO",
"group_id": null,
"created_at": "2024-08-27T14:57:51.485934Z"
},
{
"id": "e2057aa3-7b6c-4a45-b785-12ba297bcbd0",
"name": "Quickstart.png",
"cid": "bafkreiebavn2jzkqh3ehy4pkqkdi2otnho6gbcffkeqnunk2lw5nmnwaea",
"size": 223548,
"number_of_files": 1,
"mime_type": "TODO",
"group_id": "5f8adce6-7312-46e0-90f7-13896bed297d",
"created_at": "2024-08-28T23:46:07.823118Z"
},
{
"id": "ac5308a1-de49-40a3-9f5c-d20f1bb6206d",
"name": "hello.txt",
"cid": "bafkreiffsgtnic7uebaeuaixgph3pmmq2ywglpylzwrswv5so7m23hyuny",
"size": 11,
"number_of_files": 1,
"mime_type": "TODO",
"group_id": null,
"created_at": "2024-08-29T02:23:02.735018Z"
}
],
"next_page_token": "MDE5MWIzNGMtMWNmNy03MzExLThmMjYtZmZlZDMzYTVlY"
}
```
## Filters
When listing files there a few ways you can filter the results
### name
* Type: `string`
Filter results based on name
```typescript SDK {3}
const files = await pinata.files.public
.list()
.name("pinnie")
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?name=pinnie"
```
### group
* Type: `string`
Filter results based on group ID
```typescript SDK {3}
const files = await pinata.files.public
.list()
.group("5b56981c-7e5b-4dff-aeca-de784728dddb")
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?group=5b56981c-7e5b-4dff-aeca-de784728dddb"
```
### noGroup
* Type: `boolean`
Filter results to only show files that are not part of a group
```typescript SDK {3}
const files = await pinata.files.public
.list()
.noGroup(true)
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?group=null"
```
### cid
* Type: `string`
Filter results based on CID
```typescript SDK{3}
const files = await pinata.files.public
.list()
.cid("bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4")
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?cid=bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4"
```
### mimeType
* Type: `string`
Filter results based on mime type
```typescript SDK {3}
const files = await pinata.files.public
.list()
.mimeType("image/png")
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?mimeType=image/png"
```
### keyvalues
* Type: `Record`
Filter results based on keyvalue pairs in metadata
```typescript SDK {3}
const files = await pinata.files.public
.list()
.keyvalues({
env: "prod"
})
```
```typescript API
const url = "https://api.pinata.cloud/v3/files/public?keyvalues[env]=prod"
```
### order
* Type: `"ASC" | "DESC"`
Order results either ascending or descending by created date
```typescript SDK {3}
const files = await pinata.files
.list()
.order("ASC")
```
```typescript API
const url = "https://api.pinata.cloud/v3/files?order=ASC"
```
### limit
* Type: `number`
Limit the number of results
```typescript SDK {3}
const files = await pinata.files
.list()
.limit(10)
```
```typescript API
const url = "https://api.pinata.cloud/v3/files?limit=10"
```
### cidPending
* Type: `boolean`
Filters results and only returns files where `cid` is still `pending`
```typescript SDK {3}
const files = await pinata.files
.list()
.cidPending(true)
```
```typescript API
const url = "https://api.pinata.cloud/v3/files?cidPending=true"
```
## Auto Paginate (SDK)
The `list` method has an auto pagination feature that is triggered when used inside a `for await` iterator
```typescript
for await (const item of pinata.files.list() {
console.log(item.id);
}
```
Works like magic ✨
# Presigned URLs
Source: https://docs.pinata.cloud/files/presigned-urls
There are situations where you may need to upload a file client side instead of server side, but doing so might risk exposing an API key. To solve this you can create a presigned upload URL on the server and then pass it to the client for it to be consumed. Creating signed upload URLs can be done with either the [Files SDK](/sdk/upload/public/create-signed-url) or the [API](/api-reference/endpoint/create-signed-upload-url), and you can designate how long the URL is valid for, how large the file can be, the type of file allowed, or extra metadata like a name and [keyvalues](/files/key-files).
For a more robust example, check out our guides on Hono and React!
## Usage
Setting up a server side API endpoint might look something like this:
```typescript SDK
import { type NextRequest, NextResponse } from "next/server";
import { pinata } from "@/utils/config"; // Import the Pinata SDK instance
export const dynamic = "force-dynamic";
export async function GET() {
// Handle your auth here to protect the endpoint
try {
const url = await pinata.upload.public.createSignedURL({
expires: 30, // The only required param
mimeTypes: ["text/*"], // Optional restriction for certain file types
maxFileSize: 5000000 // Optional file size limit
})
return NextResponse.json({ url: url }, { status: 200 }); // Returns the signed upload URL
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating signed URL:" }, { status: 500 });
}
}
```
```typescript API
import { type NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() {
// Handle your auth here to protect the endpoint
try {
// Prepare payload data for request
const data = JSON.stringify({
network: "public",
expires: 30, // Number of seconds the signed url is good for
filename: "Client file", // Optional name
allow_mime_types: [ "text/*" ], // Optional array of allowed file types
max_file_size: 5000000 // optional max file size
})
// send request and parse response
const urlRequest = await fetch("https://uploads.pinata.cloud/v3/files/sign", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.PINATA_JWT}`
},
body: data
})
const urlResponse = await urlRequest.json()
return NextResponse.json({ url: urlResponse.data }, { status: 200 }); // Returns the key data
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating API Key:" }, { status: 500 });
}
}
```
Then back on the client side code, you can upload using the signed URL instead of the regular upload endpoint.
If you're using the SDK you can use the `.url()` parameter on any of the upload methods and pass in the signed upload URL there. If you are using the API you can simply make the upload request using the signed URL as the endpoint.
```typescript SDK {3}
const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL from your server
const urlResponse = await urlRequest.json(); // Parse response
const upload = await pinata.upload.public
.file(file)
.url(urlResponse.url); // Upload the file with the signed URL
```
```typescript API {1-2,13}
const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL
const urlResponse = await urlRequest.json(); // Parse response
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public")
const request = await fetch(urlResponse.url, {
method: "POST",
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Reference
Below are the availble parameters for presigned URLs
### expires
* Type: `number`
The number of seconds the signed URL should be valid for
```typescript {2}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
});
```
### name (Optional)
* Type: `string`
Name for the file to be uploaded
```typescript {3}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
name: "My Cool File"
});
```
### mimeTypes (Optional)
* Type: `string[]`
Specify allowed file mime types and prevent uploads from files that do not match. Accepts wildcard mime types as well.
```typescript {3-6}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
mimeTypes: [
"image/*",
"application/json"
]
});
```
### maxFileSize (Optional)
* Type: `number`
Restrict upload to a specified file size in `bytes`
```typescript {3}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
maxFileSize: 50000 // 50kb
});
```
### groupId (Optional)
* Type: `string`
The target groupId the file would be uploaded to
```typescript {3}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
groupId: "ad4bc3bf-8794-49e7-94ff-fea1ce745779"
});
```
### keyvalues (Optional)
* Type: `Record`
Keyvalue pairs for the uploaded file
```typescript {3-5}
const url = await pinata.upload.public.createSignedURL({
expires: 30,
keyvalues: {
env: "prod"
}
});
```
### date (Optional)
* Type: `number`
A UNIX timestamp of the date a URL is signed
```typescript {1-2,6}
const date = Math.floor(new Date().getTime() / 1000);
//date: 1724943711
const url = await pinata.upload.public.createSignedURL({
expires: 30,
date: date
});
```
# Private IPFS
Source: https://docs.pinata.cloud/files/private-ipfs
IPFS has traditionally been a fully public network, so anything you pin can be accessed by anyone if they have the CID. While this is a benefit for a majority of blockchain applications, there are still cases where true privacy is needed. This is why Pinata has built Private IPFS, a new service that allows you to keep content private and only share when authorized.
## Private vs Public Network
Everything in the SDK and API have been separated by two networks: `public` and `private`. This means files and groups will be in separate resources and will be accessed by designating the network in either the SDK method or API route.
### Uploading
```typescript SDK
// Uploads a file to Public IPFS
const publicUpload = await pinata.upload.public.file(file)
// Uploads a file to Private IPFS
const privateUpload = await pinata.upload.private.file(file)
```
```typescript {11} API
const JWT = "PINATA_JWT";
async function uploadFile() {
try {
const text = "Hello World!";
const blob = new Blob([text], { type: "text/plain" });
const file = new File([blob], "hello-world.txt");
const data = new FormData();
data.append("file", file);
// Upload a file to Public IPFS
data.append("network", "public")
const request = await fetch(
"https://uploads.pinata.cloud/v3/files",
{
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: data,
}
);
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Listing and Querying
```typescript SDK
// List public files
const files = await pinata.files.public.list()
// List private files
const files = await pinata.files.private.list()
```
```typescript {6,8} API
const JWT = "YOUR_PINATA_JWT";
async function files() {
try {
// Designate network to list either public or private files
const network = "public"
const url = `https://api.pinata.cloud/v3/files/${network}`,
const request = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${JWT}`,
}
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Groups
```typescript SDK
// Create a public group
const group = await pinata.groups.public.create({
name: "My Public Group",
});
// Create a private group
const group = await pinata.groups.private.create({
name: "My Private Group",
});
```
```typescript {5,7} API
const JWT = "YOUR_PINATA_JWT";
async function group() {
try {
const network = "public"
const endpoint = `https://api.pinata.cloud/v3/groups/${network}`
const payload = JSON.stringify({
name: "My Public Group",
})
const request = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${JWT}`,
},
body: payload,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Accessing Private Files
With Public IPFS you can simply access a file through an IPFS Gateway with a CID. Since Private IPFS does not announce to the public IPFS network the only way you can access them is with a temporary access link. This can be generated with either the SDK or the API and set to expire after a designated number of seconds.
```typescript SDK
const url = await pinata.gateways.private.createAccessLink({
cid: "bafkreib4pqtikzdjlj4zigobmd63lig7u6oxlug24snlr6atjlmlza45dq", // CID of the file to access
expires: 30, // Number of seconds the link is valid for
});
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function createAccessLink() {
try {
// Endpoint for creating access links
const url = "https://api.pinata.cloud/v3/files/download_link"
// CID of the file you want to access
const cid = "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4"
// Current Date
const date = Math.floor(new Date().getTime() / 1000);
const data = JSON.stringify({
url: `https://example.mypinata.cloud/files/${cid}`, // Gateway URL for the file using /files/ in the path
expires: 180, // Number of seconds the link will be valid for
date: date, // Current date of request
method: "GET" // GET for accessing file
})
const request = await fetch(url, {
method: "POSt",
headers: {
Authorization: `Bearer ${JWT}`,
"Content-Type": "application/json"
},
body: data
});
const response = await request.json();
return response.data
} catch (error) {
console.log(error);
}
}
```
## Example Apps
Pinata has built several example apps and tutorials you can reference to see how Private IPFS enables token gated experiences in blockchain and crpto contexts.
GitHub repo for [CONCEALMINT](https://concealmint.com), an app for creating and access Private NFTs
By using Private IPFS you can gate acceess to files based on NFT ownership
Sell digital content inside Farcaster frames and keeping it secure through Private IPFS
Add simple yet private logging to your crypto app using Private IPFS
See the possibilities of file management using Groups and Private IPFS
# Signatures
Source: https://docs.pinata.cloud/files/signatures
Learn how to use Pinata to cryptographically sign CIDs
In a post-AI world it will become more and more evident that every piece of content will need a cryptographic signature to verify it's authenticity. Pinata is taking steps in this direction with the [Signatures API](/sdk/signatures/public) and the [Content Addressable Gateway Plugin](/gateways/plugins/content-addressable).
## Signature Standard
Pinata is using the EIP-712 signature standard for signing CIDs with the following domain and types.
```typescript
export const domain = {
name: "Sign Content",
version: "1.0.0",
chainId: 1,
} as const;
export const types = {
Sign: [
{ name: "address", type: "address" },
{ name: "cid", type: "string" },
{ name: "date", type: "string" },
],
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
],
};
```
### address
* Type: `address`
The wallet address of the user singing the CID
```
0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826
```
### cid
* Type: `string`
The target CID to be signed
```
bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4
```
### date
* Type: `string`
The date the target CID was first uploaded to Pinata
```
2024-07-29T18:29:47.355Z
```
## Creating a Signature
In order to sign a CID you can use any library that support EIP-712 signing, like the example below with [viem](https://viem.sh/docs/actions/wallet/signTypedData).
```typescript example.ts
import { account, walletClient } from './config'
import { domain, types } from './data'
const signature = await walletClient.signTypedData({
account,
domain,
types,
primaryType: 'Sign',
message: {
address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
cid: 'bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4',
date: "2024-07-29T18:29:47.355Z"
}
})
```
```typescript data.ts
export const domain = {
name: "Sign Content",
version: "1.0.0",
chainId: 1,
} as const;
export const types = {
Sign: [
{ name: "address", type: "address" },
{ name: "cid", type: "string" },
{ name: "date", type: "string" },
],
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
],
};
```
```typescript config.ts
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
export const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!),
})
export const [account] = await walletClient.getAddresses()
↑ JSON-RPC Account
// export const account = privateKeyToAccount(...)
↑ Local Account
```
## Adding Signature to CID
In order to attach a signature to a CID the following requirements must be met:
* The CID being signed is owned by the signer
* The CID being signed was first uploaded by the signer
* The CID must not already have an existing signature with Pinata
After creating the signature with the previous step you can add it to the CID with the [add](/sdk/signatures/public/add) method in the SDK.
```typescript
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const signature = await pinata.signatures.public.add({
cid: "QmXGeVy9dVwfuFJmvbzz8y4dYK1TdxXbDGzwbNuyZ5xXSU",
signature: "0x1b...911b",
address: "0xB3899AA8E13172E48D44CE411b0c4c2f08730Dc6"
});
```
## Getting a Signature for a CID
There are two ways you can an existing signature for a CID: the [get](/sdk/signatures/public/get) method in the SDK or the [Content Addressable Gateway Plugin](/gateways/plugins/content-addressable).
### Content Addressable Gateway Plugin
After [installing the plugin](/gateways/plugins/getting-started#installing-plugins) you can simply request a CID through the Dedicated Gateway and get the signature in the header `pinata-signauture`.
```typescript
const signatureReq = await fetch(
`https://.mypinata.cloud/ipfs/`,
{
method: "HEAD",
}
);
const signature = signatureReq.headers.get("pinata-signature");
```
### SDK
You can also use the [get](/sdk/signatures/public/get) method to get a signature for a given CID.
This method will check all CIDs on Pinata and will return a signature if it exists
```typescript
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const signature = await pinata.signatures.public.get(
"QmXGeVy9dVwfuFJmvbzz8y4dYK1TdxXbDGzwbNuyZ5xXSU"
);
```
## Verifying a Signature
Since the signatures are using the EIP-712 standard you can use a library like [Viem](https://viem.sh/docs/utilities/verifyTypedData) to verify with the same typed data used to create it.
```typescript example.ts
import { account, walletClient } from './config'
import { domain, types } from './data'
import { verifyTypedData } from 'viem'
const CID = "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4"
const signatureReq = await fetch(
`https://example-gateway.mypinata.cloud/ipfs/${CID}`,
{
method: "HEAD",
}
);
const signature = signatureReq.headers.get("pinata-signature");
const valid = await verifyTypedData({
address: account.address,
domain,
types,
primaryType: 'Sign',
message: {
address: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
cid: 'bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4',
date: "2024-07-29T18:29:47.355Z"
},
signature,
})
```
```typescript data.ts
export const domain = {
name: "Sign Content",
version: "1.0.0",
chainId: 1,
} as const;
export const types = {
Sign: [
{ name: "address", type: "address" },
{ name: "cid", type: "string" },
{ name: "date", type: "string" },
],
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
],
};
```
```typescript config.ts
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
export const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!),
})
export const [account] = await walletClient.getAddresses()
↑ JSON-RPC Account
// export const account = privateKeyToAccount(...)
↑ Local Account
```
## Removing a Signature for a CID
To delete an existing signautre for a given CID you can use the [delete](/sdk/signatures/public/delete) method.
```typescript
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const signature = await pinata.signatures.public.delete(
"QmXGeVy9dVwfuFJmvbzz8y4dYK1TdxXbDGzwbNuyZ5xXSU"
);
```
# Uploading Files
Source: https://docs.pinata.cloud/files/uploading-files
At the core of Pinata's services is our IPFS APIs which allow you to upload files to either public or private IPFS. You can read more about the difference between the two [here](/files/private-ipfs).
Let's look at the multiple ways you can upload files!
## How to Upload Files
Uploading files with Pinata is simple, whether you want to use the SDK or the API. Key things to know:
* Uploads are done through `multipart/form-data` requests
* The SDK and API accept File objects per the [Web API Standard for Files]()
* You can add additional info to your upload such as a custom name for the file, keyvalue metadata, and a target group destination for organization
Here is a simple example of how you might upload a file in Typescript
```typescript SDK
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);
```
```typescript API
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
This will return the following response
```typescript SDK
{
id: "349f1bb2-5d59-4cab-9966-e94c028a05b7",
name: "file.txt",
cid: "bafybeihgxdzljxb26q6nf3r3eifqeedsvt2eubqtskghpme66cgjyw4fra",
size: 4682779,
number_of_files: 1,
mime_type: "text/plain",
group_id: null
}
```
```JSON API
{
"data": {
"id": "349f1bb2-5d59-4cab-9966-e94c028a05b7",
"name": "file.txt",
"cid": "bafybeihgxdzljxb26q6nf3r3eifqeedsvt2eubqtskghpme66cgjyw4fra",
"size": 4682779,
"number_of_files": 1,
"mime_type": "text/plain",
"group_id": null
}
}
```
* `id`: The ID of the file used for getting info, updating, or deleting
* `name`: The name of the file or the provided name in the `addMetadata` method
* `cid`: A cryptographic hash based on the contents of the file
* `size`: The size of the file in bytes
* `number_of_files`: The number of files in a reference
* `mime_type`: The mime type of the uploaded file
* `group_id`: The group the file was uploaded to if applicable
### Metadata
When uploading a file you can add additional metadata using the `name` or `keyvalues` methods after the selected upload method. This can include an optional `name` override or `keyvalue` pairs that can be used to searching the file later on
[Check out the Key-Values doc for more info](/files/key-values)
```typescript SDK {3-8}
const upload = await pinata.upload.public
.file(file)
.name("hello.txt")
.keyvalues({
env: "prod"
})
```
```typescript API {13,15-19, 21}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public");
formData.append("name", "hello.txt");
const keyvalues = JSON.stringify({
keyvalues: {
env: "prod"
}
})
formData.append("keyvalues", keyvalues);
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Groups
Pinata offers Private IPFS Groups to organize your content. You can upload a file to a group by using the `group` method.
[Check out the Groups doc for more info](/files/file-groups)
```typescript SDK {3}
const upload = await pinata.upload.public
.file(file)
.group("b07da1ff-efa4-49af-bdea-9d95d8881103")
```
```typescript API {13}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public");
formData.append("group", "b07da1ff-efa4-49af-bdea-9d95d8881103");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Client Side Uploads
There are situations where you may need to upload a file client side instead of server side. A great example is in Next.js where there is a 4MB file size restriction for files passed through Next's API routes. To solve this you can create a signed upload URL on the server and then pass it to the client for it to be consumed. This way your admin API key stays safe behind a server. Creating signed upload URLs can be done with either the [SDK](/sdk/upload/public/create-signed-url) or the [API](/api-reference/endpoint/create-signed-upload-url), and you can designate how long the URL is valid for or if there is other infromation you want to include such as metadata or a group ID.
Setting up a server side API endpoint might look something like this:
```typescript SDK
import { type NextRequest, NextResponse } from "next/server";
import { pinata } from "@/utils/config"; // Import the Files SDK instance
export const dynamic = "force-dynamic";
export async function GET() {
// Handle your auth here to protect the endpoint
try {
const url = await pinata.upload.public.createSignedURL({
expires: 30, // The only required param
name: "Client File",
group: "my-group-id"
})
return NextResponse.json({ url: url }, { status: 200 }); // Returns the signed upload URL
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating signed URL:" }, { status: 500 });
}
}
```
```typescript API
import { type NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() {
// Handle your auth here to protect the endpoint
try {
// Prepare payload data for request
const data = JSON.stringify({
network: "public",
expires: 30,
filename: "Client file",
group_id: "my-group-id"
})
// send request and parse response
const urlRequest = await fetch("https://uploads.pinata.cloud/v3/files/sign", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.PINATA_JWT}`
},
body: data
})
const urlResponse = await urlRequest.json()
return NextResponse.json({ url: urlResponse.data }, { status: 200 }); // Returns the key data
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating API Key:" }, { status: 500 });
}
}
```
Then back on the client side code, you can upload using the signed URL instead of the regular upload endpoint.
If you're using the SDK you can use the `.url()` parameter on any of the upload methods and pass in the signed upload URL there. If you are using the API you can simply make the upload request using the signed URL as the endpoint.
```typescript SDK {3}
const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL
const urlResponse = await urlRequest.json(); // Parse response
const upload = await pinata.upload.public
.file(file)
.url(urlResponse.url); // Upload the file with the signed URL
```
```typescript API {1-2,13}
const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL
const urlResponse = await urlRequest.json(); // Parse response
async function upload() {
try {
const formData = new FormData();
const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public")
const request = await fetch(urlResponse.url, {
method: "POST",
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
For more information on client side uploads check out our [Presigned URLs Guide](/files/presigned-urls)
### Upload Progress
If you happen to use the API as well as local files you can also track the progress of the upload using a library like `got`. Better support for upload progress will come in later versions of the SDK!
```typescript
import fs from "fs";
import FormData from "form-data";
import got from "got";
async function upload() {
const url = `https://uploads.pinata.cloud/v3/files`;
try {
let data = new FormData();
data.append(`file`, fs.createReadStream("path/to/file"));
const response = await got(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINATA_JWT}`,
},
body: data,
}).on("uploadProgress", (progress) => {
console.log(progress);
});
console.log(JSON.parse(response.body));
} catch (error) {
console.log(error);
}
}
```
If your file is larger than 100MB then a better approach is to follow the [Resumable Upload Guide](#resumable-uploads)
## Common File Recipes
Below are some common recipes for uploading a file.
### Blob
Usually you can pass a Blob directly into the request but to help guarantee success we recommend passing it into a `File` object.
```typescript SDK {8-10}
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const text = "Hello World!";
const blob = new Blob([text]);
const file = new File([blob], "hello.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);
```
```typescript API {7-9}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const text = "Hello World!";
const blob = new Blob([text]);
const file = new File([blob], "Testing.txt", { type: "text/plain" });
formData.append("file", file);
formData.append("network", "public");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### JSON
Pinata makes it easy to upload JSON objects using the [json](/sdk/upload/public/json) method.
```typescript SDK {8-15}
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const upload = await pinata.upload.public.json({
id: 2,
name: "Bob Smith",
email: "bob.smith@example.com",
age: 34,
isActive: false,
roles: ["user"],
});
```
```typescript API {7-16}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const json = JSON.stringify({
id: 2,
name: "Bob Smith",
email: "bob.smith@example.com",
age: 34,
isActive: false,
roles: ["user"],
})
const blob = new Blob([json]);
const file = new File([blob], "bob.json", { type: "application/json" });
formData.append("file", file);
formData.append("network", "public");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Local Files
If you need to upload files from a local file source you can use `fs` to feed a file into a `blob`, then turn that `blob` into a `File`. Due to the buffer limit in Node.js you may have issues going beyond 2GB with this approach. Using [Resumable Uploads](#resumable-uploads) with a client side file picker will help increase this.
Support for file streams will be coming in a later version of the SDK.
```typescript SDK {10-11}
const { PinataSDK } = require("pinata");
const fs = require("fs");
const { Blob } = require("buffer");
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const blob = new Blob([fs.readFileSync("./hello-world.txt")]);
const file = new File([blob], "hello-world.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);
```
```typescript API {9}
const JWT = "YOUR_PINATA_JWT";
const fs = require("fs");
const { Blob } = require("buffer");
async function upload() {
try {
const formData = new FormData();
formData.append("file", fs.createReadStream("./hello-world.txt"));
formData.append("network", "public")
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### Folders
The SDK can accept an array of files using the [fileArray](/sdk/upload/public/file-array) method. Folders can also be uploaded via the API by creating an array of files and mapping over them to add them to the form data. This is different then having a single `file` entry and having multiple files for that one entry, which does not work.
Folder uploads are currently only supported on Public IPFS
```typescript SDK
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const file1 = new File(["hello world!"], "hello.txt", { type: "text/plain" })
const file2 = new File(["hello world again!"], "hello2.txt", { type: "text/plain" })
const upload = await pinata.upload.public.fileArray([file1, file2])
```
```javascript Node.js
import fs from "fs"
import FormData from "form-data"
import rfs from "recursive-fs"
import basePathConverter from "base-path-converter"
import got from 'got'
const pinDirectoryToPinata = async () => {
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
const src = "PATH_TO_FOLDER";
var status = 0;
try {
const { dirs, files } = await rfs.read(src);
let data = new FormData();
for (const file of files) {
data.append(`file`, fs.createReadStream(file), {
filepath: basePathConverter(src, file),
});
}
const response = await got(url, {
method: 'POST',
headers: {
"Authorization": "Bearer PINATA_API_JWT"
},
body: data
})
.on('uploadProgress', progress => {
console.log(progress);
});
console.log(JSON.parse(response.body));
} catch (error) {
console.log(error);
}
};
pinDirectoryToPinata()
```
```javascript React
import { useState } from "react";
import { pinata } from "./utils/config"
function App() {
const [selectedFiles, setSelectedFiles] = useState();
const changeHandler = (event: React.ChangeEvent) => {
setSelectedFiles(event.target?.files);
};
const handleSubmission = async () => {
try {
const upload = await pinata.upload.public.fileArray(selectedFiles)
console.log(upload);
} catch (error) {
console.log(error);
}
};
return (
<>
>
);
}
export default App;
```
```javascript Javascript
import FormData from "form-data"
const pinDirectoryToIPFS = async () => {
try {
const folder = "json";
const json1 = { hello: "world" };
const json2 = { hello: "world2" };
const blob1 = new Blob([JSON.stringify(json1, null, 2)], {
type: "application/json",
});
const blob2 = new Blob([JSON.stringify(json2, null, 2)], {
type: "application/json",
});
const files = [
new File([blob1], "hello.json", { type: "application/json" }),
new File([blob2], "hello2.json", { type: "application/json" }),
];
const data = new FormData();
Array.from(files).forEach((file) => {
// If you are not using `fs` you might need to specify the folder path along with the filename
data.append("file", file, `${folder}/${file.name}`);
});
const pinataMetadata = JSON.stringify({
name: `${folder}`,
});
data.append("pinataMetadata", pinataMetadata);
const res = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
method: "POST",
headers: {
Authorization: `Bearer ${PINATA_JWT}`,
},
body: data,
});
const resData = await res.json();
console.log(resData);
} catch (error) {
console.log(error);
}
};
pinDirectoryToIPFS();
```
We also have other tools like the [Pinata IPFS CLI](/tools/ipfs-cli) which can be used to upload using [API Keys](/account-managemnet/api-keys)!
### URL
To upload a file from an external URL you can stream the contents into an `arrayBuffer`, which then gets passed into a new `Blob` that can then be uploaded to Pinata. This has been abstracted in the SDK using the [url](/sdk/upload/public/url) method.
```typescript SDK {8}
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const upload = await pinata.upload.public.url("https://i.imgur.com/u4mGk5b.gif");
```
```typescript API {7-10}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const stream = await fetch("https://i.imgur.com/u4mGk5b.gif")
const arrayBuffer = await stream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const file = new File([blob], "name.gif");
formData.append("file", file);
formData.append("network", "public");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
### base64
To upload a file in base64 simply turn the contents into a `buffer` that is passed into a `Blob`. Alternatively you can use the SDK for this as well using the [base64](/sdk/upload/public/base64) method.
```typescript SDK {8}
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const upload = await pinata.upload.public.base64("SGVsbG8gV29ybGQh");
```
```typescript API {7-11}
const JWT = "YOUR_PINATA_JWT";
async function upload() {
try {
const formData = new FormData();
const buffer = Buffer.from("SGVsbG8gV29ybGQh", "base64");
const blob = new Blob([buffer]);
const file = new File([blob], "hello.txt");
formData.append("file", file);
formData.append("network", "public");
const request = await fetch("https://uploads.pinata.cloud/v3/files", {
method: "POST",
headers: {
Authorization: `Bearer ${JWT}`,
},
body: formData,
});
const response = await request.json();
console.log(response);
} catch (error) {
console.log(error);
}
}
```
## Resumable Uploads
The upload endpoint `https://uploads.pinata.cloud/v3/files` is fully [TUS](https://tus.io) compatible, so it can support larger files with the ability to resume uploads. Any file upload larger than 100MB needs to be uploaded through the TUS method, or through the legacy [/pinFileToIPFS](/api-reference/endpoint/ipfs/pin-file-to-ipfs) endpoint. The [SDK](/sdk) handles this automatically when you use `pinata.upload..file()` by checking the file size before uploading.
At this time folder uploads must go through [/pinFileToIPFS](/api-reference/endpoint/ipfs/pin-file-to-ipfs). Read the [Folder Guide](#folders) for more info!
```typescript {6}
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const upload = await pinata.upload.public.file(massiveFile);
```
If you want to take advantage of resumable uploads then we would recommend using one of the [TUS clients](https://tus.io/implementations) and taking note of the following:
* Upload chunk size must be smaller than 50MB
* Instead of using the form data fields for `group_id` or `keyvalues` these can be passed directly into the upload metadata (see example below)
* Headers must include the Authorization with your [Pinata JWT](/account-managemnet/api-keys)
Here is an example of an upload to Pinata using the `tus-js-client`
```typescript tus-js-client [expandable]
import * as tus from "tus-js-client";
async function resumeUpload(file) {
try {
const upload = new tus.Upload(file, {
endpoint: "https://uploads.pinata.cloud/v3/files",
chunkSize: 50 * 1024 * 1024, // 50MiB chunk size
retryDelays: [0, 3000, 5000, 10000, 20000],
onUploadUrlAvailable: async function () {
if (upload.url) {
console.log("Upload URL is available! URL: ", upload.url);
}
},
metadata: {
filename: "candyroad-demo.mp4", // name
filetype: "video/mp4",
group_id: "0192868e-6144-7685-9fc5-af68a1e48f29", // group ID
network: "public",
keyvalues: JSON.stringifiy({ env: "prod" }), // keyvalues
},
headers: { Authorization: `Bearer ${process.env.PINATA_JWT}` }, // auth header
uploadSize: fileStats.size,
onError: function (error) {
console.log("Failed because: " + error);
},
onProgress: function (bytesUploaded, bytesTotal) {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
console.log(percentage + "%");
},
onSuccess: function () {
console.log("Upload completed!");
},
});
upload.start();
} catch (error) {
console.log(error);
}
}
```
## Pin by CID
Another way you can upload content to Pinata is by transferring content that is already on IPFS. This could be CIDs that are on your own local IPFS node or another IPFS pinning service! You can do this with the “Import from IPFS” button in the web app, like so:
Or you can pin by CID with the SDK using the [cid](/sdk/upload/public/cid) method.
```typescript
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const pin = await pinata.upload.public.cid("QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng")
```
This will result in a `request_id` and Pinata will start looking for the file. Progress can be checked by using the [queue](/sdk/files/public/queue) method.
```typescript
import { PinataSDK } from "pinata-web3";
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: "example-gateway.mypinata.cloud",
});
const jobs = await pinata.files.public.queue().status("prechecking")
```
All possible filters are included in the API reference below, but these are the possible "status" filters:
Filter by the status of the job in the pinning queue (see potential statuses
below)
* `prechecking` Pinata is running preliminary validations on your pin request.
* `searching` Pinata is actively searching for your content on the IPFS network. This may take some time if your content is isolated.
* `retrieving` Pinata has located your content and is now in the process of retrieving it.
* `expired` Pinata wasn't able to find your content after a day of searching the IPFS network. Please make sure your content is hosted on the IPFS network before trying to pin again.
* `backfilled` Pinata can only search 250 files at a time per account, so if you have more than 250 items in your queue then the extra items will sit in a backfilled status. Once the queue goes down it will automatically start working on the next items in the backfill queue.
* `over_free_limit` Pinning this object would put you over the free tier limit. Please add a credit card to continue pinning content.
* `over_max_size` This object is too large of an item to pin. If you're seeing this, please contact us for a more custom solution.
* `invalid_object` The object you're attempting to pin isn't readable by IPFS nodes. Please contact us if you receive this, as we'd like to better understand what you're attempting to pin.
* `bad_host_node` You provided a host node that was either invalid or unreachable. Please make sure all provided host nodes are online and reachable.
## Predetermining the CID
If you find yourself in a position where you want to pre-determine the CID before uploading you can use a combination of the `ipfs-unixfx-importer` and `blockstore-core` libraries.
```typescript
import { importer } from "ipfs-unixfs-importer";
import { MemoryBlockstore } from "blockstore-core/memory";
export const predictCID = async (file: File, version: 0 | 1 = 1) => {
try {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const blockstore = new MemoryBlockstore();
let rootCid: any;
for await (const result of importer([{ content: buffer }], blockstore, {
cidVersion: version,
hashAlg: "sha2-256",
rawLeaves: version === 1,
})) {
rootCid = result.cid;
}
return rootCid.toString();
} catch (err) {
return err;
}
};
```
Usage will just require passing in a file object and the version of the CID you want to predict. Here is an example using a local file.
```typescript
import fs from "fs"
const file = new File([fs.readFileSync("path/to-file")], "filename.extension");
const cid = await predictCID(file, 1);
console.log(cid);
```
## Peering with Pinata
If you run IPFS infrastructure and would like to peer with Pinata's nodes you can do so with the [Kubo](https://docs.ipfs.tech/reference/kubo/cli/) commands listed below. Rather than using full multiaddresses for our node IDs we use a DNS setup that is more stable and allows our infrastructure to be flexible.
```bash
ipfs swarm connect /dnsaddr/bitswap.pinata.cloud
```
```bash
ipfs config --json Peering.Peers '[{ "ID": "Qma8ddFEQWEU8ijWvdxXm3nxU7oHsRtCykAaVz8WUYhiKn", "Addrs": ["/dnsaddr/bitswap.pinata.cloud"] }]'
```
If you have any issues feel free to [reach out](mailto:team@pinata.cloud)!
## Web App
You can also use the **[Pinata App](https://app.pinata.cloud/)** to upload files. It’s as simple as clicking the “Add” button in the top right corner of the **[files page](https://app.pinata.cloud/ipfs/files)**. Select your file, give it a name, then upload. Once it's complete you’ll see it listed in the files page.
Start uploading by [signing up for a free account](https://app.pinata.cloud/register)!
# File Vectors (Beta)
Source: https://docs.pinata.cloud/files/vectors
The file vectors feature is still in beta and is only available on Private IPFS. Please contact the team at [team@pinata.cloud](mailto:team@pinata.cloud) if you have any issues.
## Overview
A common nusance when building AI apps is context embeddings. If you use a traditional stack you generall have to store an embedding, vectorize it, store the vector, then when you query a vector you'll get another reference to the file which you then have to fetch again. Pinata's solution is much more elegant. With Pinata's file vectoring you can upload a file and vector it at the same time.
```typescript
const upload = await pinata.upload.private
.file(file)
.group("GROUP_ID")
.vectorize()
```
When it comes time to query a vector, you have the option to either list your query results and judge the matching score, or just return the highest scoring file itself.
```typescript
const { data, contentType } = await pinata.files.private
.queryVectors({
groupId: "GROUP_ID",
query: "Hello World!",
returnFile: true
})
```
This enables develops to build AI applications faster and with less code!
### Pinata Vector Storage: Public Beta Limits
During the public beta, Pinata Vector Storage has the following limits:
* File Vectorization Limit: You can vectorize up to 10,000 files.
* Index Limit: You can create a maximum of 5 indexes, managed using Pinata Groups.
* Results Limit: You can query a vector and get a max of 20 files returned in one request.
**What this means**
You can organize your vector embeddings into up to 5 searchable indexes using Pinata Groups. Across all these groups, you can store a total of 10,000 vector embeddings corresponding to files stored on Pinata.
## Vectorizing Files
There are two ways you can vectorize file uploads, and with both options **files must be part of a group** in order for vectors and queries to work.
### Vectorize on Upload
If you use the [Files SDK](/sdk/getting-started) you can vectorize a file on upload.
```typescript {4}
const upload = await pinata.upload.private
.file(file)
.group("GROUP_ID")
.vectorize()
```
### Vectorize After Upload
If you already have a file that's been uploaded and it's [part of a group](/files/file-groups) then you can vectorize it.
```typescript
const update = await pinata.files.private.vectorize("FILE_ID")
```
## Querying Vectors
After a file has been vectorized and it's part of a group, you can query vectors for a given group.
```typescript
const results = await pinata.files.private.queryVectors({
groupId: "52681e41-86f4-407b-8f79-33a7e7e5df68",
query: "Hello World"
})
```
This will return the following type:
```typescript
type VectorizeQueryResponse = {
count: number;
matches: VectorQueryMatch[];
};
type VectorQueryMatch = {
file_id: string;
cid: string;
score: number;
};
```
### Returning the Top Match File
A unique feature in the SDK is if you pass in the `returnFile` flag then you will get the file and it's contents rather than just the reference to the file.
```typescript {4}
const { data, contentType } = await pinata.files.private.queryVectors({
groupId: "52681e41-86f4-407b-8f79-33a7e7e5df68",
query: "Hello World",
returnFile: true
})
```
## Deleting Vectors
If at any point you need to delete vectors for a file you can do so with the `deleteVectors` method in the SDK.
```typescript
const update = await pinata.files.private.deleteVectors("FILE_ID")
```
# Astro
Source: https://docs.pinata.cloud/frameworks/astro
Get started using Pinata with Astro
This guide will walk you through setting up Pinata with Astro
## Create an API Key and get Gateway URL
To create an API key, visit the [Keys Page](https://app.pinata.cloud/developers/keys) and click the "New Key" button in the top right. Once you do that you can select if you want your key to be admin or if you want to scope the privileges of the keys to certain endpoints or limit the number of uses. Make those selections, then give the key a name at the bottom, and click create key.
If you are just getting started we recommend using Admin privileges, then move
to scope keys as you better understand your needs
Once you have created the keys you will be shown your API Key Info. This will contain your **Api Key**, **API Secret**, and your **JWT**. Click "Copy All" and save them somewhere safe!
The API keys are only shown once, be sure to copy them somewhere safe!
After you have your API key, you will want to get your Gateway domain. When you create a Pinata account, you'll automatically have a Gateway created for you! To see it, simply visit the [Gateways Page](https://app.pinata.cloud/gateway) see it listed there.
The gateway domains are randomly generated and might look something like this:
```
aquamarine-casual-tarantula-177.mypinata.cloud
```
## Setup Astro
To create a new Astro project go ahead and run this command in the terminal:
```bash
npm create astro@latest pinata-astro
```
You can select which options you prefer, but for this exmaple we'll use the following:
```
tmpl How would you like to start your new project?
Empty
ts Do you plan to write TypeScript?
Yes
use How strict should TypeScript be?
Strict
deps Install dependencies?
Yes
git Initialize a new git repository?
Yes
```
After completing the project setup we can go ahead and `cd` into the repo and install `pinata`.
```bash
cd pinata-astro && npm i pinata
```
Since we want to keep our API key private we will need to make sure our code is deployed server side, and we can use several different adapter options which you can view [here](https://docs.astro.build/en/guides/server-side-rendering/). We also need a UI framework to handle our upload form, and there are many to choose from [here](https://docs.astro.build/en/guides/framework-components/). We'll use `vercel` and `svelte` for this tutorial, and you can install them like so.
```bash
npx astro add vercel svelte
```
This should install the dependencies and alter the `astro.config.mjs` for us.
## Setup Pinata
In the `src` folder make a new folder called `utils`, and inside there make a file called `pinata.ts` with the following contents:
```typescript src/utils/pinata.ts
import { PinataSDK } from "pinata";
export const pinata = new PinataSDK({
pinataJwt: import.meta.env.PINATA_JWT,
pinataGateway: import.meta.env.GATEWAY_URL,
});
```
This will create and export an instance of the Files SDK using environment variables, and to set those up make a new file at the root of the project called `.env` with the following values:
```
PINATA_JWT= # The Pinata JWT API key we got earlier
GATEWAY_URL= # The Gateway domain we grabbed earlier, formatting just as we copied it from the app
```
## Create Client Side Form
In the `src` folder make another folder called `components`, then inside there make file called `UploadForm.svelte`.
```html src/components/UploadForm.svelte
```
This component creates a `