Skip to main content
ImmutableLog logo
BackAPI v1

API Documentation

Complete reference for the ImmutableLog REST API. Learn how to authenticate, send events, query the immutable history, and cryptographically verify the integrity of any record.

* required field or header

SDKs & Integrations

Integrate ImmutableLog directly in your language using automatic middleware for web frameworks or an HTTP client for workers and jobs.

Host agent

Linux

Records every access and command on the server, without changing your code. It covers what the SDK cannot see: SSH logins, rejected attempts, and commands executed as root.

View agent docs →

curl -sSL .../agent | sh

Base URL

All endpoints in this documentation are relative to the base URL below. Always use HTTPS in production to ensure data confidentiality in transit.

text
https://api.immutablelog.com

Authentication*

Every API request requires an access token sent in the `Authorization` header using the Bearer scheme. You can generate your token in the customer area, under API Keys. Production tokens are prefixed with `iml_live_`. Keep it secure — it authenticates all read and write operations for your tenant.

http
Authorization: Bearer iml_live_xxxxxxxxxxxxxxxx

Security tip: Never expose your token in client-side code (frontend) or public repositories. Use server-side environment variables.

Idempotency-Key*

The `Idempotency-Key` header is required for event ingestion — a request without it (or with an empty value) gets a `400`. Use a unique value per request. If the same key is resent (e.g., in a retry after a network timeout), the server recognizes the duplicate and responds `200 OK` with the same result (`duplicate: true`), without creating a second event in the ledger.

http
Idempotency-Key: seed-42-550e8400-e29b-41d4-a716-446655440000
Request-Id: 8d0b5f06-6d1f-4d3c-9b4f-9f5a2d7b3c1a

When to use: Recommended for automatic retries, async workers, and event-driven integrations where at-least-once delivery is guaranteed by the broker.

Immutable Trail (optional)

New

`meta.immutable_trail` is a trail identifier that groups related events within the same business flow — for example, the complete lifecycle of an order, an audit session, or a subscription flow. The field is optional: if not sent, the event is created normally without being associated with any trail.

Why use it?

Without this field, finding all events for a specific order requires filtering by date, type, and name — and may still produce false positives. With `immutable_trail`, a single `?trail=<value>` parameter returns exactly the events for that flow, in the correct order, without ambiguity.

How to send

event.json
{
  "payload": "{\"order_id\":\"ORD-2026-00441\",\"status\":\"paid\"}",
  "meta": {
    "type": "info",
    "event_name": "order.payment_confirmed",
    "immutable_trail": "order-2026-00441"
  }
}

How to query by trail

query.sh
curl "https://api.immutablelog.com/v1/events?trail=order-2026-00441&limit=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

Precedence: When `trail` and `type` are both sent in a listing query, `trail` takes precedence — the `type` parameter is ignored in that request.

The server applies the following validation rules. Replicate them client-side before sending to avoid unnecessary round-trips:

RuleError message (400)
Empty after triminvalid_immutable_trail: empty after trim
More than 256 charactersinvalid_immutable_trail: max 256 chars
Contains the ":" characterinvalid_immutable_trail: cannot contain ':'

Use "-", "_", "." or "/" as separators. The ":" is forbidden because it breaks internal storage key parsing (Sled).

Case-sensitive: The field is case-sensitive: `Order-1` and `order-1` are distinct trails. Define a consistent naming convention within your team before going to production.

In the dashboard: In the dashboard, clicking any trail chip in the event table automatically applies the `?trail=<value>` filter and displays a banner with the total number of events for that trail.

No trail listing endpoint: There is no dedicated endpoint to list distinct trails. For autocomplete, aggregate from recent listings or store the used trails locally.

Client timezone (optional)

To avoid “date confusion” (UTC vs local time), you can send the client local time via headers. The server keeps canonical UTC time for ordering/proof and persists these values only as untrusted metadata for dashboard display.

