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

# Quickstart

> From zero to a generation event on your dashboard in about five minutes.

This guide takes you from nothing to a visible event in five minutes. You'll
install the SDK, point it at your ingest endpoint, and record one image
generation — submission and completion — then confirm it landed.

The whole integration is two ideas: call `keeps.capture()` around your own
provider calls, and reuse the returned `asset_id` to tie every event for one
generation together. There are no wrappers, no decorators, and no provider
patching — you stay in control of the request path.

<Steps>
  <Step title="Install the SDK">
    The package is pure Python with stdlib-only runtime dependencies.

    <CodeGroup>
      ```bash From the repo (editable) theme={null}
      uv pip install -e "mvp/sdk[dev]"
      ```

      ```bash From PyPI (placeholder name) theme={null}
      pip install keeps-sdk
      ```
    </CodeGroup>

    <Note>
      `keeps` is a placeholder package name and will change before a public release.
      Keep the import behind a single alias if you want a painless rename later.
    </Note>
  </Step>

  <Step title="Set your credentials">
    The SDK reads two environment variables. The API key authenticates you; the
    ingest URL is where events are delivered.

    ```bash theme={null}
    export KEEPS_API_KEY="kp_live_your_key_here"
    export KEEPS_INGEST_URL="https://ingest.yourcompany.com"
    ```

    `project_id` is derived server-side from your API key — you never send it and
    it is never a `capture()` argument.

    <Note>
      **Zero-config delivery.** With no `endpoint=`/`KEEPS_INGEST_URL`, events go to
      the hosted ingest (`https://ingest.keeps.dev`). Set `KEEPS_INGEST_URL`
      (or pass `endpoint=`) only to target a different environment — staging, or a
      local stack. You may pass a bare host; `init()` appends `/v1/events` for you.
    </Note>
  </Step>

  <Step title="Initialize once at startup">
    Call `keeps.init()` a single time when your process boots. It picks up
    `KEEPS_API_KEY` and `KEEPS_INGEST_URL` from the environment by default.

    ```python theme={null}
    import keeps

    keeps.init()  # reads KEEPS_API_KEY and KEEPS_INGEST_URL from the environment
    ```

    If you'd rather configure in code:

    ```python theme={null}
    keeps.init(api_key="kp_live_your_key_here", endpoint="https://ingest.yourcompany.com")
    ```

    If no API key is found (and you haven't explicitly disabled the SDK), `init()`
    logs a single warning and disables itself. It never raises.
  </Step>

  <Step title="Capture a generation">
    Record the submission first. Pass `asset_id=None` (the default) and `capture()`
    mints a uuid7 and returns it. Hold onto that id.

    ```python theme={null}
    aid = keeps.capture(
        model="fal-ai/flux/dev",
        status="submitted",
        inputs={"prompt": "a red bicycle on a beach at sunset", "steps": 28},
    )
    ```

    When the provider call returns, record completion by **reusing the same
    `asset_id`**. That's what correlates the two events into one generation on the
    backend — same id, same generation.

    ```python theme={null}
    keeps.capture(
        aid,
        status="completed",
        outputs={"url": "https://cdn.yourcompany.com/out/red-bicycle.png"},
    )
    ```

    <Note>
      Pass output URLs and metadata in `outputs`. If the provider returns the image
      inline, upload it to your own storage first and capture the resulting URL.
    </Note>
  </Step>

  <Step title="Flush before exit, then verify">
    `capture()` only appends to an in-memory buffer; a background thread delivers
    it about once a second. In a short script, serverless function, or any
    process that may exit before that tick, call `keeps.flush()` so in-buffer and
    in-flight events actually leave the process.

    ```python theme={null}
    keeps.flush()  # returns True if the buffer drained, False if it timed out
    ```

    Then open your dashboard. The generation appears as a single row keyed by
    `aid`, with `submitted` inputs and `completed` outputs folded together.
  </Step>
</Steps>

## Complete runnable example

Copy this into a file, set the two environment variables from Step 2, and run
it. It records one generation end-to-end and prints delivery counters so you
can confirm events actually shipped.

```python theme={null}
import keeps

# Reads KEEPS_API_KEY and KEEPS_INGEST_URL from the environment.
keeps.init()

# 1. Submission — mint a fresh asset_id and keep it.
aid = keeps.capture(
    model="fal-ai/flux/dev",
    status="submitted",
    inputs={"prompt": "a red bicycle on a beach at sunset", "steps": 28},
)
print("asset_id:", aid)

# ... your real provider call happens here ...

# 2. Completion — reuse the SAME asset_id to correlate.
keeps.capture(
    aid,
    status="completed",
    outputs={"url": "https://cdn.yourcompany.com/out/red-bicycle.png"},
)

# 3. Flush before the process exits, then inspect delivery counters.
delivered = keeps.flush()
print("flushed:", delivered)
print("stats:", keeps.stats())
```

A healthy run prints `emitted` and `sent` of `2` in the stats dict. If `sent`
is `0` while `dropped_send` climbs, delivery is failing — usually a wrong
ingest URL or API key. If `emitted` is `0`, the SDK disabled itself at
`init()` (check the warning log).

## "It didn't error" is not "it was delivered"

<Note>
  Every public call is **fail-open**: `capture()` never raises into your code and
  **always returns an `asset_id`** — even if the SDK is disabled, uninitialized,
  or misconfigured. That's a feature (instrumentation can never break your app),
  but it means a clean run tells you nothing about delivery. The source of truth
  is `keeps.stats()`:

  ```python theme={null}
  keeps.flush()
  print(keeps.stats())
  # {'initialized': True, 'disabled': False, 'emitted': 2, 'sent': 2,
  #  'batches': 1, 'retries': 0, 'dropped_overflow': 0, 'dropped_send': 0,
  #  'accepted': 2, 'rejected_by_server': 0}
  ```

  Watch `sent` vs `dropped_send`, and `accepted` — the server's own count of
  what it stored. If `sent` grows but `accepted` stays 0, check the per-event
  `rejected` reasons in the 202 body (also surfaced in `stats()`).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Core concepts" href="/core-concepts">
    Events, assets, and how reusing one `asset_id` merges a generation.
  </Card>

  <Card title="Recording provider calls" href="/recording-provider-calls">
    Patterns for fal, Replicate, and OpenAI — submit, poll, complete, fail.
  </Card>

  <Card title="Reliability" href="/reliability">
    Buffering, flushing, overflow, and what to do in serverless or forked workers.
  </Card>

  <Card title="API reference" href="/api-reference">
    Every parameter on `init`, `capture`, `context`, `flush`, and `stats`.
  </Card>
</CardGroup>
