Shelpin API Docs

Pin files to the Shelby network with cryptographic proof. One API call, verifiable on-chain.

What is Shelby?

Shelby is a decentralised hot storage protocol built on Aptos. Instead of storing files on one company's servers (like AWS S3), Shelby spreads your data across a network of independent Storage Provider (SP) nodes around the world.

Key properties:

What is Shelpin?

Shelpin is a managed API layer on top of Shelby. It handles the complexity so you don't have to:

Think of it like Pinata for IPFS, but for Shelby — with on-chain proofs built in, not bolted on.

shelby:// Addresses

When you upload a file through Shelpin, you get back a shelby:// address like:

shelby://0x48afe95c5c16...7c81/photo.jpg

This address has two parts:

This address is permanent and verifiable. Anyone can check the Aptos blockchain to confirm the file was uploaded and committed. You can view it on the Shelby Explorer.

Quickstart

Get from zero to a pinned file in under a minute.

1. Create an account

# Register and get your API key
curl -X POST https://shelpin.forestinfra.com/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'
Response
{
  "userId": "abc-123",
  "email": "you@example.com",
  "tier": "free",
  "apiKey": "shelpin_a1b2c3d4...",
  "message": "Save your API key — it will not be shown again."
}

2. Upload a file

# Pin a file to Shelby
curl -X POST https://shelpin.forestinfra.com/files \
  -H "Authorization: Bearer shelpin_a1b2c3d4..." \
  -F "file=@photo.jpg" \
  -F "name=photo.jpg"
Response
{
  "id": "pin_f8e7d6c5b4a3",
  "shelbyAddress": "shelby://0x48af...7c81/photo.jpg",
  "shelbyProof": "shelby://0x48af...7c81/photo.jpg",
  "size": 284729,
  "status": "pinned",
  "name": "photo.jpg",
  "created": "2026-03-24 15:28:13"
}

3. Download it back

curl https://shelpin.forestinfra.com/pins/pin_f8e7d6c5b4a3/download \
  -H "Authorization: Bearer shelpin_a1b2c3d4..." \
  -o photo.jpg

That's it. Your file is stored on the Shelby network with a verifiable shelby:// address.

Authentication

All API requests (except /health and /auth/register) require a Bearer token.

Authorization: Bearer shelpin_your_api_key_here

API keys are generated when you register or via the API Keys endpoint. Keys have read and/or write permissions.

SDK

The Shelpin SDK is a lightweight TypeScript/JavaScript client. Works in Node.js and the browser.

npm install shelpin

Usage

Upload
List & Download
Presigned
import Shelpin from 'shelpin';

const shelpin = new Shelpin({ apiKey: 'shelpin_your_key' });

// Upload a file
const pin = await shelpin.upload(
  Buffer.from('Hello Shelby!'),
  'hello.txt'
);
console.log(pin.shelbyAddress);

// Upload from URL
const pin2 = await shelpin.uploadFromUrl(
  'https://example.com/data.json'
);

// Upload JSON directly
const pin3 = await shelpin.uploadJson(
  { users: ['alice', 'bob'] },
  'users.json'
);
// List your pins
const { pins, total } = await shelpin.list({ limit: 10 });

// Download a file
const data = await shelpin.download(pins[0].id);

// Get public gateway URL (no auth needed)
const url = shelpin.gatewayUrl(pins[0].id, 'photo.jpg');
// → https://shelpin.forestinfra.com/g/pin_abc/photo.jpg

// Delete a pin
await shelpin.delete(pins[0].id);
// Server: create a time-limited upload token
const token = await shelpin.createUploadToken({
  expiresIn: 300,  // 5 minutes
  maxUses: 1,
});

// Client: upload with just the token
const form = new FormData();
form.append('file', fileInput.files[0]);

await fetch('https://shelpin.forestinfra.com/files/presigned', {
  method: 'POST',
  headers: { 'X-Upload-Token': token.uploadToken },
  body: form,
});

CLI

The Shelpin CLI lets you pin files from your terminal. Install globally or use with npx.

Install

