Skip to content

Folders

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.

When you'd snapshot a folder

A folder snapshot is a single IPFS directory CID that contains every file in the folder, addressable by name. With it you can:

  • Share the whole folder via one URL: https://ipfs.ninja/ipfs/{dirCid}/
  • Resolve https://ipfs.ninja/ipfs/{dirCid}/photo.jpg (or any other gateway) directly
  • Drop the CID into an ENS contenthash to host a static site
  • Use it as the base CID of an NFT collection so each token references ipfs://{dirCid}/<id>.json
  • Pin the directory anywhere else — every IPFS gateway in the world knows how to resolve a UnixFS dir CID

Snapshots 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.

Create folder

POST /folders

ParameterTypeRequiredDescription
namestringYesDisplay name.
parentFolderIdstring | nullNoParent folder ID for nested folders. Omit for a root-level folder.

Example

bash
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:

json
{
  "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.

List folders

GET /folders

Returns every folder in your account, root-level and nested, with the last snapshot CID for each (if any).

json
[
  {
    "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.

Move a file into a folder

PUT /files/{cid}/move

ParameterTypeRequiredDescription
folderIdstring | nullYesTarget folder ID, or null to move the file to the root.
bash
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-…" }'

Snapshot a folder (get a UnixFS dir CID)

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.

Example

bash
curl -X POST https://api.ipfs.ninja/folders/1f8e2c3a-.../snapshot \
  -H "X-Api-Key: bws_your_api_key_here"

Returns:

json
{
  "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.

Resolving a 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 file

The 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

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.

Limits

  • The folder must contain at least one file. Empty folders return 400 — folder is empty.
  • File-name characters are URL-encoded in the multipart upload Kubo accepts; gateway URLs may need percent-encoding for spaces or non-ASCII characters in your filenames.
  • Snapshots count toward your plan's pin total exactly once per unique CID — file blocks are deduplicated, so the snapshot mostly adds a tiny directory node on top of files you already pin.

Update a folder

PUT /folders/{folderId}

ParameterTypeRequiredDescription
namestringNoNew display name.
parentFolderIdstring | nullNoRe-parent the folder. null moves it to the root.

Delete a folder

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.

json
{
  "deleted": true,
  "filesDeleted": 42,
  "foldersDeleted": 3
}

Configure S3 CORS for a folder / bucket

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)
  • S3 subresource 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.

PUT /folders/{folderId}/cors

Set the CORS rules for the folder's S3 bucket. Up to 100 rules per bucket, 64 KB total.

ParameterTypeRequiredDescription
rulesCorsRule[]YesArray of AWS-shaped CORS rules (see below). Non-empty.
bucketNamestringNoExplicit S3 bucket name. Defaults to the folder's display name. If the desired name is already claimed globally, pass an alternative here.

Each CorsRule:

FieldTypeRequiredDescription
AllowedOriginsstring[]YesOrigins allowed to send requests. Supports wildcards (https://*.myapp.com). Use * for any origin.
AllowedMethodsstring[]YesOne or more of GET, HEAD, PUT, POST, DELETE.
AllowedHeadersstring[]NoHeaders browsers may include on requests. Default: none. Use ["*"] to allow all (recommended for AWS SDK v3 which sends Authorization, x-amz-*, etc.).
ExposeHeadersstring[]NoResponse headers made readable to browser JavaScript. Include ETag and x-amz-meta-cid if your app needs the returned CID.
MaxAgeSecondsnumberNoHow long browsers cache the preflight. 0-86400. Default 3600.
IDstringNoFree-text label for the rule.

Example request

bash
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
    }]
  }'

Response 200 OK

json
{ "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 } ] }

GET /folders/{folderId}/cors

Returns the current CORS rules plus the bucket name (if claimed).

json
{
  "rules": [  ],
  "bucketName": "my-project"
}

DELETE /folders/{folderId}/cors

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.