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

# Events

> The core data model -- append-only, immutable audit log entries.

## Overview

Events are the fundamental unit of Immutable. Each event represents a single action taken by an actor on a resource. Events are **append-only** -- once recorded, they cannot be modified or deleted through the API.

The API provides no `PUT`, `PATCH`, or `DELETE` endpoints for events. Each event is cryptographically linked to the previous event in a [hash chain](/guides/hash-chain), making any tampering at the database level detectable.

## Event Anatomy

When you retrieve an event from the API, it contains the following fields:

<ResponseField name="id" type="string">
  UUID v4 identifier, pre-generated at ingestion time.
</ResponseField>

<ResponseField name="actor" type="object">
  The entity that performed the action.

  * `actor.id` (string) -- Identifier of the actor. Example: `user_2hG9kLm`.
  * `actor.name` (string | null) -- Human-readable name. Example: `Sarah Chen`.
  * `actor.type` (string | null) -- Type of actor. Example: `user`, `system`, `api_client`.
</ResponseField>

<ResponseField name="action" type="string">
  The action performed, typically in `resource.verb` format. Example: `document.created`.
</ResponseField>

<ResponseField name="action_category" type="string | null">
  Optional category for grouping actions. Example: `documents`, `billing`.
</ResponseField>

<ResponseField name="resource" type="object">
  The primary resource affected.

  * `resource.type` (string | null) -- Resource type. Example: `document`, `invoice`.
  * `resource.id` (string | null) -- Resource identifier. Example: `doc_8nXpQr3`.
  * `resource.name` (string | null) -- Human-readable name. Example: `Q4 Financial Report`.
</ResponseField>

<ResponseField name="targets" type="array">
  Array of additional resources involved. Each target has `type`, `id`, `name`, and optional `metadata`. See [Targets](/guides/targets).
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Arbitrary JSON object for additional context. Example: `{"folder": "reports", "pages": 12}`.
</ResponseField>

<ResponseField name="tenant_id" type="string | null">
  Your customer's tenant identifier for multi-tenant isolation. Example: `org_7rT2xBc`.
</ResponseField>

<ResponseField name="session_id" type="string | null">
  Session identifier for grouping related events. See [Session Tracking](/guides/session-tracking).
</ResponseField>

<ResponseField name="ip_country" type="string | null">
  Two-letter ISO country code derived from the client IP. Example: `US`.
</ResponseField>

<ResponseField name="ip_city" type="string | null">
  City name derived from the client IP. Example: `San Francisco`.
</ResponseField>

<ResponseField name="idempotency_key" type="string | null">
  Unique key for deduplication. Duplicate events with the same key return the original event ID with status `duplicate`.
</ResponseField>

<ResponseField name="version" type="integer | null">
  Version number for the event schema. Useful for tracking event format changes over time.
</ResponseField>

<ResponseField name="integrity" type="object">
  Hash chain proof.

  * `integrity.event_hash` (string | null) -- SHA-256 hash of this event.
  * `integrity.previous_event_hash` (string | null) -- Hash of the preceding event in the workspace chain.
</ResponseField>

<ResponseField name="occurred_at" type="string">
  ISO 8601 timestamp of when the event occurred (client time). Defaults to server time if omitted during ingestion.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 timestamp with microsecond precision of when the event was recorded server-side.
</ResponseField>

## Example Event

```json theme={null}
{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "actor": {
    "id": "user_2hG9kLm",
    "name": "Sarah Chen",
    "type": "user"
  },
  "action": "document.updated",
  "action_category": "documents",
  "resource": {
    "type": "document",
    "id": "doc_8nXpQr3",
    "name": "Q4 Financial Report"
  },
  "metadata": {
    "changes": ["title", "content"],
    "previous_title": "Q3 Financial Report"
  },
  "targets": [
    { "type": "folder", "id": "folder_3mK9pLq", "name": "Reports" }
  ],
  "tenant_id": "org_7rT2xBc",
  "session_id": "sess_4kN8pLm",
  "ip_country": "US",
  "ip_city": "San Francisco",
  "idempotency_key": null,
  "version": null,
  "integrity": {
    "event_hash": "a3f2c8d1e4b567890abcdef1234567890abcdef1234567890abcdef12345678",
    "previous_event_hash": "7b9e1d3f5a2c67890abcdef1234567890abcdef1234567890abcdef12345678"
  },
  "occurred_at": "2026-03-15T14:32:18.847312Z",
  "created_at": "2026-03-15T14:32:18.847312Z"
}
```

## Async Processing

Event ingestion is asynchronous. When you `POST /api/v1/events`, the API returns `202 Accepted` immediately with a pre-generated UUID. The event is processed in a background queue (hash computation, geo enrichment, alert evaluation, log stream fanout).

```json theme={null}
{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "status": "queued"
}
```

## Idempotency

Include an `idempotency_key` to prevent duplicate events from retries or network issues. If Immutable receives a second event with the same idempotency key within the same workspace, it returns the original event ID with status `duplicate`:

```json theme={null}
{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "status": "duplicate"
}
```

## Geo Enrichment

Immutable automatically enriches events with geographic data derived from the client IP address using MaxMind GeoLite2:

* `ip_country` -- Two-letter ISO country code (e.g. `US`, `GB`, `DE`)
* `ip_city` -- City name (e.g. `San Francisco`, `London`)

Private/internal IP addresses return `null` for both fields. See [Geolocation](/guides/geolocation) for details.

## Microsecond Timestamps

All timestamps use microsecond precision (`TIMESTAMP(6)`) and the format `Y-m-d H:i:s.u`. This enables precise ordering even during high-throughput ingestion and ensures stable cursor pagination.
