Near real-time audit logs

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 log sources, Contentful handles the delivery, while you manage storage, access, and downstream processing.

Events use the Open Cybersecurity Schema Framework (OCSF) version 1.3.0 and are emitted as 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, GET requests are part of the feed.

Audit logs as a log source

Audit logs are a log source within log streaming, alongside the Content Delivery API and GraphQL 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 source. See Delivery guarantees and 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=<org_id>/year=YYYY/month=MM/day=DD/compacted-audit-logs-<uuid>.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 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 and GraphQL 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.

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

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 for the full comparison.

Classification fields

FieldTypeDescription
class_uidintegerAlways 6003, the OCSF API Activity class.
class_namestringAlways API Activity.
category_uidintegerAlways 6, Application Activity.
activity_idintegerDerived 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_namestringThe OCSF name corresponding to activity_id, for example Read or Update.
type_uidintegerclass_uid * 100 + activity_id, for example 600303 for an update.
severity_idintegerAlways 1, Informational. See Severity and detections.
status_idinteger1 (Success) when the response status is between 200 and 399, 2 (Failure) for any other status, and 0 (Unknown) when no status was captured.
timeintegerUnix timestamp in milliseconds when the request was made.
durationintegerEnd-to-end request duration in milliseconds. Omitted when unknown.

Actor fields

FieldTypeDescription
actor.user.uidstringID of the Contentful user who made the request. This is the field to identify an actor by.
actor.user.typestringAlways User when actor.user is present.
actor.user.type_idintegerAlways 1, the OCSF value for a user.
actor.user.email_addrstringEmail address of the user, as it stood when the event was emitted.
actor.user.full_namestringName of the user, as it stood when the event was emitted.
actor.app_uidstringID of the Contentful app installation that made the request. Present instead of actor.user when the actor is an app.
actor.invoked_bystringPresent when the request was made on behalf of an app, using the X-Contentful-Delegated-Actor-Id header. Always an app:<id> value.

The whole actor object is omitted for requests where no identity was established. See Identifying actors.

Metadata fields

FieldTypeDescription
metadata.versionstringOCSF schema version. Always 1.3.0.
metadata.uidstringUnique identifier for this delivered event. Not a request identifier, and not a deduplication key.
metadata.correlation_uidstringUnique 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_uidstringID of the Contentful organization.
metadata.log_namestringIdentifies the Contentful log that produced the event. Always cma-api-audit-log.
metadata.productobjectThe API the event describes. Always { "name": "Content Management API", "vendor_name": "Contentful" }.

Request and response fields

FieldTypeDescription
api.operationstringTemplatized 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.uidstringThe same value as metadata.correlation_uid.
http_request.http_methodstringHTTP method used by the client.
http_request.url.hostnamestringHost the request was made against, for example api.contentful.com.
http_request.url.pathstringActual request path with resolved values (e.g. /spaces/p4lm9x2q7rts/environments/master/entries/3KsD6tJE6d).
http_request.url.query_stringstringQuery string, excluding the leading ?. An empty string when the request had no query.
http_request.referrerstringThe Referer header, using the OCSF spelling. An empty string when the request had none.
http_request.user_agentstringFull User-Agent string from the request.
http_request.http_headersarraySelected request headers as name and value pairs: x-contentful-user-agent, origin, and authorization. Headers that were not sent are omitted.
http_response.codeintegerHTTP response status code (e.g. 200, 404).
http_response.lengthintegerResponse body size in bytes. Omitted when unknown.
http_response.latencyintegerThe same value as duration, in milliseconds.
http_response.http_headersarraySelected response headers as name and value pairs. Currently x-cache, the edge cache status, for example PASS.
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

FieldTypeDescription
resources[].uidstringID of a Contentful object the request addressed.
resources[].typestringThe kind of object. One of space, environment, or entity. See 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 for the payload of each enrichment type.

FieldTypeDescription
enrichments[].namestringThe OCSF attribute the enrichment data pertains to. Always resources.
enrichments[].valuestringAlways N/A. OCSF requires the field, but this data supplements resources as a whole rather than annotating one entry of it.
enrichments[].typestringThe kind of enrichment: BulkActionEnrichment or AiActionEnrichment. Use this to decide how to read data.payload.
enrichments[].providerstring<request_id>/enrichment/<enrichment_id>, identifying the source record. The request ID is the same value as metadata.correlation_uid.
enrichments[].created_timeintegerUnix 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_versionstringVersion of the data.payload contract for this type.
enrichments[].data.created_timestringThe same instant as created_time, in milliseconds.
enrichments[].data.payloadarrayThe enrichment content. Shape depends on type.

