Developers

Build private media search into your own app

MediaFind isn't only a desktop app — underneath it is an engine that transcribes, embeds, and searches audio and video entirely on-device. There are two stable, documented ways to build on it: an in-process Python SDK and a frozen HTTP API. Same engine, no API keys, no cloud, and a real versioning promise so your integration doesn't rot.

Every surface of MediaFind — the desktop app, the mediafind CLI, the MCP server for AI agents — is a face on one engine. The interesting consequence: if you're building your own product and you want “search my users' footage by meaning, on their machine, without shipping it to a server,” that engine is available to you directly. You don't rebuild transcription, embeddings, and a vector index; you call the same one the app uses.

The catch with embedding someone else's internals is that they move. So the developer surface is deliberately narrow and pinned: a small set of names that won't change under you, with everything else marked internal.

Two doors to the same engine

Pick the door that matches where your code lives.

Your app any language Python SDK mediafind.Engine · in-process HTTP API /api/v1 · over localhost Engine app · CLI · MCP Local index on your disk
Same engine, same local index. The SDK runs it in your process (fastest, zero network); the HTTP API runs it behind mediafind serve so any language or a separate process can reach it.

The SDK: mediafind.Engine

If your app is Python, this is the shortest path. Install the package, open an Engine, and call it:

pip install mediafind          # core: transcription + transcript search + Ask
pip install "mediafind[visual]"  # + CLIP visual/scene search
pip install "mediafind[llm]"     # + local, keyless LLM for Ask
from mediafind import Engine

with Engine() as mf:                    # default library (respects MEDIAFIND_DATA)
    mf.index("~/Movies")                # transcribe + embed a file or folder
    hits = mf.search("the part about pricing", k=5)
    answer = mf.ask("what did we decide about pricing?")
    print(answer["answer"], answer["citations"])

The whole public surface is five methods — index, search, ask, transcript, status — hanging off an Engine that's cheap to hold and safe to share (reads open and close a short-lived index per call). ask returns a dict with answer, citations, confidence and grounded, so you get the receipts, not just prose (why that matters).

It ships a PEP 561 py.typed marker, so your type-checker sees real annotations — autocomplete and mypy work out of the box. And it runs entirely in your process: no server to start, no port to open, no socket at all. For a Python app that wants search on the same box, nothing is faster.

The contract is mediafind.core (re-exported as mediafind.Engine) — and only that. Reaching into mediafind.index, mediafind.search, and the rest is unsupported; those move between releases. Depend on the documented names and your integration has a versioned promise instead of a time bomb.

The HTTP API: /api/v1

Not in Python? Running the engine in one process and your app in another? Start the local server and talk to it over HTTP:

mediafind serve            # local HTTP API on http://localhost:8000

curl -X POST http://localhost:8000/api/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "the part about pricing", "k": 5}'

The versioned surface mirrors the SDK — four endpoints, same concepts:

The /api/v1 routes aren't the same thing as the app's internal /api/* endpoints (those change with the web UI and promise nothing). Request and response shapes here are pinned with Pydantic models: new optional fields may appear, but nothing existing changes or disappears without a major-version bump.

Because it's a real server and not a thin wrapper, it also inherits the engine's back-pressure: the same read path as the app, a busy-timeout so reads don't jam behind an active indexer, and a load-shed slot that returns 503 Retry-After instead of melting under a burst (how that works). If you point a queue of workers at it, it degrades politely.

Same engine means the same rules

Both doors open onto the identical engine, so its guarantees apply either way. It's keyless and offline — no account, no token to call an outside service, nothing uploaded. The free tier caps how many files you can index; Pro (one-time) lifts the cap and unlocks the Pro-only channels. Ask for a Pro channel you're not entitled to — say modality="logo" — and you get the same clean paywall the app shows (a 402 over HTTP, a typed error in the SDK). One gate, enforced everywhere.

Where the network is — and isn't

The SDK opens no socket at all; it's a library call. The HTTP server binds to localhost by default, so it's reachable only from the same machine. Exposing it to other devices is possible but deliberately explicit — it takes a bind address, an auth token, and, for anything past your own LAN, TLS:

MEDIAFIND_AUTH_TOKEN=… mediafind serve \
  --host 0.0.0.0 --ssl-keyfile key.pem --ssl-certfile cert.pem

Nothing about “make it reachable” happens by accident. You can confirm the local-only default the same way you can for the rest of the engine:

$ mediafind audit
✓ core path opened 0 external sockets.

Which door should you use?

Reach for the SDK when your app is Python and lives on the same machine as the library — it's the fastest path and has no moving parts. Reach for the HTTP API when you're in another language, another process, a container, or an on-prem box that a few clients share and you want the server's concurrency and back-pressure. And if the thing calling you is an AI agent, there's a third door built for exactly that — the MCP server.


The engine was always the product; the app is just its most visible face. A typed in-process SDK and a frozen HTTP contract are how that engine becomes infrastructure — private media search you can build on, with a stability promise instead of a moving target.

Build on the engine

On-device, keyless, and versioned. No accounts, no API keys, nothing uploaded.

Read the developer docs Download for macOS