> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://contentful.com/developers/docs/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/_mcp/server. # Near real-time audit logs > Near real-time audit logs stream a record of every Contentful Management API request in your organization to your own storage destination, in OCSF format, within minutes of the request. ## Near real-time audit logs overview Near real-time audit logs give you a continuous record of API activity against your Contentful organization. Each event describes a single Contentful Management API (CMA) request: who made it, what it addressed, and how Contentful responded. Events are delivered in near real time to your configured storage destination, where they can be processed, analyzed, or retained using your existing tools. As with all [Enterprise observability](/concepts/enterprise-observability) log sources, Contentful handles the delivery, while you manage storage, access, and downstream processing. Events use the [Open Cybersecurity Schema Framework](https://schema.ocsf.io/) (OCSF) version 1.3.0 and are emitted as [API Activity](https://schema.ocsf.io/1.3.0/classes/api_activity) events, so any SIEM that understands OCSF can ingest the feed without a custom parser. Audit logs cover both writes and reads. Unlike the [daily audit log exports](/tutorials/general/audit-logs), `GET` requests are part of the feed. ## Audit logs as a log source Audit logs are a log source within [log streaming](/concepts/enterprise-observability#log-streaming-overview), alongside the Content Delivery API, GraphQL API, and Content Management API sources. The storage destinations, credentials model, authentication flow, and delivery model are the ones you already use for your other Contentful logs. Audit logs extend that setup rather than replace it. Because they are their own log source, each destination you want them delivered to is configured explicitly. This lets you route audit logs to a different bucket, container, or account than your API logs, and lets you enable or disable audit log delivery independently. ### Delivery guarantees and best-effort delivery Audit log streaming follows the same **at-least-once**, **best-effort** delivery model as every other [log streaming](/concepts/enterprise-observability#log-streaming-overview) source. See [Delivery guarantees](/concepts/enterprise-observability#delivery-guarantees) and [Best-effort delivery](/concepts/enterprise-observability#best-effort-delivery) for how that model works. Specific to audit logs: * Use `metadata.correlation_uid` as your deduplication key. It identifies the originating request, so two events carrying the same value describe the same call. * Because delivery is best-effort, the absence of an event is not proof that the corresponding request did not happen. ### Frequency and file output Logs are sent every few minutes. The exact interval is not configurable. Each delivery cycle may produce multiple files. Files are delivered in NDJSON format, compressed as `.jsonl.gz`, with one OCSF event per line. Each file follows the same structured naming convention as other log sources: `log_source=audit_logs/organization_id=/year=YYYY/month=MM/day=DD/compacted-audit-logs-.jsonl.gz` Example: `log_source=audit_logs/organization_id=274qvl9SkVAlToItncE81X/year=2026/month=05/day=28/compacted-audit-logs-b6afdaef-8bce-4f21-a8c2-72abfd3ba15c-166.jsonl.gz` Files are partitioned by organization and by the UTC date of the event. The `log_source=audit_logs` prefix keeps audit logs separate from your other log sources when they share a destination. There are no configuration options for delivery frequency, interval, batch size, or file format. Events are targeted to reach your destination within **5 minutes** of the originating request, subject to your destination's own ingest latency. ## Setting up audit log streaming Audit logs are delivered to the same destinations as other log sources: **Amazon S3**, **Google Cloud Storage**, and **Azure Blob Storage**. ### Prerequisites and limits Before setting up audit log streaming, ensure you have: * An Enterprise plan subscription. * Organization owner or admin role in Contentful. * A storage destination prepared. The [provider setup steps](/concepts/enterprise-observability#amazon-s3-setup) for Amazon S3, Google Cloud Storage, and Azure Blob Storage are the same for audit logs, and a destination you already use for other log sources can receive audit logs as well. **Configuration limits**: audit logs count as their own log source, so the standard limit applies independently. You can have a maximum of **2 audit log configurations per storage destination** per organization, in addition to your Content Delivery API, GraphQL API, and Content Management API configurations. ### Static IP addresses Contentful uses the same static egress IP addresses for audit logs as for other log sources. If your organization requires IP allowlisting, configure your firewall or network settings to include them. The addresses vary by data residency region. For the full list, read [Audit Logs: Static IP addresses](/tutorials/general/audit-logs#static-ip-addresses-for-audit-logs). ### Configure audit log streaming in Contentful After preparing your cloud storage destination, complete the configuration in the Contentful web app. 1. Go to **Organization settings → Observability**. 2. Click **Create new configuration**. 3. Select **Audit logs** as the **Log source**. 4. Select a **Storage provider**: Amazon S3 Bucket, Google Cloud Storage, or Azure Blob Storage. 5. Enter a **Configuration name**. This is a descriptive label to identify this configuration (e.g. `audit-logs-siem`). 6. Click **Save** to begin streaming audit logs to your configured destination. Audit logs are delivered under their own `log_source=audit_logs` prefix, so pointing an audit log configuration at a destination that already receives other log sources will not overwrite anything. ### What happens next * After successful configuration, your organization is enabled automatically. Events will begin arriving within **5–15 minutes**. * You can monitor delivery status in **Organization settings → Observability** in the Contentful web app, or use the [delivery status API](/references/content-management-api/enterprise-observability/get-logging-configuration-delivery-status) to check programmatically. * Delivery operates on a **best-effort basis**, meaning logs are sent as quickly as possible, typically within minutes. * If delivery fails, Contentful retries automatically and notifies organization owners and admins by email; undelivered logs are permanently discarded after **24 hours** of continued failure — see [Monitoring delivery](#monitoring-delivery) below. ## Audit log schema Use the following schema to understand the structure of each audit log event. Each event represents a single CMA request and response, described using OCSF attributes. When building parsers, expect that additional attributes may be added. Reference by attribute name instead of field position, and dispatch on `class_uid` rather than on file path so that any event class added in the future is additive for your parser. Fields defined by OCSF but not listed below are not populated. > **Info** > > Near real-time audit logs use OCSF **API Activity** (`class_uid` `6003`), while daily audit log exports use OCSF **Web Resources Activity** (`class_uid` `6001`). This is an intentional change for the near real-time feed — see [Event shape](#event-shape) for the full comparison. ### Classification fields | Field | Type | Description | | --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `class_uid` | integer | Always `6003`, the OCSF API Activity class. | | `class_name` | string | Always `API Activity`. | | `category_uid` | integer | Always `6`, Application Activity. | | `activity_id` | integer | Derived from the HTTP method: `GET` and `HEAD` are `2` (Read), `POST` is `1` (Create), `PUT` and `PATCH` are `3` (Update), `DELETE` is `4` (Delete), any other method is `99` (Other), and a missing method is `0` (Unknown). | | `activity_name` | string | The OCSF name corresponding to `activity_id`, for example `Read` or `Update`. | | `type_uid` | integer | `class_uid * 100 + activity_id`, for example `600303` for an update. | | `severity_id` | integer | Always `1`, Informational. See [Severity and detections](#severity-and-detections). | | `status_id` | integer | `1` (Success) when the response status is between 200 and 399, `2` (Failure) for any other status, and `0` (Unknown) when no status was captured. | | `time` | integer | Unix timestamp in milliseconds when the request was made. | | `duration` | integer | End-to-end request duration in milliseconds. Omitted when unknown. | ### Actor fields | Field | Type | Description | | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `actor.user.uid` | string | ID of the Contentful user who made the request. This is the field to identify an actor by. | | `actor.user.type` | string | Always `User` when `actor.user` is present. | | `actor.user.type_id` | integer | Always `1`, the OCSF value for a user. | | `actor.user.email_addr` | string | Email address of the user, as it stood when the event was emitted. | | `actor.user.full_name` | string | Name of the user, as it stood when the event was emitted. | | `actor.app_uid` | string | ID of the Contentful app installation that made the request. Present instead of `actor.user` when the actor is an app. | | `actor.invoked_by` | string | Present when the request was made on behalf of an app, using the `X-Contentful-Delegated-Actor-Id` header. Always an `app:` value. | The whole `actor` object is omitted for requests where no identity was established. See [Identifying actors](#identifying-actors). ### Metadata fields | Field | Type | Description | | -------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `metadata.version` | string | OCSF schema version. Always `1.3.0`. | | `metadata.uid` | string | Unique identifier for this delivered event. Not a request identifier, and not a deduplication key. | | `metadata.correlation_uid` | string | Unique identifier for the request. Use this field as a deduplication key in your pipeline, and to join audit events with other logs for the same request. | | `metadata.tenant_uid` | string | ID of the Contentful organization. | | `metadata.log_name` | string | Identifies the Contentful log that produced the event. Always `cma-api-audit-log`. | | `metadata.product` | object | The API the event describes. Always `{ "name": "Content Management API", "vendor_name": "Contentful" }`. | ### Request and response fields | Field | Type | Description | | ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api.operation` | string | Templatized route pattern with path parameters as placeholders (e.g. `/spaces/:space/environments/:environment/entries/:id`). Useful for grouping requests by endpoint type. Placeholder naming can vary in form across the full Contentful API, so treat the value as an opaque string and match it as a whole rather than parsing it. The `api` object is omitted when no route pattern was resolved. | | `http_request.uid` | string | The same value as `metadata.correlation_uid`. | | `http_request.http_method` | string | HTTP method used by the client. | | `http_request.url.hostname` | string | Host the request was made against, for example `api.contentful.com`. | | `http_request.url.path` | string | Actual request path with resolved values (e.g. `/spaces/p4lm9x2q7rts/environments/master/entries/3KsD6tJE6d`). | | `http_request.url.query_string` | string | Query string, excluding the leading `?`. An empty string when the request had no query. | | `http_request.referrer` | string | The `Referer` header, using the OCSF spelling. An empty string when the request had none. | | `http_request.user_agent` | string | Full User-Agent string from the request. | | `http_request.http_headers` | array | Selected request headers as `name` and `value` pairs: `x-contentful-user-agent`, `origin`, and `authorization`. Headers that were not sent are omitted. | | `http_response.code` | integer | HTTP response status code (e.g. `200`, `404`). | | `http_response.length` | integer | Response body size in bytes. Omitted when unknown. | | `http_response.latency` | integer | The same value as `duration`, in milliseconds. | | `http_response.http_headers` | array | Selected response headers as `name` and `value` pairs. Currently `x-cache`, the edge cache status, for example `PASS`. | > **Info** > > The `authorization` header value is redacted. It keeps a short prefix and suffix around a `[REDACTED]` marker, which is enough to attribute activity to a specific token, and to recognize the same token across events, without exposing the token itself. ### Resource fields | Field | Type | Description | | ------------------ | ------ | ------------------------------------------------------------------------------------------------------ | | `resources[].uid` | string | ID of a Contentful object the request addressed. | | `resources[].type` | string | The kind of object. One of `space`, `environment`, or `entity`. See [Resource types](#resource-types). | `resources` holds up to three entries, in the order space, environment, entity. It is omitted for endpoints with no space context, such as organization-level and user-level endpoints, where `metadata.tenant_uid` still identifies your organization. ### Enrichment fields `enrichments` carries context that the HTTP layer alone cannot express, for the requests that produce it. See [Enrichments](#enrichments) for the payload of each enrichment type. | Field | Type | Description | | --------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `enrichments[].name` | string | The OCSF attribute the enrichment data pertains to. Always `resources`. | | `enrichments[].value` | string | Always `N/A`. OCSF requires the field, but this data supplements `resources` as a whole rather than annotating one entry of it. | | `enrichments[].type` | string | The kind of enrichment: `BulkActionEnrichment` or `AiActionEnrichment`. Use this to decide how to read `data.payload`. | | `enrichments[].provider` | string | `/enrichment/`, identifying the source record. The request ID is the same value as `metadata.correlation_uid`. | | `enrichments[].created_time` | integer | Unix timestamp in milliseconds when the enrichment record was created. Shortly after the event's own `time`, so the two are not identical. | | `enrichments[].data.type_version` | string | Version of the `data.payload` contract for this `type`. | | `enrichments[].data.created_time` | string | The same instant as `created_time`, in milliseconds. | | `enrichments[].data.payload` | array | The enrichment content. Shape depends on `type`. | ### Example events **Entry update by an authenticated user:** ```js { "activity_id": 3, "activity_name": "Update", "class_uid": 6003, "class_name": "API Activity", "category_uid": 6, "type_uid": 600303, "severity_id": 1, "status_id": 1, "time": 1779969600123, "duration": 142, "actor": { "user": { "uid": "", "type": "User", "type_id": 1, "email_addr": "jane.doe@example.com", "full_name": "Jane Doe" } }, "metadata": { "version": "1.3.0", "uid": "9f1e7a4c-5b3d-4d2e-a8f1-2c6e9a1b4d3f", "correlation_uid": "7c4e2a1f-8b3d-4a9e-bc12-3456789abcde", "tenant_uid": "", "log_name": "cma-api-audit-log", "product": { "name": "Content Management API", "vendor_name": "Contentful" } }, "api": { "operation": "/spaces/:space/environments/:environment/entries/:id" }, "resources": [ { "uid": "p4lm9x2q7rts", "type": "space" }, { "uid": "master", "type": "environment" }, { "uid": "3KsD6tJE6d", "type": "entity" } ], "http_request": { "uid": "7c4e2a1f-8b3d-4a9e-bc12-3456789abcde", "http_method": "PUT", "referrer": "https://app.contentful.com/spaces/p4lm9x2q7rts/entries", "user_agent": "contentful.js/10.4.2 (Node.js/v20.10.0)", "url": { "hostname": "api.contentful.com", "path": "/spaces/p4lm9x2q7rts/environments/master/entries/3KsD6tJE6d", "query_string": "" }, "http_headers": [ { "name": "x-contentful-user-agent", "value": "app contentful.js/10.4.2; platform Node.js/v20.10.0;" }, { "name": "origin", "value": "https://app.contentful.com" }, { "name": "authorization", "value": "CFPA[REDACTED]x7Qz" } ] }, "http_response": { "code": 200, "length": 2048, "latency": 142, "http_headers": [ { "name": "x-cache", "value": "PASS" } ] } } ``` **Organization-level request, with no space context and no identified actor:** ```js { "activity_id": 2, "activity_name": "Read", "class_uid": 6003, "class_name": "API Activity", "category_uid": 6, "type_uid": 600302, "severity_id": 1, "status_id": 2, "time": 1779969604000, "duration": 11, "metadata": { "version": "1.3.0", "uid": "b3c1f6d8-2a47-4e19-9f52-7d0ab8e34c61", "correlation_uid": "5a9d2e70-14bc-42f8-8b31-6ce907f2a4d5", "tenant_uid": "", "log_name": "cma-api-audit-log", "product": { "name": "Content Management API", "vendor_name": "Contentful" } }, "api": { "operation": "/organizations/:organization_id/organization_memberships" }, "http_request": { "uid": "5a9d2e70-14bc-42f8-8b31-6ce907f2a4d5", "http_method": "GET", "referrer": "", "user_agent": "curl/8.7.1", "url": { "hostname": "api.contentful.com", "path": "/organizations/274qvl9SkVAlToItncE81X/organization_memberships", "query_string": "limit=100" }, "http_headers": [ { "name": "authorization", "value": "CFPA[REDACTED]4tRm" } ] }, "http_response": { "code": 401, "http_headers": [ { "name": "x-cache", "value": "PASS" } ] } } ``` ## Interpreting audit log events The following behavior is intentional and stable. ### Identifying actors Identify actors by `actor.user.uid` for users and `actor.app_uid` for apps. These are stable identifiers and the right keys for correlation, grouping, and detection rules. The `email_addr` and `full_name` fields carry the user's profile as it stood when the event was emitted. Because they are a point-in-time snapshot, a profile that was just changed can take a short while to come through, so events for the same user may carry differing values before converging on the current profile. The `actor` object is omitted entirely for requests where no identity was established. Audit logs capture every request that reaches the Management API surface, from any client, which includes malformed requests and requests from clients you do not operate. A missing `actor` does not make an event invalid. ### Missing values Empty values are omitted rather than filled in. A serialized `0` means the value really was zero, never that Contentful did not capture it. Optional fields and objects such as `duration`, `http_response.length`, `actor`, `resources`, and `enrichments` are absent from the JSON when they do not apply. Two request fields are exceptions and are delivered as empty strings when the client sent nothing: `http_request.referrer` and `http_request.url.query_string`. ### Severity and detections `severity_id` is always `1`, Informational. Audit logs are an event stream, not a security signal. Build detections on `status_id`, `activity_id`, `api.operation`, and resource patterns, so that transport failures such as HTTP 5xx responses are not conflated with security incidents. Unknown values use OCSF sentinels: `activity_id` `0` for Unknown and `99` for Other both come from the specification. Contentful does not invent enum values. ### Activity IDs and content operations `activity_id` reflects the HTTP method, not the content operation. Publish, unpublish, archive, schedule, and workflow transitions are usually expressed as `PUT` and therefore appear as `activity_id` `3` (Update). Some, notably unpublish, can also be expressed as `DELETE`, so the same logical action can arrive with a different value depending on the client. Match on `api.operation` when a rule needs to detect a specific content operation. ### Resource types `resources[].type` is `space`, `environment`, or `entity`. The Management API exposes many specific object types, including entries, assets, content types, tags, releases, roles, webhooks, and API keys, and all of them appear as `entity` so that the schema stays stable as the API grows. Parse `http_request.url.path` if you need the specific type. > **Info** > > `GET` requests don't include an `entity` resource. Audit logging captures what a request changed, and a read changes nothing, so `resources` for a `GET` request holds only `space` and `environment`. Parse `http_request.url.path` if you need the addressed object's ID for a read. ## Enrichments Some requests carry information that the HTTP layer alone cannot express. A bulk action names every entity it addressed in its request body, and an AI action invocation records which model produced which output. Contentful delivers that context on the same event, in the OCSF `enrichments` array, so there is no second event to correlate. Two enrichment types are populated today: * **Bulk actions**, `type` `BulkActionEnrichment`: the operation performed and the full set of entities it addressed, which `resources` does not list individually. * **AI actions**, `type` `AiActionEnrichment`: the invocation, the AI action and entry involved, and the model that served it. A request can produce more than one entry, including entries of different types. Read `data.payload` according to `type` and `data.type_version`. Both the set of enrichment types and the fields inside `data.payload` may grow over time, so ignore payload fields you do not recognize rather than rejecting the entry. > **Info** > > `enrichments` appears only on requests that produce this data, so most events do not carry it. ### Bulk actions `data.payload` is an array of `action` and `entities` pairs. `action` is the bulk operation, such as `publish`, `unpublish`, `validate`, or `duplicate`. `entities` lists the addressed items as Contentful links. ```js { "name": "resources", "value": "N/A", "type": "BulkActionEnrichment", "provider": "1f8c3a9d-52b4-4e07-9a6d-c31b8e45f207/enrichment/6d41a0b8-3e57-4c92-b8f1-2a9e07c5d431", "created_time": 1779969612480, "data": { "type_version": "1.0.0", "created_time": 1779969612480, "payload": [ { "action": "validate", "entities": [ { "sys": { "type": "Link", "linkType": "Entry", "id": "3KsD6tJE6d" } }, { "sys": { "type": "Link", "linkType": "Entry", "id": "7fCVLCDShH" } } ] } ] } } ``` On a bulk action request, `resources` identifies the space, the environment, and the bulk action itself. The entries the action addressed are in `enrichments[].data.payload[].entities[]`. ### AI actions `data.payload` is an array of invocations, each recording the AI action invoked, the entry and field affected, and the model that served the request. ```js { "name": "resources", "value": "N/A", "type": "AiActionEnrichment", "provider": "8a2e5f14-7b93-4d60-a1c8-5e07b9d3f462/enrichment/c05f9b73-1d24-42a8-9e36-7f18ca40b5de", "created_time": 1779970104250, "data": { "type_version": "1.1", "created_time": 1779970104250, "payload": [ { "invocationId": "5wQ2mNbT8kRfPzL3vYcH1s", "aiActionId": { "sys": { "type": "Link", "linkType": "AiAction", "id": "2pKdR7nMxQwJ4tBvZs9Lqe", "version": 5 } }, "createdBy": { "sys": { "type": "Link", "linkType": "User", "id": "" } }, "entryAffected": { "entityId": "3KsD6tJE6d", "entityType": "Entry", "fieldId": "productDescription", "sourceLocale": "en-US" }, "modelName": "anthropic.claude-4-5-sonnet", "modelProvider": "aws_bedrock", "modelTemperature": 0.1, "outputFormat": "Suggestion" } ] } } ``` ## Migrating from daily audit log exports If your organization already consumes [daily audit log exports](/tutorials/general/audit-logs), the near real-time feed differs in ways that require changes to your ingest pipeline. Re-validate your parsers and detection rules against the tables below. ### Coverage and file format | | Daily export | Near real-time | | ------------------- | ----------------------------- | -------------------------- | | Cadence | Once per day | Continuous | | File contents | A single JSON object per file | NDJSON, one event per line | | Compression | Uncompressed | gzip | | Read requests | Not included | Included | | Authorization token | Not included | Included, redacted | Consuming the near real-time feed means decompressing gzip and parsing line by line, rather than parsing one JSON document per file. ### Event shape | | Daily export | Near real-time | | ----------------------------------------- | -------------------------------- | -------------------------------- | | OCSF class | Web Resources Activity, `6001` | API Activity, `6003` | | `class_uid`, `category_uid`, `type_uid` | Strings | Integers | | `time` | ISO 8601 string | Unix timestamp in milliseconds | | `severity_id` | `0`, Unknown | `1`, Informational | | `status_id` | Not emitted | Derived from the response status | | `metadata.uid` | The request ID | A per-event identifier | | `metadata.correlation_uid` | Not set | The request ID | | `metadata.tenant_uid`, `metadata.product` | Not set | Set | | Empty values | Often `""`, `0`, or empty arrays | Omitted | If you previously joined audit events to other logs on `metadata.uid`, switch that join to `metadata.correlation_uid`. ### Actors | | Daily export | Near real-time | | ------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------- | | `actor.id`, `actor.type` | Present, though deprecated in OCSF | Not emitted. Use `actor.user.uid` and `actor.user.type` | | Human users | `uid`, `type: "User"`, `type_id: 2`, plus `email_addr`, `full_name` | `uid`, `type: "User"`, `type_id: 1`, plus `email_addr`, `full_name` | | Apps | On `actor.user`, with `type: "App"` and `type_id: 3` | On `actor.app_uid` | | An app acting for a user | Not represented | On `actor.invoked_by` | | No identity established | An `actor` object with `type: "Unknown"` | `actor` omitted | Near real-time audit logs use the OCSF value `type_id` `1` for a user, where the daily export emits `2`. ### Resources and enrichments | | Daily export | Near real-time | | -------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------- | | Addressed objects | `web_resources`, using the specific API type such as `BulkAction` | `resources`, using `space`, `environment`, `entity` | | Path entities in `enrichments` | Present as synthetic entries | Moved to `resources` | | Delegated actor in `enrichments` | Present as an entry | Moved to `actor.invoked_by` | | Redacted token in `enrichments` | Present as an entry | Moved to `http_request.http_headers` | | Bulk and AI action data | In `enrichments` | In `enrichments` | Bulk and AI action entries stay in `enrichments`, close to the shape you already parse. Within an entry: | Field | Daily export | Near real-time | | --------------------------- | ------------------- | ---------------------------------------------------------------------------------------------- | | `name` | `web_resources` | `resources` | | `value`, `type`, `provider` | Set | Unchanged | | `created_time` | RFC 3339 string | Unix timestamp in milliseconds. `data.created_time` is now milliseconds too, the same instant. | | `type_version` | At entry level | At `data.type_version` | | The payload array | `data` is the array | `data.payload` is the array | So code reading `enrichment.data[0].entities` reads `enrichment.data.payload[0].entities`, code reading `enrichment.created_time` as a string receives an integer, and code reading `enrichment.data.created_time` as a string also receives an integer. Those three are the only breaking changes within an entry. ## Monitoring delivery Audit log configurations appear in **Organization settings → Observability** alongside your other log source configurations, and are monitored the same way. Delivery statuses, email notifications to organization owners and admins, automatic retries, and the 24-hour window before undelivered logs are discarded all behave as described in [Monitoring delivery](/concepts/enterprise-observability#monitoring-delivery) and [Troubleshooting log delivery failures](/concepts/enterprise-observability#troubleshooting-log-delivery-failures). ### Receiving duplicate events Duplicate events are expected. Audit log streaming uses an at-least-once delivery model by design. Use the `metadata.correlation_uid` field to deduplicate events in your downstream pipeline. ### Logs are missing for a past time period If audit log delivery was not enabled and configured during that period, those logs cannot be recovered. Contentful does not backfill historical audit logs. ## Limitations * `resources` does not list the entities a bulk operation addressed. For endpoints such as bulk actions, `resources` identifies the space, the environment, and the bulk action itself. The entities are in `enrichments`. * `api.operation` can be absent. When no route pattern is resolved for a request, the `api` object is omitted. The rest of the event, including the concrete request path, is unaffected. ## Related reading * [Enterprise observability](/concepts/enterprise-observability) * [Audit logs (daily export)](/tutorials/general/audit-logs) * [OCSF API Activity class, version 1.3.0](https://schema.ocsf.io/1.3.0/classes/api_activity) * [OCSF enrichment object, version 1.3.0](https://schema.ocsf.io/1.3.0/objects/enrichment) > Near real-time audit logs stream a record of every Contentful Management API request in your organization to your own storage destination, in OCSF format, within minutes of the request.