English
English
Appearance
English
English
Appearance
Folders organise your uploaded files in the dashboard. They're metadata-only by default — files keep their own CIDs and aren't moved on IPFS — but you can also snapshot a folder to materialise it as a real UnixFS directory and get one CID for the whole thing.
A folder snapshot is a single IPFS directory CID that contains every file in the folder, addressable by name. With it you can:
https://ipfs.ninja/ipfs/{dirCid}/https://ipfs.ninja/ipfs/{dirCid}/photo.jpg (or any other gateway) directlyipfs://{dirCid}/<id>.jsonSnapshots are content-addressed: identical folder contents always produce the same CID. Re-snapshotting a folder you haven't changed returns the same CID it returned before. Adding/removing/renaming a file produces a new CID; the previous CID stays pinned and resolvable as long as you don't delete its files.
POST /folders
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name. |
parentFolderId | string | null | No | Parent folder ID for nested folders. Omit for a root-level folder. |
curl -X POST https://api.ipfs.ninja/folders \
-H "X-Api-Key: bws_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "name": "My NFT collection" }'Returns:
{
"folderId": "1f8e2c3a-…",
"name": "My NFT collection",
"parentFolderId": null,
"createdAt": 1746360000000
}Newly-created folders have no snapshot. The latestSnapshot field shows up on the folder once you call POST /folders/{id}/snapshot (see below) and on subsequent GET /folders responses.
GET /folders
Returns every folder in your account, root-level and nested, with the last snapshot CID for each (if any).
[
{
"folderId": "1f8e2c3a-…",
"name": "My NFT collection",
"parentFolderId": null,
"createdAt": 1746360000000,
"fileCount": 42,
"latestSnapshot": {
"cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
"takenAt": 1746421000000,
"fileCount": 42
}
}
]fileCount reflects the folder's current contents; latestSnapshot.fileCount reflects the contents at the time of the last snapshot. If they differ the snapshot CID still resolves but is stale — re-snapshot to refresh.
PUT /files/{cid}/move
| Parameter | Type | Required | Description |
|---|---|---|---|
folderId | string | null | Yes | Target folder ID, or null to move the file to the root. |
curl -X PUT https://api.ipfs.ninja/files/Qm.../move \
-H "X-Api-Key: bws_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "folderId": "1f8e2c3a-…" }'POST /folders/{folderId}/snapshot
Materialise the folder as a real UnixFS directory on the IPFS cluster and pin the result. Returns one CID for the whole folder. Names of children come from each file's fileName; duplicates are de-collided automatically.
No request body is needed; the path parameter identifies the folder.
curl -X POST https://api.ipfs.ninja/folders/1f8e2c3a-.../snapshot \
-H "X-Api-Key: bws_your_api_key_here"Returns:
{
"ok": true,
"folderId": "1f8e2c3a-…",
"cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
"fileCount": 42,
"sizeBytes": 8421376,
"takenAt": 1746421000000,
"ipfsUrl": "https://ipfs.ninja/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/"
}The CID is also persisted on the folder row, so subsequent GET /folders calls return it as latestSnapshot.cid without needing another snapshot.
Once a snapshot is pinned, the directory CID resolves via any IPFS gateway. The simplest URL pattern:
https://ipfs.ninja/ipfs/{dirCid}/ → directory listing
https://ipfs.ninja/ipfs/{dirCid}/photo.jpg → that one fileThe cluster pins recursively, so children are resolvable too — even if you later delete the original file from your account, the snapshot's copy survives because it's a separate pin recursing through the directory.
Re-snapshotting an unchanged folder returns the same CID — directory CIDs are content-addressed, so identical contents always produce the same hash, and the cluster's pin call recognises the duplicate and is a no-op on its end.
Note: the snapshot path itself is not free even when the result is the same CID. Each call reads every file's bytes back from IPFS and reuploads them as a multipart to the cluster's /add endpoint — that's where the wrap-with-directory wrapping happens. For typical folders (≤100 small files) this still completes in a few seconds; for very large folders prefer to call snapshot only when the contents actually changed.
Calling snapshot after you've added or removed files produces a different CID; the previous one continues to resolve as long as you don't delete its underlying files.
400 — folder is empty.PUT /folders/{folderId}
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | New display name. |
parentFolderId | string | null | No | Re-parent the folder. null moves it to the root. |
DELETE /folders/{folderId}
Deletes the folder and recursively cascades through every file and subfolder it contains. Subject to the same shared-CID safety guard as individual file deletes — if other users still pin a CID you uploaded, your unpin doesn't strip it for them.
{
"deleted": true,
"filesDeleted": 42,
"foldersDeleted": 3
}Folders exposed through the S3-compatible API act as buckets. If you're driving that API from browser JavaScript, you need CORS rules on the bucket so browser preflights pass. Two equivalent surfaces persist to the same store:
PUT /folders/{folderId}/cors — this REST endpoint, JWT-authed (used by the dashboard)PUT /{bucket}?cors — SigV4-authed (used by AWS SDKs, see s3-compatibility.md)The PUT on this endpoint also claims the folder's name as a globally-unique bucket if it hasn't been claimed yet.
Set the CORS rules for the folder's S3 bucket. Up to 100 rules per bucket, 64 KB total.
| Parameter | Type | Required | Description |
|---|---|---|---|
rules | CorsRule[] | Yes | Array of AWS-shaped CORS rules (see below). Non-empty. |
bucketName | string | No | Explicit S3 bucket name. Defaults to the folder's display name. If the desired name is already claimed globally, pass an alternative here. |
Each CorsRule:
| Field | Type | Required | Description |
|---|---|---|---|
AllowedOrigins | string[] | Yes | Origins allowed to send requests. Supports wildcards (https://*.myapp.com). Use * for any origin. |
AllowedMethods | string[] | Yes | One or more of GET, HEAD, PUT, POST, DELETE. |
AllowedHeaders | string[] | No | Headers browsers may include on requests. Default: none. Use ["*"] to allow all (recommended for AWS SDK v3 which sends Authorization, x-amz-*, etc.). |
ExposeHeaders | string[] | No | Response headers made readable to browser JavaScript. Include ETag and x-amz-meta-cid if your app needs the returned CID. |
MaxAgeSeconds | number | No | How long browsers cache the preflight. 0-86400. Default 3600. |
ID | string | No | Free-text label for the rule. |
curl -X PUT https://api.ipfs.ninja/folders/17f6dfd8-519c-4d0e-8f3a-5988a1d34ef2/cors \
-H "Authorization: Bearer $COGNITO_JWT" \
-H "Content-Type: application/json" \
-d '{
"rules": [{
"AllowedOrigins": ["https://myapp.com", "http://localhost:3000"],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "x-amz-meta-cid", "x-amz-request-id"],
"MaxAgeSeconds": 3600
}]
}'200 OK { "success": true, "rules": [ { "AllowedOrigins": ["https://myapp.com", "http://localhost:3000"], "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], "AllowedHeaders": ["*"], "ExposeHeaders": ["ETag", "x-amz-meta-cid", "x-amz-request-id"], "MaxAgeSeconds": 3600 } ] }Returns the current CORS rules plus the bucket name (if claimed).
{
"rules": [ … ],
"bucketName": "my-project"
}Removes all CORS rules. Browser preflights against the bucket will fail closed until new rules are set.
Dashboard alternative
On the Files page, each folder's action menu has an S3 CORS entry that opens a form-based editor. Same underlying store as this REST endpoint and as PutBucketCors via the S3 API.