Documentation

Upload any file. Stow detects the type, generates metadata, deduplicates by content, and gives you a CDN URL.

Quick Start

1Create an account

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

2Create an API key

Go to your dashboard and create an API key. Copy it — 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 analysis

For images under 5 MB, Stow runs analysis before responding — your upload response includes auto-generated tags, description, category, and content rating. For larger files and non-image types, analysis runs in the background and the asset is updated once complete.

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

Create API keys in your dashboard. Each key is scoped to your account.

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.

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

MCP Server Setup

Stow ships an MCP server so AI coding tools (Claude Code, Cursor, etc.) can search, upload, and manage your media library through natural language.

Installation

Add this to your MCP config (e.g. .claude/settings.json or Cursor settings):

{
  "mcpServers": {
    "stow": {
      "command": "npx",
      "args": ["@usestow/mcp-server"],
      "env": {
        "STOW_API_URL": "https://api.usestow.com",
        "STOW_API_KEY": "stow_sk_..."
      }
    }
  }
}

Available tools

stow_search

Search your media library by type, tags, category, or text.

stow_get

Get an asset by ID, content hash, or key.

stow_upload

Upload a file. Only the file is required — Stow auto-detects everything else.

stow_get_or_create

Check if a file exists by content hash before uploading. Prevents duplicates.

Example prompts

  • “Upload this screenshot to Stow”
  • “Search Stow for landscape images tagged nature”
  • “Check if this file is already in Stow”

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.