
The AIOZ Pin CLI is a separate Go binary from the Node.js SDK, built for pinning from a shell script, CI pipeline, or terminal without pulling in a JavaScript runtime. It lives in the pinning-service-cli repository on the AIOZNetwork GitHub org. This tutorial covers every real command it supports, login, pin, list-pins, get-pin, and unpin, with the actual flags rather than a partial summary.
TL;DR:
go buildpinning login --key --secret (or a ~/.pinning/credentials file)pinning pin --file, pinning list-pins, pinning get-pin --id, and pinning unpin --idThe tool is distributed as source, not a prebuilt binary. With Go and git installed:
git clone https://github.com/AIOZNetwork/pinning-service-cli.git
cd pinning-service-cli/pinning
go build
That produces a local pinning executable you can run directly or move somewhere on your PATH.
Credentials come from an API key and secret, generated the same way as for the SDK or raw API:
pinning login --key "your_key" --secret "your_secret"
If you'd rather not pass credentials as flags on every invocation, especially in a script that runs repeatedly, you can drop them into ~/.pinning/credentials as JSON instead:
{"APIKey":"your_key","SecretKey":"your_secret"}
The CLI reads that file automatically, so subsequent commands don't need the flags at all.
pinning pin --file afile.txt
Beyond the basic case, the pin command supports several flags worth knowing: --file also accepts a directory, not just a single file, -w wraps the pinned content in a parent directory hash (useful when you want the folder structure preserved rather than flattened), --name sets a display name for the pin, --keyvalue attaches arbitrary metadata and can be repeated for multiple key-value pairs, and --hash pins a CID you already have instead of uploading a new file.
pinning list-pins
For anything beyond "show me everything," the command supports real filtering and pagination: --pinned=true to filter by pin status, --sortBy=created_at and --sortOrder=DESC to control ordering, --limit and --offset for pagination, and --keyvalue=key1:value1 to filter by metadata you attached when pinning. Combined, a real invocation looks like:
pinning list-pins --pinned=true --sortBy=created_at --sortOrder=DESC --limit=10 --offset=0
pinning get-pin --id=00000000-0000-0000-0000-000000000000
pinning unpin --id=00000000-0000-0000-0000-000000000000
Both take the pin's ID directly, which you'd get from a list-pins call or from the response of the original pin command. There's also an info command for general account/service information beyond individual pins, and this maps onto the same underlying Pinning Services API standard covered separately.
Every command exits 0 on success and non-zero on failure, the standard convention any shell script or CI step already checks for without extra parsing. On failure, the CLI writes a human-readable error to stderr, an invalid or expired key/secret pair, a file path that doesn't exist, a pin ID that isn't found, while stdout stays clean rather than emitting partial or malformed JSON. That separation is what makes error handling in a script straightforward: check the exit code first, and only parse stdout as JSON once you know the command actually succeeded.
if ! pinning pin --file "$ASSET" > result.json; then
echo "pin failed, see stderr above" >&2
exit 1
fi
CID=$(jq -r '.cid' result.json)
The CLI's design, a single static Go binary with JSON output, fits naturally into a CI step without pulling in a Node.js toolchain just to pin one build artifact. A minimal GitHub Actions step, using credentials from repository secrets rather than hardcoded flags:
- name: Pin build artifact to AIOZ Pin
run: |
pinning login --key "${{ secrets.AIOZ_PIN_KEY }}" --secret "${{ secrets.AIOZ_PIN_SECRET }}"
pinning pin --file dist/bundle.zip --name "build-${{ github.sha }}" > pin-result.json
echo "CID=$(jq -r '.cid' pin-result.json)" >> "$GITHUB_ENV"
The --name flag here tags the pin with the commit SHA, which pays off later: piping pinning list-pins output through jq 'select(.name | startswith("build-"))' finds that exact build's pin by its display name without needing to have saved the CID anywhere else. For a pipeline that pins on every merge to main, this is the difference between "some CID exists somewhere" and being able to look up exactly which pin corresponds to which commit.
Every command prints its result as JSON to stdout, with logs and diagnostic output going to stderr instead. That separation matters if you're scripting against this: pipe stdout straight into jq or another JSON tool without log lines mixed in, and redirect stderr separately if you want to capture logs without polluting the data you're parsing. A pin call's output includes at minimum the cid, the pin's id, and its current status, the same fields the SDK's pinFilesToIPFS() response returns, since both surfaces ultimately call the same underlying API.
It's worth being direct about when the CLI is actually the right tool versus reaching for the Node.js SDK instead. The CLI's real advantage is that it's a single static binary, no node_modules, no runtime version to manage, which matters specifically in environments where pulling in a JavaScript toolchain is disproportionate to the task: a lightweight CI image, a cron job on a bare Linux box, a Makefile target that shouldn't need npm install just to pin one file. For anything already running inside a Node.js application, the SDK is the more natural fit, native async/await instead of shelling out to a subprocess and parsing its stdout. Both call the same underlying REST API, so the choice is about the runtime environment you're already in, not a difference in what's actually possible through either path.
How do I install the AIOZ Pin CLI? Clone the pinning-service-cli repository from the AIOZNetwork GitHub org and build it with go build, which requires Go and git installed locally. There's no prebuilt binary distribution.
How do I authenticate the AIOZ Pin CLI without passing credentials every time? Save your API key and secret to ~/.pinning/credentials as JSON ({"APIKey":"...","SecretKey":"..."}), and the CLI reads it automatically instead of requiring --key/--secret flags on each command.
Can the AIOZ Pin CLI pin an entire directory? Yes, the --file flag on the pin command accepts a directory path, not just a single file, and -w wraps the result in a parent directory hash if you want the folder structure preserved.
How do I filter or sort my pin list with the CLI? pinning list-pins supports --pinned, --sortBy, --sortOrder, --limit, --offset, and --keyvalue flags for filtering and pagination.
What format does the AIOZ Pin CLI output? JSON on stdout, with logs sent to stderr separately, so command output pipes cleanly into tools like jq without log noise mixed in.

IPFS defaults to 256 KiB fixed-size chunks, but also ships Rabin and Buzhash content-defined chunkers. Here is why the choice affects deduplication.

An IPFS CID is not a random string. It encodes a version, a codec, and a hash algorithm plus digest. Here is how to decode a real CID piece by piece.

IPFS does not use a B-tree for large directories. It uses a HAMT, a Hash Array Mapped Trie. Here is exactly how it shards a folder once it outgrows one block.

AIOZ Pin image resizing happens straight in the gateway URL, no upload step or separate service. Here is every img- parameter, with real srcset examples.

x402 payments let ERC-8004 weight a paid AI agent interaction more heavily than free work in its reputation score. Here is exactly how that link works.

AIOZ Pin NFT API calls pin an asset and its metadata as two tracked pins under one record. Here is how to automate it directly over REST, no SDK needed.