> 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

Store inference events on the edge device with automatic retention, storage limits, and optional cloud backup.

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 runs cleanup to limit disk use. If protected data prevents cleanup, the store blocks data requests until space is available.

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>Eligible events older than this many days are deleted. Pending uploads and drafts can stay longer.</td></tr><tr><td>"Max Records"</td><td><code>MAX_RECORDS</code></td><td><code>1000000</code></td><td>Record count cleanup target. Protected records can prevent cleanup from reaching it.</td></tr><tr><td>"Max Record Size (KiB)"</td><td><code>MAX_RECORD_SIZE_BYTES</code></td><td><code>524288</code> (512 KiB)</td><td>Maximum stored event metadata size, excluding image and video bytes. The app field is in KiB. A separate request limit also applies.</td></tr><tr><td>"Max Storage (GiB)"</td><td><code>MAX_STORAGE_BYTES</code></td><td><code>5368709120</code> (5 GiB)</td><td>Cleanup limit for tracked files, not total filesystem use. The app field is in GiB.</td></tr><tr><td>"Cleanup Interval (sec)"</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 background thread and commits in batches, but can still compete with requests for database access. Each scheduled pass runs three steps in order:

1. Retention. Delete eligible records older than `RETENTION_DAYS`, measured from creation in this store.
2. Record limit. If the record count exceeds `MAX_RECORDS`, delete eligible records, already-uploaded records first and then the oldest in each group.
3. Storage limit. Delete eligible files to reach `CLEANUP_PRUNE_THRESHOLD` percent of `MAX_STORAGE_BYTES` (default `95`), already-uploaded files first and then the oldest. The parent record is preserved and its `current_file_count` is decremented.

With cloud backup enabled, drafts and pending uploads are protected. Finalized abandoned records and local-only schemas can be removed under capacity pressure. In "Records and Files" mode, files awaiting backup are also protected; "Records Only" allows files on finalized events to be removed without backup. With backup disabled, cleanup can remove unuploaded events and unfinished drafts.

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 tracking rows for files that are 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 (sec)"</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 (MB)"</td><td><code>MAX_VIDEO_UPLOAD_BYTES</code></td><td><code>1073741824</code> (1 GiB, entered as <code>1024</code> in the app)</td><td>Largest single video that can be attached to an event on the device. The variable is in bytes; the app field is in MB.</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 data requests with HTTP 507. Finalize the stuck drafts so backup and cleanup can proceed.
{% endhint %}

## Per-Image Metadata and Local-Only Files

Pipelines can attach extra data to an event beyond its images and predictions.

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. With "Records and Files" backup, it uploads with each image. `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. They stay on the device and are never uploaded. There is no content-type restriction, and `MAX_LOCAL_ONLY_FILE_UPLOAD_BYTES` (default `104857600`, 100 MiB) 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, per-image metadata, and video clips.
* Records Only uploads event metadata and leaves images and videos on the device, which uses less bandwidth.

Local-only files are never uploaded in either mode.

In Records and Files mode, MP4 and MOV clips upload, up to four per event, and play in the Vision Events dashboard. Clips in other formats (ex: MKV, AVI), clips past the fourth, and clips over 3 GiB (the cloud limit) stay on the device. The device cap ("Max Video Upload Size (MB)") is separate and defaults to 1 GiB, so raise it past 3 GiB only for clips you keep on the device.

A retryable clip upload failure holds back its whole event for `UPLOAD_RETRY_DELAY_SECONDS` (default `3600`, 1 hour) before the next try. After `VIDEO_UPLOAD_MAX_ATTEMPTS` counted attempts (default `3`), the event proceeds without waiting for the clip. Some failures do not consume an attempt, so this is not a delivery deadline.

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 507 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 data and administration routes require the `X-API-Key` header. `/health`, `/docs`, `/redoc`, and `/openapi.json` remain accessible without it.

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