npm install -g shelpin-cli
# or use without installing:
npx shelpin-cli upload ./my-file.jpg

Authenticate

shelpin login shelpin_YOUR_API_KEY

Your key is saved to ~/.shelpin.json. Alternatively, set SHELPIN_API_KEY as an environment variable.

Upload files

# Single file
shelpin upload photo.jpg

# Multiple files
shelpin upload file1.txt file2.png

# Entire directory (recursive)
shelpin upload ./my-site/

List pins

shelpin list
shelpin list --limit 5 --status pinned

Download

shelpin download pin_a1b2c3d4 output.jpg

Get gateway URL

shelpin gateway pin_a1b2c3d4
# → https://shelpin.forestinfra.com/g/pin_a1b2c3d4/photo.jpg

Check health

shelpin health

All commands

CommandAliasDescription
login <key>Save API key
whoamiShow account info
upload <path>pinUpload files or folders
listlsList pins
download <id>dlDownload a file
delete <id>rmDelete a pin
gateway <id>gwGet public gateway URL
healthCheck service status

Upload File

POST /files

Upload a file using multipart form data. Max file size: 100MB.

FieldTypeRequiredDescription
fileFileYesThe file to upload
namestringNoDisplay name (defaults to original filename)
metadataJSON stringNoArbitrary key-value metadata
cURL
JavaScript
curl -X POST https://shelpin.forestinfra.com/files \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@document.pdf" \
  -F "name=document.pdf"
const form = new FormData();
form.append("file", fileBlob, "document.pdf");