http
X-Client-Time: 2026-02-05T21:13:22-03:00
X-Client-TZ: America/Sao_Paulo
X-Client-Offset-Minutes: -180

`X-Client-Time` (ISO8601) is the most important. `X-Client-TZ` and `X-Client-Offset-Minutes` are optional and help the UI render correctly. `Request-Id` is recommended for traceability (logs/support).

bash
curl -X POST https://api.immutablelog.com/v1/events \
  -H "X-Client-Time: 2026-02-05T21:00:00-03:00" \
  -H "X-Client-TZ: America/Sao_Paulo" \
  -H "X-Client-Offset-Minutes: -180" \
  -H "Request-Id: req-123" \
  -d '{"payload":"..."}'

Payload Structure

The body of each ingestion request contains two fields: `payload` and `meta`. The `payload` field is treated as an opaque string by the server — its content is not interpreted, only stored and hashed. This ensures the hash is computed over exactly what you sent, without any transformation.

payload must be a string, not an object

Serialize your object with JSON.stringify() before sending. The server treats the payload field as an opaque string to ensure the SHA-256 hash is computed over exactly what you sent.

Wrong: "payload": { "id": 1 }Correct: "payload": "{\"id\":1}"

meta.type — event classification*

The `meta.type` field classifies the severity level or category of the event. Recommended values: `error` (critical errors requiring attention), `warning` (non-critical anomalies), `info` (normal system operations), `success` (successfully completed operations). These values are used for filters and metrics in the dashboard.

meta.event_name — business event name*

The `meta.event_name` field is a human-readable label describing the business event (e.g., `payment.approved`, `user.login_failed`, `invoice.created`). Use consistent, descriptive names — they appear in the dashboard, reports, and make audits easier.

meta.service, meta.env, meta.trace_id — traceability(optional)

Additional fields in `meta` like `service`, `env`, and `trace_id` are optional but highly recommended for traceability in distributed systems. The `trace_id` field lets you correlate an ImmutableLog event with logs from other systems (e.g., APM, Datadog, OpenTelemetry).

json
{
  "payload": "{\"id\":\"d6b6c2e5-0c1a-4b92-9a62-2f2c4c2e9a2a\",\"kind\":\"info\",\"message\":\"http.GET.user-me\",\"context\":{\"user_id\":123,\"email\":\"user@example.com\"},\"timestamp\":\"2026-02-05T12:00:00Z\"}",
  "meta": {
    "type": "info",
    "event_name": "http.GET.user-me",
    "service": "api",
    "env": "prod",
    "trace_id": "2c3f4f1e-7d2d-4d10-9c47-0f8d7a1b2c3d"
  }
}

Limit: Size limit: ~16KB per event. Larger payloads should be stored externally (e.g., S3, database) and the event should contain only the identifier and relevant metadata.

meta fields

Beyond the payload, the meta object carries the fields the SIEM uses to normalize (ECS) and enrich the event. All meta values are strings.

Field in metaWhat it does in the SIEM
event_nameThe event label (e.g. http.GET.auth-me-user, Payment error) — the identifier shown in the listing. Becomes event.action and derives event.category (e.g. a name with loginauthentication).
client_ipBecomes source.ip with maximum precedence and provenance client_asserted. See the section below.
immutable_trailBecomes imtbl.immutable_trail — groups related events for investigation (e.g. one order, one user).
typeEvent severity: error / warning / info / success. Colors the badge in the listing. (event_type is accepted as a synonym.)
service, env, request_idMetadata: indexed and queryable in the dashboard (search/filters).

client_ip — end-user IP

The problem it solves

