keeps is observability for apps that generate media with models. If your
product calls fal, Replicate, OpenAI, or anything like them to make images,
video, or audio, keeps records what happened to every generation and turns
that stream into analytics you can act on.
Think PostHog, but the event is a generation, not a pageview.
The mental model
There’s exactly one idea to hold onto:You capture an asset — one generation — and append events to it as its life unfolds. The backend folds all those events into a single row per asset and powers your insights from there.A generation has a story: it gets submitted, it queues, it finishes or fails, and then a human accepts it, regenerates, or downloads it. Each of those moments is an event you append to the same asset. You don’t manage state or stitch things together — you just keep calling
capture() with the same
asset_id, and correlation happens server-side.
What keeps is
Event-sourced
You emit small, immutable events. The backend merges them — latest value
per field wins, and a finished status sticks — into one materialized row
per asset. Events can even arrive out of order.
Fail-open
No
keeps call ever raises into your code. capture() is a non-blocking
in-memory append. If our ingest is slow or down, your app does not notice.References, not bytes
Outputs are URLs and metadata — never raw media.
keeps never touches,
copies, or stores your generated pixels.Your dimensions
Attach
properties like film_id or prompt_version and break your
insights down by them. They’re your tags, not our schema.properties are stored as a single map per asset and folded per key
(latest value per key wins, by ts) — so you can append partial property bags
freely; a later event only touches the keys it sets and leaves the rest intact.
Details in Core concepts.What keeps is NOT
- Never in the request path.
keepsdoes not wrap, intercept, or proxy your provider calls. There is no auto-patching and there are no decorators — you instrument by callingcapture()yourself, on your terms. - Never a proxy. Your generation traffic goes straight to fal / Replicate
/ OpenAI exactly as it does today.
keepsonly sees the events you choose to emit on the side. - Never ships media bytes. You send references (URLs) and metadata. Raw images, video, and audio stay where they are.
keeps is side-band and fail-open, the worst case for your app is that
some analytics events are missed — never a slowed-down or broken generation.
How it works
Init once
Call
keeps.init() at startup with your API key (or set KEEPS_API_KEY /
KEEPS_INGEST_URL). Your project_id is derived server-side from that key
— you never send it.Capture per generation, append events
Call
keeps.capture() when a generation starts; reuse the returned
asset_id to append every later event (queued, completed, failed,
cancelled, plus your own annotations like “accepted”) to that same asset.capture() buffers in memory and a background thread delivers on a timer.
In short-lived, serverless, or worker processes that may exit before the next
tick, call keeps.flush() before your work returns so nothing is lost. See
Reliability.Where to go next
Quickstart
From
pip install to your first live asset in a few minutes.Core concepts
Assets, events, correlation by
asset_id, and how properties fold.