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

# Log a Single Tamper-Proof Audit Event — POST /v1/events

> Append a single tamper-proof audit log entry to your AuditRails stream. Accepts an action, actor, resource, and arbitrary metadata.

The `/v1/events` endpoint lets you record a single immutable audit event in your AuditRails log stream. Every write is cryptographically sealed on ingestion, so the record cannot be altered or deleted after it is accepted. Use this endpoint when you need to capture one discrete action — such as a user login, a permissions change, or a resource deletion — and want the simplest possible integration path. For high-throughput scenarios where you need to record many events at once, consider the [batch endpoint](/api-reference/post-events-batch) instead.

## Request

**`POST https://api.auditrails.io/v1/events`**

### Headers

<ParamField header="Authorization" type="string" required>
  Your AuditRails API key, prefixed with `Bearer`. Use a live key (`at_live_xxx`) for production traffic and a test key (`at_test_xxx`) for development.

  Example: `Bearer at_live_xxx`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`.
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  An optional client-generated string (e.g. a UUID) that makes the request safe to retry. If you send the same key with an identical request body, AuditRails replays the original 202 response and sets the `Idempotency-Replayed: true` response header — no duplicate event is written.

  Sending the same key with a **different** body returns a `409` conflict error. Keys are retained for 24 hours.
</ParamField>

### Body

<ParamField body="action" type="string" required>
  A string identifying what happened. Maximum 255 characters. Must match an entry in your compliance catalog or be a registered custom action. We strongly recommend the `resource.verb` format (e.g. `user.login`, `document.deleted`, `permission.granted`) for consistency and searchability.
</ParamField>

<ParamField body="actor_id" type="string">
  The identifier of the person or system that performed the action (e.g. a user ID, service account name, or API key ID). Maximum 255 characters. Omit only when the actor is genuinely unknown or not applicable.
</ParamField>

<ParamField body="resource" type="string">
  The object the action was performed on. Maximum 255 characters. Use the `type/id` format (e.g. `document/doc-456`, `session/sess_abc`) so that resource-scoped queries work correctly in the AuditRails dashboard.
</ParamField>

<ParamField body="metadata" type="object">
  A free-form key-value object for any additional context you want to attach — IP addresses, request IDs, before/after diffs, etc. There is no hard limit on the number of keys, but the total request body must not exceed **256 KB**.
</ParamField>

### Example request

```bash theme={null}
curl -X POST https://api.auditrails.io/v1/events \
  -H "Authorization: Bearer at_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "user.login",
    "actor_id": "user_123",
    "resource": "session/sess_abc",
    "metadata": {
      "ip": "203.0.113.1",
      "method": "password"
    }
  }'
```

```json theme={null}
{
  "action": "user.login",
  "actor_id": "user_123",
  "resource": "session/sess_abc",
  "metadata": {
    "ip": "203.0.113.1",
    "method": "password"
  }
}
```

<Note>
  **Idempotency semantics:** When you include an `Idempotency-Key` header and the request succeeds, AuditRails caches the response for 24 hours. Any retry with the same key and the same body receives the cached response (with `Idempotency-Replayed: true`) rather than creating a second event. This makes it safe to retry on network timeouts without risk of duplicate log entries.
</Note>

***

## Response

A `202 Accepted` response means the event has been durably written and sealed.

### 202 Accepted

<ResponseField name="log_id" type="string">
  The unique, immutable identifier for the audit log entry. This is a [ULID](https://github.com/ulid/spec) — lexicographically sortable and time-prefixed — that you can use to retrieve or reference this specific event later.
</ResponseField>

<ResponseField name="request_id" type="string">
  A unique identifier for the API request itself. Include this value in any support tickets or bug reports related to this call.
</ResponseField>

### Example response

```json theme={null}
{
  "log_id": "01HX7YGBFZ3QK8N9VMJT5RPCE4",
  "request_id": "req_01HX7YH5KP2RMWT6AJCD8BXEN7"
}
```

***

## Error responses

| HTTP Status | Error Code                       | Description                                                                                                                          |
| ----------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `401`       | `auth/key_not_found`             | The API key in the `Authorization` header was not recognized. Check that you are using the correct key for the target environment.   |
| `409`       | `event/idempotency_key_conflict` | An `Idempotency-Key` was reused with a different request body. Use a new key or resend the original body.                            |
| `422`       | `validation/missing_action`      | The `action` field is absent or empty. Include a valid action string in the request body.                                            |
| `429`       | `rate/limit_exceeded`            | You have exceeded the request rate limit. Check the `Retry-After` response header for the number of seconds to wait before retrying. |

<Warning>
  A `429` response includes a `Retry-After` header indicating how many seconds to wait. Retrying before that window elapses will continue to return `429` and may extend your back-off period. Use the `Idempotency-Key` header on retries to ensure the event is only written once when the request eventually succeeds.
</Warning>
