One media library for all your apps. Upload files, attach metadata, and access them from anywhere with a single API.
Upload your first file in 30 seconds
Connect multiple applications
Programmatic admin access
AI agent integration
Sign up for a free account — no credit card required.
Go to Apps and register your application. Copy the API key — you'll only see it once.
Just send a file. That's it — no metadata required.
curl -X POST https://api.usestow.com/v1/assets \
-H "Authorization: Bearer stow_sk_..." \
-F file=@photo.pngStow auto-detects the media type, generates a title from the filename, and returns a CDN URL. If analysis is enabled on your plan, tags, description, and category are generated automatically.
curl https://api.usestow.com/v1/assets/search \
-H "Authorization: Bearer stow_sk_..." \
-H "Content-Type: application/json" \
-d '{"media_type":"image","tags":["nature"]}'When you upload a file, Stow automatically detects:
On the Pro plan, Stow automatically processes your uploads: transcribes audio, summarizes content, and generates tags, descriptions, and categories. For images under 5 MB, analysis runs before responding. For audio, documents, and larger files, processing runs in the background.
Each app can define an extraction schema — a set of fields to extract as structured tags (e.g., ticker:AAPL, speaker:tim-cook). Configure this in the Apps dashboard.
You can always override any auto-generated field by including it in the metadata form field.
Every file is hashed on upload using SHA256. If you upload the same file twice — even with a different filename — Stow returns the existing asset instead of creating a duplicate.
# First upload — creates the asset
curl -F file=@sunset.png ... → 201 Created
# Same file, different name — returns existing
curl -F file=@sunset-copy.png ... → 200 OK (existing asset)You can also check for duplicates before uploading by looking up a content hash:
GET /v1/assets/by-content-hash/a1b2c3d4...All API requests (except health check) require a Bearer token:
Authorization: Bearer stow_sk_...Add your apps in the dashboard. Each app gets its own API key, but all apps share the same library.
Base URL: https://api.usestow.com
/v1/healthHealth check. No auth required.
{ "status": "ok", "service": "stow-api", "version": "0.1.0" }/v1/assetsUpload a file. Send as multipart form data. Only the file is required — everything else is optional.
| Parameter | Type | Description |
|---|---|---|
file* | File | The file to upload (any type) |
metadata | JSON string | Optional metadata (see below). Omit to let Stow auto-generate everything. |
| Parameter | Type | Description |
|---|---|---|
title | string | Title. Auto-generated from filename if omitted. |
description | string | Description. Auto-generated by AI analysis if omitted. |
tags | string[] | Tags for filtering. Auto-generated by AI if omitted. |
category | string | Category path (e.g. "nature/landscape"). Auto-generated if omitted. |
content_rating | string | general, teen, mature, or adult. Auto-detected if omitted. |
key | string | Human-readable lookup key (e.g. "hero-banner") |
format | string | File format override. Auto-detected from file contents. |
duration_seconds | number | Duration for audio/video files |
Returns 201 with the created asset. If a duplicate exists (same content hash), returns 200 with the existing asset.
/v1/assetsUpload raw text. Send as JSON instead of multipart. Use this for scraped content, articles, notes, or any text you want to store and analyze.
| Parameter | Type | Description |
|---|---|---|
text* | string | The text content to store |
title | string | Title. Auto-generated by AI if omitted. |
description | string | Description. Auto-generated if omitted. |
tags | string[] | Tags for filtering. Auto-generated if omitted. |
category | string | Category path. Auto-generated if omitted. |
content_rating | string | general, teen, mature, or adult. Defaults to general. |
key | string | Human-readable lookup key |
Set Content-Type: application/json to use this mode. The text is stored as a document and can be analyzed by AI on the Pro plan.
curl -X POST https://api.usestow.com/v1/assets \
-H "Authorization: Bearer stow_sk_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Full article or scraped content here...",
"title": "Discussion about topic X",
"tags": ["reddit", "discussion"]
}'/v1/assets/:idGet an asset by UUID.
/v1/assets/by-content-hash/:hashLook up by content hash (SHA256). Use this to check for duplicates before uploading.
GET /v1/assets/by-content-hash/a1b2c3d4e5f6.../v1/assets/by-key/:type/:keyLook up by type and human-readable key. Edge-cached.
GET /v1/assets/by-key/image/hero-banner/v1/assets/by-hash/:type/:hashLook up by type and prompt hash (legacy). Edge-cached.
/v1/assets/searchSearch assets with filters.
| Parameter | Type | Description |
|---|---|---|
media_type | string | Filter by media type: image, audio, video, document, other |
tags | string[] | Filter by tags (all must match) |
category | string | Filter by category |
content_rating | string | Max content rating to include |
key | string | Filter by key (exact match) |
text | string | Full-text search across title, description, and filename |
limit | number | Max results (default 25, max 100) |
offset | number | Pagination offset |
{
"data": [{ ... }],
"count": 3
}/v1/assets/:id/useIncrement the use count for an asset. Fire-and-forget.
{ "ok": true }{
"id": "uuid",
"media_type": "image",
"mime_type": "image/png",
"content_hash": "a1b2c3d4e5f6...64 hex chars",
"original_filename": "sunset-mountains.png",
"title": "Sunset Over Mountains",
"description": "A vibrant sunset behind a mountain range",
"tags": ["nature", "landscape", "sunset"],
"category": "nature/landscape",
"content_rating": "general",
"analysis_status": "completed",
"storage_path": "image/a1b2c3d4...f6.png",
"public_url": "https://api.usestow.com/r2/image/a1b2c3d4...f6.png",
"format": "png",
"file_size_bytes": 2048000,
"use_count": 12,
"created_at": "2026-03-07T12:00:00Z"
}| Status | Meaning |
|---|---|
| none | No analysis requested (quota exceeded or not applicable) |
| pending | Analysis is running in the background |
| completed | Analysis finished, metadata is enriched |
| failed | Analysis failed (asset still usable, just not enriched) |
Assets can have a content rating, either set manually or auto-detected. When searching, pass content_rating to set the maximum rating to include.
| Rating | Description |
|---|---|
| general | Safe for all audiences |
| teen | Mild violence or tension |
| mature | Graphic violence, horror |
| adult | Explicit content |
All errors return JSON:
{ "error": "Human-readable error message" }| Status | Meaning |
|---|---|
| 400 | Bad request (validation error) |
| 401 | Missing or invalid API key |
| 402 | Plan limit reached (upgrade required) |
| 404 | Asset not found |
| 429 | Rate limit exceeded (try again later) |
| 500 | Internal server error |
Stow is designed for developers with multiple applications. Each app gets its own API key, but they all share the same media library.
Go to Apps in the dashboard and register your application. You'll get an API key to use in that app.
Service keys give programmatic access to admin operations — creating app keys, viewing usage, and managing assets. Use them in automation, CI/CD, or AI agents.
| Prefix | Type | Access |
|---|---|---|
| stow_sk_ | App key | Upload, search, retrieve assets |
| stow_ak_ | Service key | Everything app keys can do, plus: create/revoke app keys, view usage, delete assets |
Go to Apps in the dashboard. Under Service Keys, click “Create service key.” Copy the key — you'll only see it once.
Service keys can access all /v1/admin/* endpoints:
/v1/admin/keysCreate a new app key.
| Parameter | Type | Description |
|---|---|---|
name* | string | Name for the app (e.g. "my-app") |
Returns the full API key (shown once), key hash, and prefix. The new key inherits your account's plan.
curl -X POST https://api.usestow.com/v1/admin/keys \
-H "Authorization: Bearer stow_ak_..." \
-H "Content-Type: application/json" \
-d '{"name": "my-new-app"}'/v1/admin/keysList all API keys on the account.
/v1/admin/keys/:hashRevoke an API key by its hash. The key is immediately invalidated.
/v1/admin/usageGet account usage: asset count, storage used, number of apps, and requests today.
/v1/admin/assets/:idDelete an asset by UUID. Permanently removes the file from storage.
The Stow MCP server lets AI agents interact with your media library directly. Upload files, search assets, create app keys — all through natural language.
Install the MCP server from npm:
npm install -g @usestow/mcp-serverAdd to your MCP client configuration (e.g. Claude Desktop, Claude Code):
{
"mcpServers": {
"stow": {
"command": "stow-mcp",
"env": {
"STOW_API_KEY": "stow_ak_..."
}
}
}
}Use a service key (stow_ak_) to get admin tools. An app key (stow_sk_) limits the server to upload, search, get, and get-or-create only.
| Tool | Key type | Description |
|---|---|---|
| stow_search | Any | Search assets with filters |
| stow_get | Any | Get asset by ID |
| stow_upload | Any | Upload a file from a URL |
| stow_get_or_create | Any | Get by key, or upload if missing |
| stow_create_app | Service | Create a new app and API key |
| stow_list_apps | Service | List all apps with metadata |
| stow_revoke_app | Service | Revoke an app key |
| stow_usage | Service | View account usage stats |
| stow_upload_text | Service | Store raw text content |
| stow_delete_asset | Service | Delete an asset |
Want your AI coding assistant to set up Stow automatically? Here's how to make it self-service.
In the dashboard, create a service key. This gives your AI agent admin access to create app keys on your behalf.
Add a Stow section to your project's AI configuration (e.g. CLAUDE.md):
## Stow — Media Library
This project uses Stow (usestow.com) for file storage.
- API: https://api.usestow.com
- Docs: https://usestow.com/docs
- App key: stow_sk_... (in .env as STOW_API_KEY)With the MCP server configured using a service key, your AI can create app keys for new projects:
> "Set up Stow for this project"
AI calls stow_create_app with name "my-new-app"
→ Returns stow_sk_... key
→ AI adds it to .env
→ Ready to upload and searchThe simplest possible upload — just send a file:
const form = new FormData()
form.append('file', photoBlob, 'sunset.png')
const res = await fetch('https://api.usestow.com/v1/assets', {
method: 'POST',
headers: { Authorization: `Bearer ${STOW_KEY}` },
body: form,
})
const asset = await res.json()
console.log(asset.public_url) // CDN URL
console.log(asset.media_type) // "image"
console.log(asset.tags) // ["nature", "landscape", "sunset"]Override any auto-generated field by including a metadata JSON blob:
const form = new FormData()
form.append('file', audioBlob, 'tavern-ambiance.mp3')
form.append('metadata', JSON.stringify({
title: 'Tavern Ambiance',
tags: ['fantasy', 'interior', 'social'],
category: 'environment/interior',
key: 'tavern',
}))
const res = await fetch('https://api.usestow.com/v1/assets', {
method: 'POST',
headers: { Authorization: `Bearer ${STOW_KEY}` },
body: form,
})const { data } = await fetch('https://api.usestow.com/v1/assets/search', {
method: 'POST',
headers: {
Authorization: `Bearer ${STOW_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
media_type: 'image',
tags: ['landscape'],
limit: 10,
}),
}).then(r => r.json())Compute a SHA256 hash client-side and check before uploading:
// Hash the file
const hashBuffer = await crypto.subtle.digest('SHA-256', fileBuffer)
const hashHex = [...new Uint8Array(hashBuffer)]
.map(b => b.toString(16).padStart(2, '0')).join('')
// Check if it exists
const res = await fetch(
`https://api.usestow.com/v1/assets/by-content-hash/${hashHex}`,
{ headers: { Authorization: `Bearer ${STOW_KEY}` } }
)
if (res.ok) {
// Already exists — use the existing asset
const existing = await res.json()
console.log('Already uploaded:', existing.public_url)
} else {
// New file — upload it
}Need help? Create an account to get started.