Example events

Entry update by an authenticated user:

1{
2 "activity_id": 3,
3 "activity_name": "Update",
4 "class_uid": 6003,
5 "class_name": "API Activity",
6 "category_uid": 6,
7 "type_uid": 600303,
8 "severity_id": 1,
9 "status_id": 1,
10 "time": 1779969600123,
11 "duration": 142,
12 "actor": {
13 "user": {
14 "uid": "<user id>",
15 "type": "User",
16 "type_id": 1,
17 "email_addr": "jane.doe@example.com",
18 "full_name": "Jane Doe"
19 }
20 },
21 "metadata": {
22 "version": "1.3.0",
23 "uid": "9f1e7a4c-5b3d-4d2e-a8f1-2c6e9a1b4d3f",
24 "correlation_uid": "7c4e2a1f-8b3d-4a9e-bc12-3456789abcde",
25 "tenant_uid": "<org id>",
26 "log_name": "cma-api-audit-log",
27 "product": {
28 "name": "Content Management API",
29 "vendor_name": "Contentful"
30 }
31 },
32 "api": {
33 "operation": "/spaces/:space/environments/:environment/entries/:id"
34 },
35 "resources": [
36 { "uid": "p4lm9x2q7rts", "type": "space" },
37 { "uid": "master", "type": "environment" },
38 { "uid": "3KsD6tJE6d", "type": "entity" }
39 ],
40 "http_request": {
41 "uid": "7c4e2a1f-8b3d-4a9e-bc12-3456789abcde",
42 "http_method": "PUT",
43 "referrer": "https://app.contentful.com/spaces/p4lm9x2q7rts/entries",
44 "user_agent": "contentful.js/10.4.2 (Node.js/v20.10.0)",
45 "url": {
46 "hostname": "api.contentful.com",
47 "path": "/spaces/p4lm9x2q7rts/environments/master/entries/3KsD6tJE6d",
48 "query_string": ""
49 },
50 "http_headers": [
51 { "name": "x-contentful-user-agent", "value": "app contentful.js/10.4.2; platform Node.js/v20.10.0;" },
52 { "name": "origin", "value": "https://app.contentful.com" },
53 { "name": "authorization", "value": "CFPA[REDACTED]x7Qz" }
54 ]
55 },
56 "http_response": {
57 "code": 200,
58 "length": 2048,
59 "latency": 142,
60 "http_headers": [
61 { "name": "x-cache", "value": "PASS" }
62 ]
63 }
64}

Organization-level request, with no space context and no identified actor:

1{
2 "activity_id": 2,
3 "activity_name": "Read",
4 "class_uid": 6003,
5 "class_name": "API Activity",
6 "category_uid": 6,
7 "type_uid": 600302,
8 "severity_id": 1,
9 "status_id": 2,
10 "time": 1779969604000,
11 "duration": 11,
12 "metadata": {
13 "version": "1.3.0",
14 "uid": "b3c1f6d8-2a47-4e19-9f52-7d0ab8e34c61",
15 "correlation_uid": "5a9d2e70-14bc-42f8-8b31-6ce907f2a4d5",
16 "tenant_uid": "<org id>",
17 "log_name": "cma-api-audit-log",
18 "product": {
19 "name": "Content Management API",
20 "vendor_name": "Contentful"
21 }
22 },
23 "api": {
24 "operation": "/organizations/:organization_id/organization_memberships"
25 },
26 "http_request": {
27 "uid": "5a9d2e70-14bc-42f8-8b31-6ce907f2a4d5",
28 "http_method": "GET",
29 "referrer": "",
30 "user_agent": "curl/8.7.1",
31 "url": {
32 "hostname": "api.contentful.com",
33 "path": "/organizations/274qvl9SkVAlToItncE81X/organization_memberships",
34 "query_string": "limit=100"
35 },
36 "http_headers": [
37 { "name": "authorization", "value": "CFPA[REDACTED]4tRm" }
38 ]
39 },
40 "http_response": {
41 "code": 401,
42 "http_headers": [
43 { "name": "x-cache", "value": "PASS" }
44 ]
45 }
46}

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.

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.

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.

