Documentation

One media library for all your apps. Upload files, attach metadata, and access them from anywhere with a single API.

Quick Start

1Create an account

Sign up for a free account — no credit card required.

2Add an app

Go to Apps and register your application. Copy the API key — you'll only see it once.

3Upload a file

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.png

Stow 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.

4Search your library

curl https://api.usestow.com/v1/assets/search \
  -H "Authorization: Bearer stow_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"media_type":"image","tags":["nature"]}'

Auto-Detection

When you upload a file, Stow automatically detects:

  • Media type— image, audio, video, document, or other. Detected from file contents (magic bytes), not the extension.
  • MIME type— The specific content type (e.g. image/png, audio/mpeg).
  • Content hash— SHA256 of the file bytes. Same file = same hash, regardless of filename. Used for dedup.
  • Title— Generated from the filename if not provided (e.g. “my-photo_2024.png” → “My Photo 2024”).

AI processing (Pro plan)

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.

Content Deduplication

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...

Authentication

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.

API Reference

Base URL: https://api.usestow.com

GET/v1/health

Health check. No auth required.

{ "status": "ok", "service": "stow-api", "version": "0.1.0" }
POST/v1/assets

Upload a file. Send as multipart form data. Only the file is required — everything else is optional.

Form fields

ParameterTypeDescription
file*FileThe file to upload (any type)
metadataJSON stringOptional metadata (see below). Omit to let Stow auto-generate everything.

Metadata fields (all optional)

ParameterTypeDescription
titlestringTitle. Auto-generated from filename if omitted.
descriptionstringDescription. Auto-generated by AI analysis if omitted.
tagsstring[]Tags for filtering. Auto-generated by AI if omitted.
categorystringCategory path (e.g. "nature/landscape"). Auto-generated if omitted.
content_ratingstringgeneral, teen, mature, or adult. Auto-detected if omitted.
keystringHuman-readable lookup key (e.g. "hero-banner")
formatstringFile format override. Auto-detected from file contents.
duration_secondsnumberDuration for audio/video files

Returns 201 with the created asset. If a duplicate exists (same content hash), returns 200 with the existing asset.

POST/v1/assets

Upload raw text. Send as JSON instead of multipart. Use this for scraped content, articles, notes, or any text you want to store and analyze.

JSON body

ParameterTypeDescription
text*stringThe text content to store
titlestringTitle. Auto-generated by AI if omitted.
descriptionstringDescription. Auto-generated if omitted.
tagsstring[]Tags for filtering. Auto-generated if omitted.
categorystringCategory path. Auto-generated if omitted.
content_ratingstringgeneral, teen, mature, or adult. Defaults to general.
keystringHuman-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"]
  }'
GET/v1/assets/:id

Get an asset by UUID.

GET/v1/assets/by-content-hash/:hash

Look up by content hash (SHA256). Use this to check for duplicates before uploading.

GET /v1/assets/by-content-hash/a1b2c3d4e5f6...
GET/v1/assets/by-key/:type/:key

Look up by type and human-readable key. Edge-cached.

GET /v1/assets/by-key/image/hero-banner
GET/v1/assets/by-hash/:type/:hash

Look up by type and prompt hash (legacy). Edge-cached.

POST/v1/assets/search

Search assets with filters.

Body parameters

ParameterTypeDescription
media_typestringFilter by media type: image, audio, video, document, other
tagsstring[]Filter by tags (all must match)
categorystringFilter by category
content_ratingstringMax content rating to include
keystringFilter by key (exact match)
textstringFull-text search across title, description, and filename
limitnumberMax results (default 25, max 100)
offsetnumberPagination offset
{
  "data": [{ ... }],
  "count": 3
}
POST/v1/assets/:id/use

Increment the use count for an asset. Fire-and-forget.

{ "ok": true }

Asset Object

{
  "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"
}

Analysis status

StatusMeaning
noneNo analysis requested (quota exceeded or not applicable)
pendingAnalysis is running in the background
completedAnalysis finished, metadata is enriched
failedAnalysis failed (asset still usable, just not enriched)

Content Ratings

Assets can have a content rating, either set manually or auto-detected. When searching, pass content_rating to set the maximum rating to include.

RatingDescription
generalSafe for all audiences
teenMild violence or tension
matureGraphic violence, horror
adultExplicit content

Error Responses

All errors return JSON:

{ "error": "Human-readable error message" }
StatusMeaning
400Bad request (validation error)
401Missing or invalid API key
402Plan limit reached (upgrade required)
404Asset not found
429Rate limit exceeded (try again later)
500Internal server error

Apps

Stow is designed for developers with multiple applications. Each app gets its own API key, but they all share the same media library.

How apps work

  • Shared library— All apps under your account access the same files. Upload from App A, use in App B.
  • Source tracking— Each upload is tagged with the app that uploaded it. Filter by app in the dashboard or via search.
  • My Library— Files uploaded through the dashboard or scrapers go to “My Library” — a shared space all apps can access.

Adding an app

Go to Apps in the dashboard and register your application. You'll get an API key to use in that app.

Service Keys

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.

Key types

PrefixTypeAccess
stow_sk_App keyUpload, search, retrieve assets
stow_ak_Service keyEverything app keys can do, plus: create/revoke app keys, view usage, delete assets

Creating a service key

Go to Apps in the dashboard. Under Service Keys, click “Create service key.” Copy the key — you'll only see it once.

Admin endpoints

Service keys can access all /v1/admin/* endpoints:

POST/v1/admin/keys

Create a new app key.

ParameterTypeDescription
name*stringName 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"}'
GET/v1/admin/keys

List all API keys on the account.

DELETE/v1/admin/keys/:hash

Revoke an API key by its hash. The key is immediately invalidated.

GET/v1/admin/usage

Get account usage: asset count, storage used, number of apps, and requests today.

DELETE/v1/admin/assets/:id

Delete an asset by UUID. Permanently removes the file from storage.

MCP Server

The Stow MCP server lets AI agents interact with your media library directly. Upload files, search assets, create app keys — all through natural language.

Setup

Install the MCP server from npm:

npm install -g @usestow/mcp-server

Configuration

Add 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.

Available tools

ToolKey typeDescription
stow_searchAnySearch assets with filters
stow_getAnyGet asset by ID
stow_uploadAnyUpload a file from a URL
stow_get_or_createAnyGet by key, or upload if missing
stow_create_appServiceCreate a new app and API key
stow_list_appsServiceList all apps with metadata
stow_revoke_appServiceRevoke an app key
stow_usageServiceView account usage stats
stow_upload_textServiceStore raw text content
stow_delete_assetServiceDelete an asset

AI Onboarding

Want your AI coding assistant to set up Stow automatically? Here's how to make it self-service.

1. Create a service key

In the dashboard, create a service key. This gives your AI agent admin access to create app keys on your behalf.

2. Add to your AI config

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)

3. Let AI create app keys

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 search

Integration Guide

Upload with JavaScript

The 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"]

Upload with metadata

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,
})

Search your library

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())

Check for duplicates

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.