Between the browser and the core there are proxies/ALB. The IP the core sees on the connection is the previous hop (the client's backend or an AWS proxy), not the user's. Without action, source.ip would be the proxy IP — useless for geo, threat and IP-based detection. (Real example: XFF arrived as 44.192.13.3 (AWS) while the user was 179.110.4.205.)

The solution

The client's backend is the only one that sees the browser's real IP — so it forwards that IP in meta.client_ip when calling POST /v1/events.

How the SIEM handles it

  • Validates it is an IP (v4/v6). A non-IP value is ignored — it does not drop the event.
  • Writes to source.ip with maximum precedence (see order below).
  • Stamps imtbl.source_ip_origin = "client_asserted" — the UI shows the “User IP” badge.
  • Feeds GeoIP, threat intel and IP-based detection rules.
source.ip precedence:client_ipX-Forwarded-For (1º hop)X-Real-IPconnection IP

Provenance and trust

client_ip is asserted by the tenant — the client's backend claims the IP, and it is spoofable by whoever controls that backend. However it is self-contained to the tenant (it never crosses another tenant's boundary). A threat/geo hit on a client_asserted value is a claim, not an edge observation.

How to obtain the IP in your backend

Take the first IP from X-Forwarded-For (closest to the user); as a fallback, use the connection IP.

typescript
function getClientIp(req: Request): string | undefined {
  const xff = req.headers["x-forwarded-for"];
  if (typeof xff === "string" && xff.length) return xff.split(",")[0].trim(); // primeiro hop
  return req.socket.remoteAddress ?? undefined;
}

const meta = { event_name: "user.login", client_ip: getClientIp(req) };

Payload example

json
{
  "payload": "{\"user\":\"bob\",\"action\":\"login\"}",
  "meta": {
    "event_name": "user.login",
    "client_ip": "179.110.4.205",
    "immutable_trail": "user-bob"
  }
}

PII note: client_ip is PII and gets sealed into the immutable block (permanent). Decide consciously between forensic evidence and “right to be forgotten” before sending it.

Event ingestion

Send events via `POST /v1/events`. The server computes the SHA-256 hash of the payload, stores the event, and includes it in the next block of the immutable ledger. The response returns the `tx_id` (unique transaction identifier) and the `payload_hash`, which you can use to verify integrity later.

bash
curl -X POST https://api.immutablelog.com/v1/events \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Idempotency-Key: seed-42-550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": "{\"id\":\"d6b6c2e5-0c1a-4b92-9a62-2f2c4c2e9a2a\",\"kind\":\"info\",\"message\":\"http.GET.user-me\"}",
    "meta": {
      "type": "info",
      "event_name": "http.GET.user-me",
      "immutable_trail": "order-2026-00441",
      "service": "api",
      "env": "prod",
      "trace_id": "2c3f4f1e-7d2d-4d10-9c47-0f8d7a1b2c3d"
    }
  }'

Important: Save the `tx_id` from the response in your system. It is required to query the event individually and to generate the proof of inclusion.

Querying events

The API offers two query modes: listing with filters and pagination, or direct lookup by transaction ID. Use listing for period scans and audits; use the ID lookup when you need to verify a specific event.

Listing with filters and pagination

List events with period filters (`from_ts`, `to_ts` as Unix timestamps), type filter (`type`), and cursor-based pagination (`cursor`, `limit`). The `cursor_next` field in the response is used to fetch the next page:

bash
curl "https://api.immutablelog.com/v1/events?from_ts=1704067200&to_ts=1704153600&limit=100&type=info" \
  -H "Authorization: Bearer YOUR_TOKEN"

Filter by trail (immutable_trail)

Use the `trail` parameter to retrieve all events from a specific flow. Combinable with `from_ts`, `to_ts`, `cursor`, and `limit`. When `trail` is present, the `type` parameter is ignored.

bash
curl "https://api.immutablelog.com/v1/events?trail=order-2026-00441&limit=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

The response includes the `immutable_trail` field on each event when a trail is present.

json
{
  "events": [
    {
      "tx_id": "a1b2c3d4-...",
      "timestamp": 1746631234,
      "event_type": "info",
      "event_name": "order.payment_confirmed",
      "immutable_trail": "order-2026-00441",
      "payload_preview": "{\"order_id\":\"ORD-2026-00441\",...}",
      "block_index": 1234
    }
  ],
  "cursor_next": null,
  "count": 7
}

