Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
name: Test build on PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
cache: yarn
- name: Test build
run: |
Expand Down
111 changes: 111 additions & 0 deletions docs/pinning-service/ipfs-pinning-service-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,114 @@ For more commands and general help:
```bash
ipfs pin remote --help
```

## Pinning a DAG (recursive pinning)

<Note>In the IPFS Pinning Service API, **pinning a CID is recursive DAG pinning**. There is no separate "DAG pin" operation and no recursive flag — every pin request pins the *entire* DAG rooted at the CID.</Note>

The [IPFS Pinning Services API Spec](https://ipfs.github.io/pinning-services-api-spec/) defines the `cid` of a pin as:

> Content Identifier (CID) points at the root of a DAG that is pinned recursively.

So when you pin `bafybeib32…` (a file, a directory, or any IPLD/UnixFS DAG root), Functionland Fula retrieves and pins **every block reachable from that root**. A single CID is all you need to pin an arbitrarily large DAG — the commands in the previous section (`ipfs pin remote add …`) are already performing recursive DAG pins.

How the service obtains the DAG (per the standard): after you submit a CID, the service finds providers for the involved CIDs across the IPFS network — optionally helped by the `origins` hints you supply — and downloads the DAG over Bitswap. This is why a pin you just created starts in `queued`/`pinning` and only becomes `pinned` once the whole DAG has been fetched.

## Standard REST API

The `ipfs` CLI above is a convenience wrapper over the standard REST endpoints. You can call them directly against `https://api.cloud.fx.land` with your access token as a bearer credential. Functionland Fula implements the full IPFS Pinning Services API spec.

| Operation | Endpoint | Description |
|---|---|---|
| Add pin | `POST /pins` | Request a recursive DAG pin for a CID |
| List pins | `GET /pins` | List/filter pin objects (by `cid`, `name`, `status`, `before`/`after`, `meta`) |
| Get pin | `GET /pins/{requestid}` | Check the status of one pin request |
| Replace pin | `POST /pins/{requestid}` | Atomically remove + re-add (avoids GC of blocks common to both pins) |
| Remove pin | `DELETE /pins/{requestid}` | Remove a pin object |

All requests send `Authorization: Bearer YOUR_JWT`.

**Create a pin (recursive DAG pin):**

```bash
curl -X POST "https://api.cloud.fx.land/pins" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"cid": "bafybeib32tuqzs2wrc52rdt56cz73sqe3qu2deqdudssspnu4gbezmhig4",
"name": "war-and-peace.txt"
}'
```

The **Pin** object you send accepts: `cid` (required — the DAG root), `name` (optional label), `origins` (optional multiaddrs the service should try first to fetch the data), and `meta` (optional string-to-string key/value metadata).

The service responds with a **PinStatus** object:

```json
{
"requestid": "UniqueIdOfPinRequest",
"status": "queued",
"created": "2024-06-28T15:02:42Z",
"pin": {
"cid": "bafybeib32tuqzs2wrc52rdt56cz73sqe3qu2deqdudssspnu4gbezmhig4",
"name": "war-and-peace.txt"
},
"delegates": ["/dnsaddr/.../p2p/Qm..."],
"info": {}
}
```

**Pin lifecycle:** `queued` → `pinning` → `pinned` (or `failed`). Poll `GET /pins/{requestid}` until the status settles:

```bash
curl "https://api.cloud.fx.land/pins/UniqueIdOfPinRequest" \
-H "Authorization: Bearer YOUR_JWT"
```

**List your pins.** `GET /pins` returns a paginated `PinResults` object — a `count` of total matches plus a `results` array of `PinStatus` objects. To page through more than one batch, pass the `before` filter with the `created` timestamp of the oldest item you've seen:

```bash
curl "https://api.cloud.fx.land/pins?status=pinned&limit=10" \
-H "Authorization: Bearer YOUR_JWT"
```

```json
{ "count": 42, "results": [ { "requestid": "…", "status": "pinned", "pin": { "cid": "…" } } ] }
```

To speed up the transfer when your own node already has the data, put your node's multiaddrs in `origins`, and pre-connect to the peers returned in the response's `delegates` array (see [Provider hints](https://ipfs.github.io/pinning-services-api-spec/#section/Provider-hints) in the spec).

## Importing a DAG by uploading a CAR file

<Note>This is a **Functionland extension**, not part of the IPFS Pinning Service API standard. The standard is pin-by-CID only and has no data-upload endpoint — it assumes the DAG can be fetched from the network. CAR import fills the gap when the data exists **only on your machine** and no other peer is providing it yet.</Note>

The same content-addressed convention used by [web3.storage](https://web3.storage), NFT.storage and Filebase: you package your DAG as a [CAR (Content Addressable aRchive)](https://ipld.io/specs/transport/car/) file and upload it; the service imports the blocks and recursively pins the root CID to your account.

**1. Export your DAG to a CAR file** (using your local IPFS/Kubo node):

```bash
# Pin or add your data locally first, then export its DAG to a CAR:
ipfs dag export bafybeib32tuqzs2wrc52rdt56cz73sqe3qu2deqdudssspnu4gbezmhig4 > data.car
```

**2. Upload the CAR** to the import endpoint (multipart `file`, optional `name`):

```bash
curl -X POST "https://api.cloud.fx.land/pins/import/car" \
-H "Authorization: Bearer YOUR_JWT" \
-F "file=@data.car" \
-F "name=my-dataset"
```

The service validates the CAR, imports its blocks into the Fula network, and recursively pins the root. It returns the same **PinStatus** object as `POST /pins` (with `status: "queued"`), so you track it the same way via `GET /pins/{requestid}` until it becomes `pinned`. Re-importing a CAR whose root you already have simply returns your existing pin.

**Requirements and limits:**

- **Single root** — the CAR must have exactly one root CID (the DAG that gets pinned). Multi-root CARs are rejected.
- **Complete DAG** — every block referenced by the DAG must be present in the CAR. A partial DAG is rejected (it could never finish pinning).
- **Formats** — CARv1 and CARv2 are both accepted (the `ipfs dag export` default is CARv1).
- **Integrity** — every block's bytes are verified against its CID on the way in.
- **Size** — up to 800 MB per CAR by default; blocks above 2 MiB are rejected.
- **Quota** — the imported DAG counts against your storage quota; an import that wouldn't fit your plan is rejected up front with `402`.

**Error responses:** `400` invalid CAR (bad/truncated, hash mismatch, zero or multiple roots, missing blocks, unsupported codec, oversized/over-deep block); `402` insufficient storage credit; `413` CAR larger than the allowed maximum; `429` too many concurrent imports. The response body carries the specific reason.
Loading