const res = await fetch("https://shelpin.forestinfra.com/files", {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}` },
  body: form,
});
const pin = await res.json();

Upload Base64 / URL

POST /pins

Pin content from a base64 string or fetch from a URL.

FieldTypeRequiredDescription
contentstring*Base64-encoded file content
urlstring*URL to fetch and pin
namestringNoDisplay name
metadataobjectNoArbitrary key-value metadata

* Provide either content or url, not both.

cURL (base64)
cURL (URL)
JavaScript
curl -X POST https://shelpin.forestinfra.com/pins \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "SGVsbG8gU2hlbGJ5IQ==", "name": "hello.txt"}'
curl -X POST https://shelpin.forestinfra.com/pins \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/data.json", "name": "data.json"}'
const res = await fetch("https://shelpin.forestinfra.com/pins", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: btoa("Hello Shelby!"),
    name: "hello.txt",
  }),
});
const pin = await res.json();

List & Query Pins

GET /pins

List all your pins with optional filters.

ParamTypeDefaultDescription
limitnumber20Results per page (max 100)
offsetnumber0Pagination offset
statusstringFilter: pinned, pinning, failed, unpinned
namestringSearch by name (partial match)
curl https://shelpin.forestinfra.com/pins?limit=10&status=pinned \
  -H "Authorization: Bearer $API_KEY"
Response
{
  "pins": [
    {
      "id": "pin_f8e7d6c5b4a3",
      "shelbyAddress": "shelby://0x48af...7c81/photo.jpg",
      "shelbyProof": "shelby://0x48af...7c81/photo.jpg",
      "size": 284729,
      "status": "pinned",
      "name": "photo.jpg",
      "metadata": null,
      "created": "2026-03-24 15:28:13"
    }
  ],
  "total": 1,
  "limit": 10,
  "offset": 0
}

Get Pin

GET /pins/:id

Retrieve metadata for a single pin by its ID.

curl https://shelpin.forestinfra.com/pins/pin_f8e7d6c5b4a3 \
  -H "Authorization: Bearer $API_KEY"
Response
{
  "id": "pin_f8e7d6c5b4a3",
  "shelbyAddress": "shelby://0x48af...7c81/photo.jpg",
  "shelbyProof": "shelby://0x48af...7c81/photo.jpg",
  "size": 284729,
  "status": "pinned",
  "name": "photo.jpg",
  "metadata": null,
  "created": "2026-03-24 15:28:13"
}

Download File

GET /pins/:id/download

Download the actual file content from the Shelby network. Returns the raw file with appropriate headers.

cURL
JavaScript
curl https://shelpin.forestinfra.com/pins/pin_f8e7d6c5b4a3/download \
  -H "Authorization: Bearer $API_KEY" \
  -o photo.jpg
const res = await fetch(
  "https://shelpin.forestinfra.com/pins/pin_f8e7d6c5b4a3/download",
  { headers: { Authorization: `Bearer ${API_KEY}` } }
);
const blob = await res.blob();
// Save or use the blob
Response Headers
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="photo.jpg"
Content-Length: 284729

Delete Pin

DELETE /pins/:id

Unpin a file. The file is marked as unpinned and will no longer appear in default listings.

curl -X DELETE https://shelpin.forestinfra.com/pins/pin_f8e7d6c5b4a3 \
  -H "Authorization: Bearer $API_KEY"
Response
{
  "message": "Pin removed",
  "id": "pin_f8e7d6c5b4a3"
}

Presigned Uploads

Let your frontend users upload files directly without exposing your API key. Generate a time-limited upload token, pass it to the client, and they upload straight to Shelpin.

1. Create an upload token (server-side)

POST /auth/upload-token

FieldTypeDefaultDescription
namestringDefault filename for uploads
expiresInnumber3600Token lifetime in seconds
maxUsesnumber1Max number of uploads allowed
maxSizeBytesnumber104857600Max file size per upload (100MB)
curl -X POST https://shelpin.forestinfra.com/auth/upload-token \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresIn": 300, "maxUses": 1}'
Response
{
  "uploadToken": "shelpin_upload_a1b2c3...",
  "uploadUrl": "/files/presigned",
  "expiresAt": "2026-03-24T16:30:00.000Z",
  "maxUses": 1,
  "maxSizeBytes": 104857600
}

2. Upload from the client (no API key needed)

POST /files/presigned

Pass the token in the X-Upload-Token header. No Authorization header required.

cURL
JavaScript (browser)
curl -X POST https://shelpin.forestinfra.com/files/presigned \
  -H "X-Upload-Token: shelpin_upload_a1b2c3..." \
  -F "file=@photo.jpg"
// In the browser — no API key exposed
const form = new FormData();
form.append("file", fileInput.files[0]);

const res = await fetch("https://shelpin.forestinfra.com/files/presigned", {
  method: "POST",
  headers: { "X-Upload-Token": uploadToken },
  body: form,
});
const pin = await res.json();
console.log(pin.shelbyAddress); // shelby://0x48af.../photo.jpg

Public Gateway

Serve pinned files publicly — no authentication required. Perfect for images, assets, and any content you want shareable via URL.

GET /g/:pinId

GET /g/:pinId/:filename

The second form includes the filename in the URL for friendly links and correct MIME types.

# Direct link — anyone can access, no auth
https://shelpin.forestinfra.com/g/pin_f8e7d6c5b4a3/photo.jpg

Use it in HTML:

<img src="https://shelpin.forestinfra.com/g/pin_f8e7d6c5b4a3/photo.jpg" />

Response headers include:

Webhooks

Get notified when pins complete, fail, or are deleted. Register a URL and Shelpin will POST to it with event details.

Events

EventFired when
pin.completedA file is successfully pinned to Shelby
pin.failedA pin attempt fails
pin.deletedA pin is deleted/unpinned
*All events (default)

Create a webhook

curl -X POST https://shelpin.forestinfra.com/webhooks \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourapp.com/hooks/shelpin", "events": ["pin.completed"]}'

Response includes a secret — save it to verify signatures.

Verifying signatures

Every webhook includes an X-Shelpin-Signature header — an HMAC-SHA256 of the request body using your secret:

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return signature === expected;
}

Payload format

{
  "event": "pin.completed",
  "timestamp": "2026-03-24T14:30:00.000Z",
  "data": {
    "pin": {
      "id": "pin_a1b2c3d4e5f6",
      "shelbyAddress": "shelby://0x...",
      "size": 48291,
      "status": "pinned",
      "name": "photo.jpg"
    }
  }
}

Other webhook endpoints

MethodEndpointDescription
GET/webhooksList your webhooks
PATCH/webhooks/:idUpdate URL, events, or active status
DELETE/webhooks/:idDelete a webhook
POST/webhooks/:id/testSend a test event

Webhooks auto-disable after 10 consecutive delivery failures. Re-enable via PATCH with {"active": true}.

S3-Compatible API

Drop-in replacement for AWS S3 — swap your endpoint URL and your app stores files on Shelby instead of Amazon. Uses the same Shelpin Bearer auth (not AWS SigV4).

Upload an object

curl -X PUT https://shelpin.forestinfra.com/s3/my-bucket/photos/cat.jpg \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @cat.jpg

Download an object

curl https://shelpin.forestinfra.com/s3/my-bucket/photos/cat.jpg \
  -H "Authorization: Bearer YOUR_KEY" \
  -o cat.jpg

Check if an object exists

curl -I https://shelpin.forestinfra.com/s3/my-bucket/photos/cat.jpg \
  -H "Authorization: Bearer YOUR_KEY"

Delete an object

curl -X DELETE https://shelpin.forestinfra.com/s3/my-bucket/photos/cat.jpg \
  -H "Authorization: Bearer YOUR_KEY"

List objects in a bucket

curl https://shelpin.forestinfra.com/s3/my-bucket?prefix=photos/ \
  -H "Authorization: Bearer YOUR_KEY"

Returns S3-compatible XML with <ListBucketResult>.

Using with the AWS SDK

const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

const s3 = new S3Client({
  endpoint: 'https://shelpin.forestinfra.com/s3',
  region: 'auto',
  credentials: {
    accessKeyId: 'shelpin',       // any value
    secretAccessKey: 'YOUR_KEY',  // your Shelpin API key
  },
});

await s3.send(new PutObjectCommand({
  Bucket: 'my-bucket',
  Key: 'hello.txt',
  Body: 'Hello Shelby!',
}));

Supported operations

S3 OperationHTTPShelpin Endpoint
PutObjectPUT/s3/:bucket/:key
GetObjectGET/s3/:bucket/:key
HeadObjectHEAD/s3/:bucket/:key
DeleteObjectDELETE/s3/:bucket/:key
ListObjectsV2GET/s3/:bucket?prefix=

Note: Shelpin uses Bearer auth, not AWS SigV4. When using the AWS SDK, pass your Shelpin API key as secretAccessKey. The accessKeyId can be any value.

Analytics

GET /analytics

Track downloads, bandwidth, and visitors across your files. All gateway and API downloads are logged.

ParamTypeDefaultDescription
daysnumber30Lookback period (max 90)
pinIdstringFilter to a single file
curl https://shelpin.forestinfra.com/analytics?days=7 \
  -H "Authorization: Bearer $API_KEY"
Response
{
  "period": { "days": 7, "since": "2026-03-17T..." },
  "summary": {
    "totalDownloads": 142,
    "totalBandwidth": 48293847,
    "uniqueFiles": 8,
    "uniqueVisitors": 34
  },
  "daily": [
    { "date": "2026-03-24", "downloads": 23, "bandwidth": 8294817 }
  ],
  "topFiles": [
    { "pin_id": "pin_f8e7...", "name": "photo.jpg", "downloads": 89, "bandwidth": 25384729 }
  ]
}

Folders

Organise your pins into folders. A pin can belong to multiple folders.

Create a folder

POST /folders

curl -X POST https://shelpin.forestinfra.com/folders \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "photos"}'

Response:

{
  "id": "fld_a1b2c3d4e5f6",
  "name": "photos",
  "parentId": null
}

List folders

GET /folders

curl https://shelpin.forestinfra.com/folders \
  -H "Authorization: Bearer YOUR_KEY"

Returns all folders with pin counts:

{
  "folders": [
    { "id": "fld_a1b2c3d4e5f6", "name": "photos", "pin_count": 12 }
  ]
}

Add pins to a folder

POST /folders/:id/pins

curl -X POST https://shelpin.forestinfra.com/folders/fld_a1b2c3d4e5f6/pins \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pinIds": ["pin_abc123", "pin_def456"]}'

Remove pins from a folder

DELETE /folders/:id/pins

curl -X DELETE https://shelpin.forestinfra.com/folders/fld_a1b2c3d4e5f6/pins \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pinIds": ["pin_abc123"]}'

Delete a folder

DELETE /folders/:id

Deleting a folder removes the association but does not delete the pins inside it.

curl -X DELETE https://shelpin.forestinfra.com/folders/fld_a1b2c3d4e5f6 \
  -H "Authorization: Bearer YOUR_KEY"

Filter pins by folder

Pass folderId as a query parameter when listing pins:

curl https://shelpin.forestinfra.com/pins?folderId=fld_a1b2c3d4e5f6 \
  -H "Authorization: Bearer YOUR_KEY"

Tags

Label your pins with coloured tags for quick filtering. A pin can have multiple tags.

Create a tag

POST /tags

curl -X POST https://shelpin.forestinfra.com/tags \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "production"}'

Response:

{
  "id": "tag_x1y2z3",
  "name": "production",
  "color": "#8BC53F"
}

List tags

GET /tags

curl https://shelpin.forestinfra.com/tags \
  -H "Authorization: Bearer YOUR_KEY"

Tag a pin

POST /pins/:pinId/tags

curl -X POST https://shelpin.forestinfra.com/pins/pin_abc123/tags \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tagIds": ["tag_x1y2z3"]}'

Remove tags from a pin

DELETE /pins/:pinId/tags

curl -X DELETE https://shelpin.forestinfra.com/pins/pin_abc123/tags \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tagIds": ["tag_x1y2z3"]}'

Filter pins by tag

Pass tagId as a query parameter when listing pins:

curl https://shelpin.forestinfra.com/pins?tagId=tag_x1y2z3 \
  -H "Authorization: Bearer YOUR_KEY"

Delete a tag

DELETE /tags/:id

curl -X DELETE https://shelpin.forestinfra.com/tags/tag_x1y2z3 \
  -H "Authorization: Bearer YOUR_KEY"

Activity Log

GET /activity

Returns a timeline of all events for your account — downloads, uploads, views. Useful for debugging and auditing.

Query parameters

ParameterDefaultDescription
limit50Max events to return (max 200)
offset0Pagination offset

Example

curl https://shelpin.forestinfra.com/activity?limit=20 \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "events": [
    {
      "id": "evt_abc123",
      "type": "download",
      "pinId": "pin_f8e7d6c5b4a3",
      "pinName": "photo.jpg",
      "bytes": 48291,
      "ip": "192.168.1.1",
      "timestamp": "2026-03-25T14:30:00.000Z"
    }
  ],
  "total": 156,
  "limit": 20,
  "offset": 0
}

Event types

TypeDescription
downloadFile downloaded via API or gateway
uploadFile uploaded
viewFile viewed via gateway

Register

POST /auth/register

Create a new account. No authentication required. Returns an API key — save it, it won't be shown again.

FieldTypeRequiredDescription
emailstringYesYour email address
curl -X POST https://shelpin.forestinfra.com/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'

API Keys

Create a key

POST /auth/keys

FieldTypeRequiredDescription
namestringNoLabel for the key (e.g. "ci-pipeline")
curl -X POST https://shelpin.forestinfra.com/auth/keys \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci-pipeline"}'

List keys

GET /auth/keys

curl https://shelpin.forestinfra.com/auth/keys \
  -H "Authorization: Bearer $API_KEY"

Health

GET /health

No authentication required. Check service status and Shelby network connectivity.

curl https://shelpin.forestinfra.com/health
Response
{
  "service": "shelpin",
  "status": "ok",
  "version": "0.1.0",
  "shelby": {
    "healthy": true,
    "networkVersion": "shelbynet",
    "mock": false
  }
}

Tiers & Limits

Free

1 GB storage
100 requests/day

Pro

10 GB storage
10,000 requests/day

Enterprise

Unlimited storage
Unlimited requests

Upload limit per file: 100 MB. Need more? Get in touch.