Lookup by transaction ID

Fetch a specific event by its `tx_id`. The response includes the original payload, the computed hash, and the block metadata where it was included:

bash
curl "https://api.immutablelog.com/v1/events/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer YOUR_TOKEN"
json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": 1704067200,
  "payload": "{\"id\":\"d6b6c2e5\",\"kind\":\"info\",\"message\":\"Event received\"}",
  "payload_parsed": { "id": "d6b6c2e5", "kind": "info", "message": "Event received" },
  "payload_hash": "4f2b8e9a3c5d7f1e2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a9b1c3d5e7",
  "meta": {
    "tenant_id": "...",
    "type": "info",
    "event_name": "Event received"
  }
}

Statistics

The statistics endpoint returns event volume grouped by day, with breakdown by type (`success`, `info`, `warning`, `error`). Useful for operational dashboards, audit reports, and volume anomaly monitoring.

Mode 1: Last N days

Parameter `days`: returns the last N days from the moment of the query. Maximum: 7 days. Use this mode for real-time dashboards.

bash
curl "https://api.immutablelog.com/v1/events/stats?days=7&tz_offset_minutes=-180" \
  -H "Authorization: Bearer YOUR_TOKEN"

Mode 2: Custom period (from_ts/to_ts)

Parameters `from_ts` and `to_ts`: custom period using Unix timestamps (seconds). Maximum range is 7 days between `from_ts` and `to_ts`. Use this mode for historical reports of specific periods.

bash
curl "https://api.immutablelog.com/v1/events/stats?from_ts=1769904000&to_ts=1770422399&tz_offset_minutes=-180" \
  -H "Authorization: Bearer YOUR_TOKEN"

from_ts=1769904000 is 02/01/2026 00:00 UTC, to_ts=1770422399 is 02/07/2026 23:59 UTC (7 days)

json
{
  "stats": [
    {
      "date": "2026-02-01",
      "count": 8186,
      "timestamp": 1769904000,
      "by_type": {
        "success": 4523,
        "info": 2103,
        "error": 1203,
        "warning": 357
      }
    },
    {
      "date": "2026-02-02",
      "count": 7892,
      "timestamp": 1769990400,
      "by_type": { "success": 4201, "info": 2145, "error": 1198, "warning": 348 }
    }
  ],
  "total": 54600,
  "total_by_type": {
    "success": 30003,
    "info": 15087,
    "error": 6657,
    "warning": 2853
  }
}

Range limit: Maximum 7 days between from_ts and to_ts.

Retention: Respects the plan's retention period. Free plan: 7 days. Paid plans: as contracted. Queries outside the retention window return empty data without error.

Timezone: tz_offset_minutes groups events by day in local timezone.

Proof of inclusion

Verifiable

`GET /v1/events/:tx_id/proof` returns the Merkle proof that cryptographically demonstrates a specific event was included in the ledger exactly as submitted, without modification. This proof is self-contained and can be verified by any third party without trusting ImmutableLog.

The proof is cryptographically verifiable and does not depend on trusting the system operator. Ideal for external audits, contractual disputes, and regulatory compliance.

bash
curl "https://api.immutablelog.com/v1/events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/proof" \
  -H "Authorization: Bearer YOUR_TOKEN"
json
{
  "tx_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "tx_hash": "d20a624740ce1b7e2c74659bb291f665c021d202be02d13ce27feb067eeec837",
  "payload_hash": "75f3ae1057c197610e33633c630a4e0000dd997f095592bb90cf797202a92b07",
  "block_index": 48291,
  "block_hash": "9a59c5f8229aab55e9f855173ef94485aab8497eea0588f365c871d6d0561722",
  "prev_hash": "3da2892d37823d9298e1d5011d7dcfaaf2d9d9a6d465e99be33af5be1d87c12b",
  "merkle_root": "ca12ad3f60dde6fcadc46c58f7a877648c9119484bd2e3178a1b65da78111efa",
  "merkle_path": [
    { "sibling_hash": "a8c0cce8bb067e91cf2766c26be4e5d7cfba3d3323dc19d08a834391a1ce5acf", "position": "left" },
    { "sibling_hash": "7765907da7bb75aa6424ddc1d582fb15ee9a79abfb457e9f9cf2bdf1d24848d6", "position": "right" },
    { "sibling_hash": "773bc304a3b0a626a520a8d6eacc36809ac18c0b174f3ff3cdaf0a4e9c64433d", "position": "left" }
  ]
}