1{
2 "name": "resources",
3 "value": "N/A",
4 "type": "BulkActionEnrichment",
5 "provider": "1f8c3a9d-52b4-4e07-9a6d-c31b8e45f207/enrichment/6d41a0b8-3e57-4c92-b8f1-2a9e07c5d431",
6 "created_time": 1779969612480,
7 "data": {
8 "type_version": "1.0.0",
9 "created_time": 1779969612480,
10 "payload": [
11 {
12 "action": "validate",
13 "entities": [
14 { "sys": { "type": "Link", "linkType": "Entry", "id": "3KsD6tJE6d" } },
15 { "sys": { "type": "Link", "linkType": "Entry", "id": "7fCVLCDShH" } }
16 ]
17 }
18 ]
19 }
20}

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.

1{
2 "name": "resources",
3 "value": "N/A",
4 "type": "AiActionEnrichment",
5 "provider": "8a2e5f14-7b93-4d60-a1c8-5e07b9d3f462/enrichment/c05f9b73-1d24-42a8-9e36-7f18ca40b5de",
6 "created_time": 1779970104250,
7 "data": {
8 "type_version": "1.1",
9 "created_time": 1779970104250,
10 "payload": [
11 {
12 "invocationId": "5wQ2mNbT8kRfPzL3vYcH1s",
13 "aiActionId": { "sys": { "type": "Link", "linkType": "AiAction", "id": "2pKdR7nMxQwJ4tBvZs9Lqe", "version": 5 } },
14 "createdBy": { "sys": { "type": "Link", "linkType": "User", "id": "<user id>" } },
15 "entryAffected": {
16 "entityId": "3KsD6tJE6d",
17 "entityType": "Entry",
18 "fieldId": "productDescription",
19 "sourceLocale": "en-US"
20 },
21 "modelName": "anthropic.claude-4-5-sonnet",
22 "modelProvider": "aws_bedrock",
23 "modelTemperature": 0.1,
24 "outputFormat": "Suggestion"
25 }
26 ]
27 }
28}

Migrating from daily audit log exports

If your organization already consumes daily audit log exports, 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 exportNear real-time
CadenceOnce per dayContinuous
File contentsA single JSON object per fileNDJSON, one event per line
CompressionUncompressedgzip
Read requestsNot includedIncluded
Authorization tokenNot includedIncluded, 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 exportNear real-time
OCSF classWeb Resources Activity, 6001API Activity, 6003
class_uid, category_uid, type_uidStringsIntegers
timeISO 8601 stringUnix timestamp in milliseconds
severity_id0, Unknown1, Informational
status_idNot emittedDerived from the response status
metadata.uidThe request IDA per-event identifier
metadata.correlation_uidNot setThe request ID
metadata.tenant_uid, metadata.productNot setSet
Empty valuesOften "", 0, or empty arraysOmitted

If you previously joined audit events to other logs on metadata.uid, switch that join to metadata.correlation_uid.

Actors

Daily exportNear real-time
actor.id, actor.typePresent, though deprecated in OCSFNot emitted. Use actor.user.uid and actor.user.type
Human usersuid, type: "User", type_id: 2, plus email_addr, full_nameuid, type: "User", type_id: 1, plus email_addr, full_name
AppsOn actor.user, with type: "App" and type_id: 3On actor.app_uid
An app acting for a userNot representedOn actor.invoked_by
No identity establishedAn 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 exportNear real-time
Addressed objectsweb_resources, using the specific API type such as BulkActionresources, using space, environment, entity
Path entities in enrichmentsPresent as synthetic entriesMoved to resources
Delegated actor in enrichmentsPresent as an entryMoved to actor.invoked_by
Redacted token in enrichmentsPresent as an entryMoved to http_request.http_headers
Bulk and AI action dataIn enrichmentsIn enrichments

Bulk and AI action entries stay in enrichments, close to the shape you already parse. Within an entry:

FieldDaily exportNear real-time
nameweb_resourcesresources
value, type, providerSetUnchanged
created_timeRFC 3339 stringUnix timestamp in milliseconds. data.created_time is now milliseconds too, the same instant.
type_versionAt entry levelAt data.type_version
The payload arraydata is the arraydata.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 and 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.