> For the complete documentation index, see [llms.txt](https://docs.roboflow.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roboflow.com/deployment/self-hosted/enterprise/deployment-manager/services/event-store.md).

# Event Store

The Event Store is an edge container service that records events produced by inference pipelines on the device. It holds inspection results, quality checks, safety alerts, and other Workflow outputs locally, serves them back over a REST API, and manages its own disk usage so a device never fills up.

Events written to the Event Store can also be backed up to Roboflow as [Vision Events](/deployment/monitoring-and-analytics/vision-events.md) for long-term storage and analysis.

{% hint style="info" %}
Event Store is available exclusively for Enterprise customers. [Contact the Roboflow sales team](https://roboflow.com/sales) to learn more.
{% endhint %}

## Connection Details

Replace `<device-ip>` with the IP address shown on the device page in Deployment Manager.

<table data-search="false"><thead><tr><th>Purpose</th><th>Address</th></tr></thead><tbody><tr><td>REST API</td><td><code>http://&#x3C;device-ip>:8001</code></td></tr><tr><td>Interactive Swagger docs</td><td><code>http://&#x3C;device-ip>:8001/docs</code></td></tr></tbody></table>

Port 8001 serves both over HTTP. See [Event Store REST API](/deployment/self-hosted/enterprise/deployment-manager/services/event-store/rest-api.md) for the endpoint reference.

To see live capacity and usage history for a device without calling the API, use [View Event Store Status](/deployment/self-hosted/enterprise/deployment-manager/monitoring/view-event-store-status.md).

## Storage Settings

Configure these from the Event Store card on the device's Configuration tab. Each maps to an environment variable on the service.

<table data-search="false"><thead><tr><th>Setting</th><th>Variable</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>"Retention Days"</td><td><code>RETENTION_DAYS</code></td><td><code>1</code></td><td>Events older than this many days are deleted.</td></tr><tr><td>"Max Records"</td><td><code>MAX_RECORDS</code></td><td><code>1000000</code></td><td>Maximum number of events kept. The oldest are removed once exceeded.</td></tr><tr><td>"Max Record Size"</td><td><code>MAX_RECORD_SIZE_BYTES</code></td><td><code>524288</code> (512 KB)</td><td>Maximum size of a single event record. Larger events are rejected.</td></tr><tr><td>"Max Storage"</td><td><code>MAX_STORAGE_BYTES</code></td><td><code>5368709120</code> (5 GB)</td><td>Total disk limit for stored files. Cleanup prunes files as usage approaches this limit.</td></tr><tr><td>"Cleanup Interval"</td><td><code>CLEANUP_INTERVAL_SECONDS</code></td><td><code>300</code></td><td>How often automatic cleanup runs.</td></tr></tbody></table>

Additional variables:

<table data-search="false"><thead><tr><th>Variable</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>PORT</code></td><td><code>8001</code></td><td>HTTP port for the API.</td></tr><tr><td><code>DATA_DIR</code></td><td><code>/data</code></td><td>Data storage directory on the device.</td></tr><tr><td><code>CONSISTENCY_CHECK_INTERVAL</code></td><td><code>12</code></td><td>Run a consistency check every N cleanups (roughly hourly at the default cleanup interval).</td></tr><tr><td><code>API_KEY</code></td><td>none</td><td>Optional API key. See <a href="#authentication">Authentication</a>.</td></tr></tbody></table>

## Automatic Cleanup

Cleanup runs on a dedicated background thread, so it never blocks API requests. Each scheduled pass runs three steps in order:

1. Retention. Delete records older than `RETENTION_DAYS`.
2. Record limit. If the record count exceeds `MAX_RECORDS`, delete records over the limit, already-uploaded records first and then the oldest.
3. Storage limit. As stored files approach `MAX_STORAGE_BYTES`, delete image files, already-uploaded files first and then the oldest. The parent record is preserved and its `current_file_count` is decremented.

Every `CONSISTENCY_CHECK_INTERVAL` cleanups the service also reconciles the database against the filesystem. It removes orphaned files on disk that have no matching database record, using a 10 minute grace period so in-flight writes are not touched, and removes dangling database records whose file is already gone.

You can also trigger a pass immediately with `POST /admin/cleanup`. See [Administration](/deployment/self-hosted/enterprise/deployment-manager/services/event-store/rest-api.md#administration).

### Image Ephemerality

Image IDs returned by the API are inherently short-lived. An ID that resolved a minute ago can 404 after a cleanup pass, and that is by design on storage-constrained edge hardware.

* Handle `404` responses when fetching images.
* Do not cache image IDs beyond a single session.
* Compare `current_file_count` against `original_file_count` on an event to tell whether its files were cleaned up.

## Draft Events and Video

A pipeline can save an event immediately and attach a video a few seconds later, once encoding finishes. Create the event with `draft: true`, upload the video when it is ready, then finalize the event. Events created without `draft` are finalized on creation, so existing pipelines are unaffected.

Until an event is finalized it is skipped by cloud upload. It is also protected from cleanup, but only while cloud upload is enabled on the service; with cloud upload off, a draft is as deletable as any other record.

<table data-search="false"><thead><tr><th>Setting</th><th>Variable</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>"Auto-finalize Drafts After"</td><td><code>DRAFT_AUTO_FINALIZE_SECONDS</code></td><td><code>3600</code></td><td>A draft whose video never arrives is force-finalized after this many seconds so it can back up and be cleaned up.</td></tr><tr><td>"Max Video Upload Size"</td><td><code>MAX_VIDEO_UPLOAD_BYTES</code></td><td><code>1073741824</code> (1 GB)</td><td>Largest single video that can be attached to an event.</td></tr></tbody></table>

{% hint style="warning" %}
Setting `DRAFT_AUTO_FINALIZE_SECONDS` to `0` disables the sweep. Drafts from a producer that never calls finalize then accumulate and, with cloud backup on, can fill the store and block new writes with HTTP 529 until the store is reset.
{% endhint %}

## Per-Image Metadata and Local-Only Files

Pipelines can attach extra data to an event that stays on the device and is never uploaded to the cloud.

Per-image metadata is a small single-level object of key/value data attached to an individual image, such as a verdict, serial number, or angle label. It is stored with the record and returned by the API, but it is not queryable and is excluded from backup. `METADATA_MAX_VALUE_LENGTH` (default `1000`) caps the length of a string value.

Local-only files are arbitrary files attached to an event, such as inspection blobs, thumbnails, or JSON. There is no content-type restriction, and `MAX_LOCAL_ONLY_FILE_UPLOAD_BYTES` (default `104857600`, 100 MB) caps each upload. Files can only be attached while the event is still a draft, so the pipeline must create the event with `draft: true`, attach the file, then finalize.

Both are described in detail in the [REST API reference](/deployment/self-hosted/enterprise/deployment-manager/services/event-store/rest-api.md).

## Cloud Backup

When Vision Events backup is enabled on the device, finalized events are uploaded to Roboflow. Two modes are available:

* Records and Files uploads event metadata along with the associated image files.
* Records Only uploads event metadata and leaves images on the device, which uses less bandwidth.

Per-image metadata and local-only files are never uploaded in either mode.

See [Send Events](/deployment/monitoring-and-analytics/vision-events/send-events.md) for enabling backup and querying the results in Roboflow.

### Upload Reliability

Failed uploads are retried. "Upload Abandonment Policy" controls what happens to a record the server keeps rejecting.

<table data-search="false"><thead><tr><th>Policy</th><th>Variable value</th><th>Behavior</th></tr></thead><tbody><tr><td>Never abandon</td><td><code>NEVER_ABANDON</code> (default)</td><td>Records retry forever until they succeed. Safest for data, but a device filling with stuck records starts rejecting new writes with HTTP 529 rather than dropping unuploaded ones.</td></tr><tr><td>Abandon after max attempts</td><td><code>ABANDON_AFTER_MAX_ATTEMPTS</code></td><td>After <code>UPLOAD_MAX_ATTEMPTS</code> content-error attempts (default <code>10</code>) a record is marked abandoned and becomes a preferred candidate for storage cleanup. Use this if losing persistently failing records is better than blocking new writes.</td></tr></tbody></table>

Only content-style 4xx responses from the server (ex: 400, 413, 422) advance the attempt counter. All 5xx responses, timeouts, network errors, and 401, 403, 404, 408, and 429 are treated as transient and retry regardless of policy.

`MIN_UPLOAD_IMAGE_BYTES` (default `1`) skips images smaller than the given size before upload. Corrupt but non-empty images currently come back as 5xx from the server and so retry forever under either policy. Raising this value above typical corruption sizes is the workaround.

## Authentication

API key authentication is optional and off by default. Set `API_KEY` on the service to turn it on, after which every endpoint except `/health` requires the `X-API-Key` header.

```yaml
environment:
  - API_KEY=your-secret-api-key-here
```

```bash
curl -H "X-API-Key: your-secret-api-key-here" \
  http://<device-ip>:8001/v2/events/latest/query
```

`/health` stays reachable without authentication so monitoring systems and load balancers can poll it.