How to verify: How to verify: for each step of `merkle_path`, compute SHA-256 of the concatenation of the two hex strings (not of the decoded bytes): if `position` is `left`, use `sibling_hash + current`; if it is `right`, use `current + sibling_hash`. The initial value of `current` is the `tx_hash`. Repeat for every step, in order. If the final result matches the block's `merkle_root`, inclusion is proven. Do not normalize the values (no lowercasing, no stripping the `0x` prefix): use the strings exactly as the API returns them.

API Responses

The API follows standard REST conventions. All response bodies are JSON. Below are the main HTTP status codes you may encounter and what to do in each case.

202

202 — Accepted

json
{
  "ok": true,
  "tx_id": "3f227a2d-ae7b-4fde-95a5-313197cc6b3a",
  "payload_hash": "00cf048489f716479f124654cdb7c3551a05b46a1245eaaac34bd2fe933f2fd3",
  "status": "accepted",
  "duplicate": false,
  "request_id": "f7ba7cfe-bb18-454c-af2f-19404719b421"
}

The event was successfully accepted and will be included in the next ledger block. Save the returned `tx_id` — it is the permanent event identifier required for future queries and inclusion proofs.

200

200 — Idempotent duplicate

json
{
  "ok": true,
  "tx_id": "3f227a2d-ae7b-4fde-95a5-313197cc6b3a",
  "payload_hash": "00cf048489f716479f124654cdb7c3551a05b46a1245eaaac34bd2fe933f2fd3",
  "status": "committed",
  "duplicate": true,
  "request_id": "f7ba7cfe-bb18-454c-af2f-19404719b421"
}

The Idempotency-Key was already used. The server returns the original event's tx_id with duplicate: true — no new event is created. Safe for retries.

400

400 — Bad request

json
{
  "ok": false,
  "reason": "missing_idempotency_key"
}

Missing/empty Idempotency-Key (missing_idempotency_key / empty_idempotency_key) or invalid immutable_trail (invalid_immutable_trail: empty, > 256 characters, or contains ":").

401

401 — Unauthorized

json
{
  "ok": false,
  "reason": "unauthorized"
}

Token missing, invalid, or expired. Check that the `Authorization: Bearer <token>` header is present and correct. If the token has expired or been revoked, generate a new one in the customer area.

403

403 — Forbidden

json
{
  "ok": false,
  "reason": "forbidden"
}

Valid token, but insufficient permission for this operation. Common causes: insufficient scope for the accessed endpoint or inactive/suspended subscription. Check your contracted plan and API key permissions in the customer area.

429

429 — Monthly limit exceeded

json
{
  "ok": false,
  "reason": "monthly_limit_exceeded: 50000/50000 events used this billing cycle"
}

The plan's event limit for the current billing cycle has been reached. The `reason` field indicates the volume used (e.g., `50000/50000`). Upgrade your plan or wait for the next billing cycle to resume ingestion.

413

413 — Payload too large

json
{
  "ok": false,
  "reason": "payload_too_large"
}

The payload field exceeded the server limit (16KB). Reduce the payload or store large data externally, keeping only the identifier and metadata in the event.

503

503 — Service unavailable

json
{
  "ok": false,
  "reason": "mempool_full"
}

The ingestion queue (mempool) is full — a transient condition under peak load. It is safe to retry with exponential backoff reusing the same Idempotency-Key.

This documentation reflects the current API behavior. For production integrations or specific questions, contact the support team.