Upload a document, wait for structuring, then point your agent at the files.
Prerequisites
You need a Shelv API key. Pick whichever path suits your workflow:
No browser required — ideal for AI agents and coding agents (Claude Code, Codex, OpenClaw). # Register
curl -X POST https://api.shelv.dev/v1/auth/register \
-H " Content-Type: application/json " \
-d ' {"name": "Alice", "email": "alice@example.com", "password": "your-secure-password"} '
# Check your email for the 6-digit code, then verify
curl -X POST https://api.shelv.dev/v1/auth/verify-email \
-H " Content-Type: application/json " \
-d ' {"email": "alice@example.com", "code": "123456"} '
The verify response contains your API key. Save it: export SHELV_API_KEY = " sk_live_... "
See the full Authentication guide for details.
Sign in at shelv.dev
Open Settings > API Keys
Create a key and copy it
Set environment variables:
export SHELV_API_URL = " https://api.shelv.dev "
export SHELV_API_KEY = " sk_your_api_key "
const SHELV_API_URL = process . env . SHELV_API_URL ?? " https://api.shelv.dev " ;
const SHELV_API_KEY = process . env . SHELV_API_KEY ! ;
import os
shelv_api_url = os.environ.get( " SHELV_API_URL " , " https://api.shelv.dev " )
shelv_api_key = os.environ[ " SHELV_API_KEY " ]
1. Upload a document
curl -X POST " $SHELV_API_URL /v1/shelves " \
-H " Authorization: Bearer $SHELV_API_KEY " \
-F " file=@contract.pdf " \
-F " name=My Contract "
import { readFile } from " node:fs/promises " ;
const form = new FormData ();
form . append ( " file " , new Blob ([ await readFile ( " contract.pdf " )]), " contract.pdf " );
form . append ( " name " , " My Contract " );
const res = await fetch ( `${ SHELV_API_URL } /v1/shelves ` , {
method : " POST " ,
headers : { Authorization : ` Bearer ${ SHELV_API_KEY }` },
body : form ,
});
const shelf = await res . json ();
import requests
with open ( " contract.pdf " , " rb " ) as f:
res = requests.post(
f " { shelv_api_url } /v1/shelves" ,
headers = { " Authorization " : f "Bearer { shelv_api_key } " },
files = { " file " : ( " contract.pdf " , f)},
data = { " name " : " My Contract " },
)
shelf = res.json()
Optional fields:
template=book|legal-contract|charter-party|academic-paper
review=true to pause in review before finalization
Save the returned shelf public ID:
export SHELF_PUBLIC_ID = " shf_0123456789abcdef01234567 "
const SHELF_PUBLIC_ID = shelf . publicId ; // "shf_0123456789abcdef01234567"
shelf_public_id = shelf[ " publicId " ] # "shf_0123456789abcdef01234567"
2. Wait for completion
Webhooks (recommended)
Polling
Register a webhook endpoint once — it will receive events for every shelf you submit. # One-time setup
curl -X POST " $SHELV_API_URL /v1/webhooks " \
-H " Authorization: Bearer $SHELV_API_KEY " \
-H " Content-Type: application/json " \
-d ' {
"url": "https://your-app.com/webhooks/shelv",
"events": ["shelf.ready", "shelf.failed"]
} '
When a shelf reaches ready or failed, your endpoint receives a signed POST with the shelfPublicId in the payload. See the Webhooks guide for signature verification and handler details. Poll the shelf status endpoint until it reaches a terminal state. curl " $SHELV_API_URL /v1/shelves/ $SHELF_PUBLIC_ID " \
-H " Authorization: Bearer $SHELV_API_KEY "
const res = await fetch ( `${ SHELV_API_URL } /v1/shelves/ ${ SHELF_PUBLIC_ID }` , {
headers : { Authorization : ` Bearer ${ SHELV_API_KEY }` },
});
const shelf = await res . json ();
import requests
res = requests.get(
f " { shelv_api_url } /v1/shelves/ { shelf_public_id } " ,
headers = { " Authorization " : f "Bearer { shelv_api_key } " },
)
shelf = res.json()
Common flow:
uploading -> parsing -> structuring -> verifying -> ready
If review=true, status stops at review until you call POST /v1/shelves/{shelfPublicId}/approve.
3. Browse the filesystem
When status is ready or review:
curl " $SHELV_API_URL /v1/shelves/ $SHELF_PUBLIC_ID /tree " \
-H " Authorization: Bearer $SHELV_API_KEY "
const res = await fetch ( `${ SHELV_API_URL } /v1/shelves/ ${ SHELF_PUBLIC_ID } /tree ` , {
headers : { Authorization : ` Bearer ${ SHELV_API_KEY }` },
});
const tree = await res . json ();
import requests
res = requests.get(
f " { shelv_api_url } /v1/shelves/ { shelf_public_id } /tree" ,
headers = { " Authorization " : f "Bearer { shelv_api_key } " },
)
tree = res.json()
4. Read a file
curl " $SHELV_API_URL /v1/shelves/ $SHELF_PUBLIC_ID /files/clauses/force-majeure.md " \
-H " Authorization: Bearer $SHELV_API_KEY "
const res = await fetch (
`${ SHELV_API_URL } /v1/shelves/ ${ SHELF_PUBLIC_ID } /files/clauses/force-majeure.md ` ,
{ headers : { Authorization : ` Bearer ${ SHELV_API_KEY }` } },
);
const markdown = await res . text ();
import requests
res = requests.get(
f " { shelv_api_url } /v1/shelves/ { shelf_public_id } /files/clauses/force-majeure.md" ,
headers = { " Authorization " : f "Bearer { shelv_api_key } " },
)
markdown = res.text
5. Choose how your agent accesses files
@shelv/mcp — MCP server for MCP clients (source )
OpenClaw skill — upload, poll, and hydrate from OpenClaw
GET /v1/shelves/{shelfPublicId}/tree for full tree access
GET /v1/shelves/{shelfPublicId}/files/* for individual file reads
GET /v1/shelves/{shelfPublicId}/archive-url for snapshots
GET /v1/helpers/download.sh for archive pull
@shelv/adapters for sandbox hydration and snapshots (source )
Already set up a webhook? You can skip polling entirely — see step 2.
Next steps
Shelves Understand lifecycle and status-gated endpoints.
Authentication Lock down API key and route auth behavior.
Webhooks Receive signed lifecycle events.
Archive Download Pull portable snapshots for CI and offline jobs.
MCP Server Connect Claude Code, Cursor, or any MCP client.