> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keeps.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

> PostHog for generative media — a side-band observability layer for apps that generate images, video, and audio with models.

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

```python theme={null}
import keeps

keeps.init(api_key="kp_live_…")   # once, where your app starts

# Capture the asset — the first call mints and returns an id.
asset_id = keeps.capture(model="fal-ai/flux/dev", status="submitted",
                        inputs={"prompt": "studio product shot"})

# … your real provider call runs here …

# Append more events to the SAME asset as its story unfolds.
keeps.capture(asset_id, status="completed",
             outputs={"url": "https://cdn.example.com/out.png"})
keeps.capture(asset_id, properties={"accepted": "true"})   # what the user did with it

keeps.flush()   # best-effort delivery; see /reliability
```

That's the whole shape of it. Everything else in these docs is detail on top
of *capture an asset, append events*.

## What keeps is

<CardGroup cols={2}>
  <Card title="Event-sourced" icon="layer-group">
    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.
  </Card>

  <Card title="Fail-open" icon="shield-check">
    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.
  </Card>

  <Card title="URLs in, insights out" icon="link">
    Outputs are URLs and metadata; your media files stay wherever you host
    them.
  </Card>

  <Card title="Your dimensions" icon="tags">
    Attach `properties` like `film_id` or `prompt_version` and break your
    insights down by them. They're your tags, not our schema.
  </Card>
</CardGroup>

<Note>
  `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](/core-concepts).
</Note>

## What keeps is NOT

<Warning>
  `keeps` is a **neutral third party that sits beside your pipeline, never inside
  it.** This is a hard line, not a default.
</Warning>

* **Never in the request path.** `keeps` does not wrap, intercept, or proxy
  your provider calls. There is no auto-patching and there are no decorators —
  you instrument by calling `capture()` yourself, on your terms.
* **Never a proxy.** Your generation traffic goes straight to fal / Replicate
  / OpenAI exactly as it does today. `keeps` only 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.

Because `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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Query insights">
    The backend folds your events into one row per asset and serves the
    dashboard — failure rates, latency, spend, retries, broken down by any
    `properties` you attached.
  </Step>
</Steps>

<Note>
  `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](/reliability).
</Note>

## Where to go next

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    From `pip install` to your first live asset in a few minutes.
  </Card>

  <Card title="Core concepts" icon="diagram-project" href="/core-concepts">
    Assets, events, correlation by `asset_id`, and how `properties` fold.
  </Card>
</CardGroup>
