diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java
new file mode 100644
index 00000000000..c6458f170d0
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/ActorType.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+/**
+ * Classifies the kind of entity that performed an auditable action.
+ *
+ * @see AuditRecordBuilder#setActorType(ActorType)
+ */
+public enum ActorType {
+
+ /** A human user, identified by a user account. */
+ USER,
+
+ /** An automated service, daemon, or service account. */
+ SERVICE,
+
+ /** The operating system or a privileged system component. */
+ SYSTEM
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java
new file mode 100644
index 00000000000..8b30f2c9009
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditDeliveryException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+/**
+ * Hard-error thrown by {@link AuditRecordBuilder#emit()} when the audit sink cannot be reached and
+ * all retries are exhausted. This is an unchecked exception so that audit-logging call sites remain
+ * clean, but callers SHOULD catch it and escalate the failure through their incident-management
+ * process.
+ */
+public final class AuditDeliveryException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public AuditDeliveryException(String message) {
+ super(message);
+ }
+
+ public AuditDeliveryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java
new file mode 100644
index 00000000000..e345e56e7f9
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLogger.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The entry point for emitting audit records.
+ *
+ *
Obtain an {@link AuditRecordBuilder} via {@link #auditRecordBuilder()}, populate all required
+ * fields, and call {@link AuditRecordBuilder#emit()} to deliver the record to the audit sink.
+ *
+ *
Unlike {@link io.opentelemetry.api.logs.Logger}, this interface does not expose an
+ * {@code isEnabled} check: audit records are ALWAYS emitted. Dropping audit records is prohibited
+ * by the audit logging specification.
+ */
+@ThreadSafe
+public interface AuditLogger {
+
+ /** Returns an {@link AuditRecordBuilder} for constructing and emitting an audit record. */
+ AuditRecordBuilder auditRecordBuilder();
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java
new file mode 100644
index 00000000000..b2ea7437c76
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditLoggerBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+/**
+ * Builder for creating named {@link AuditLogger} instances.
+ *
+ *
The {@code name} (set on the owning {@link AuditProvider}) is stored as a diagnostic label on
+ * the logger. Unlike {@link io.opentelemetry.api.logs.LoggerBuilder}, the name is NOT mapped to an
+ * OTLP {@code InstrumentationScope}.
+ */
+public interface AuditLoggerBuilder {
+
+ /**
+ * Sets the schema URL to be recorded on emitted {@link AuditRecordBuilder}s for semantic
+ * convention versioning.
+ */
+ AuditLoggerBuilder setSchemaUrl(String schemaUrl);
+
+ /** Sets the version of the component or library that is emitting audit records. */
+ AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion);
+
+ /** Returns the configured {@link AuditLogger}. */
+ AuditLogger build();
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java
new file mode 100644
index 00000000000..125174064da
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The entry point of the Audit Logging API. Provides named {@link AuditLogger} instances.
+ *
+ *
The provider is expected to be accessed from a central place. Use {@link
+ * GlobalAuditProvider#get()} to obtain the globally registered instance, or create an {@link
+ * AuditProvider} directly via the SDK.
+ *
+ *
When no SDK is installed, {@link #noop()} returns an {@link AuditProvider} whose loggers emit
+ * no-op receipts without error.
+ */
+@ThreadSafe
+public interface AuditProvider {
+
+ /**
+ * Gets or creates a named {@link AuditLogger} instance.
+ *
+ * @param name A string identifying the component or subsystem emitting audit records (for example
+ * {@code "com.example.auth"}). MUST NOT be empty.
+ */
+ default AuditLogger get(String name) {
+ return auditLoggerBuilder(name).build();
+ }
+
+ /**
+ * Creates an {@link AuditLoggerBuilder} for a named {@link AuditLogger}.
+ *
+ * @param name A string identifying the component or subsystem emitting audit records. MUST NOT be
+ * empty.
+ */
+ AuditLoggerBuilder auditLoggerBuilder(String name);
+
+ /**
+ * Returns a no-op {@link AuditProvider} whose loggers return no-op {@link AuditReceipt}s
+ * immediately without error.
+ */
+ static AuditProvider noop() {
+ return DefaultAuditProvider.getInstance();
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java
new file mode 100644
index 00000000000..74ec7a4c3b6
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditReceipt.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Proof-of-delivery returned by {@link AuditLogger} once the audit sink has persisted the record.
+ *
+ *
The {@code recordId} echoes the caller's {@link AuditRecordBuilder#setRecordId(String)}.
+ * {@code integrityHash} is the SHA-256 of the record as written by the sink. {@code
+ * sinkTimestampEpochNanos} is the nanosecond UNIX epoch at which the sink persisted the record.
+ */
+@Immutable
+public final class AuditReceipt {
+
+ private final String recordId;
+ private final String integrityHash;
+ private final long sinkTimestampEpochNanos;
+
+ private AuditReceipt(String recordId, String integrityHash, long sinkTimestampEpochNanos) {
+ this.recordId = recordId;
+ this.integrityHash = integrityHash;
+ this.sinkTimestampEpochNanos = sinkTimestampEpochNanos;
+ }
+
+ /** Creates an {@link AuditReceipt} with the given fields. */
+ public static AuditReceipt create(
+ String recordId, String integrityHash, long sinkTimestampEpochNanos) {
+ return new AuditReceipt(recordId, integrityHash, sinkTimestampEpochNanos);
+ }
+
+ /** Returns the {@code RecordId} echoed from the corresponding {@link AuditRecordBuilder}. */
+ public String recordId() {
+ return recordId;
+ }
+
+ /**
+ * Returns the SHA-256 hex digest of the canonical serialization of the {@code AuditRecord} as
+ * persisted by the audit sink.
+ */
+ public String integrityHash() {
+ return integrityHash;
+ }
+
+ /** Returns the nanosecond UNIX epoch at which the audit sink persisted the record. */
+ public long sinkTimestampEpochNanos() {
+ return sinkTimestampEpochNanos;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AuditReceipt)) {
+ return false;
+ }
+ AuditReceipt other = (AuditReceipt) obj;
+ return recordId.equals(other.recordId)
+ && integrityHash.equals(other.integrityHash)
+ && sinkTimestampEpochNanos == other.sinkTimestampEpochNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = recordId.hashCode();
+ result = 31 * result + integrityHash.hashCode();
+ result = 31 * result + Long.hashCode(sinkTimestampEpochNanos);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "AuditReceipt{"
+ + "recordId="
+ + recordId
+ + ", integrityHash="
+ + integrityHash
+ + ", sinkTimestampEpochNanos="
+ + sinkTimestampEpochNanos
+ + "}";
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java
new file mode 100644
index 00000000000..9879ce78111
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/AuditRecordBuilder.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Value;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Used to construct and emit {@link AuditReceipt}-returning audit records from an {@link
+ * AuditLogger}.
+ *
+ *
Obtain an {@link AuditLogger#auditRecordBuilder()}, set all required and desired optional
+ * fields, then call {@link #emit()} which blocks until the audit sink acknowledges the record and
+ * returns an {@link AuditReceipt} as proof-of-delivery.
+ *
+ *
Unlike {@link io.opentelemetry.api.logs.LogRecordBuilder}, {@code emit()} returns a non-void
+ * {@link AuditReceipt} and MUST NOT silently drop the record. An exception is raised if the sink
+ * cannot be reached within the configured timeout.
+ */
+public interface AuditRecordBuilder {
+
+ // ── Required fields ──────────────────────────────────────────────────────
+
+ /**
+ * Sets the caller-generated unique identifier for this record. If not set, the SDK MUST generate
+ * a UUID v4. The value MUST remain stable across retries of the same event.
+ */
+ AuditRecordBuilder setRecordId(String recordId);
+
+ /**
+ * Sets the epoch timestamp (event time) using the given value and unit.
+ *
+ *
This field is required. It represents the time at which the auditable action occurred.
+ */
+ AuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit);
+
+ /** Sets the epoch timestamp (event time) using the given {@link Instant}. */
+ AuditRecordBuilder setTimestamp(Instant instant);
+
+ /**
+ * Sets the semantic name that uniquely identifies the type of audit event, e.g. {@code
+ * "user.login.success"}. MUST be non-empty and stable across releases.
+ */
+ AuditRecordBuilder setEventName(String eventName);
+
+ /**
+ * Sets the identity of the entity that performed the auditable action.
+ *
+ *
MAY be a structured value. If the actor cannot be determined, set to a sentinel such as
+ * {@code "anonymous"}.
+ */
+ AuditRecordBuilder setActor(Value> actor);
+
+ /** Convenience overload of {@link #setActor(Value)} accepting a plain string. */
+ default AuditRecordBuilder setActor(String actor) {
+ return setActor(Value.of(actor));
+ }
+
+ /** Sets the type of the actor. */
+ AuditRecordBuilder setActorType(ActorType actorType);
+
+ /**
+ * Sets the verb that describes what the actor did, e.g. {@code "LOGIN"}, {@code "READ"}, {@code
+ * "DELETE"}. MUST be non-empty and stable across releases.
+ */
+ AuditRecordBuilder setAction(String action);
+
+ /** Sets the result of the auditable action. */
+ AuditRecordBuilder setOutcome(Outcome outcome);
+
+ // ── Optional fields ───────────────────────────────────────────────────────
+
+ /**
+ * Sets the epoch observed-timestamp using the given value and unit. If not set, the SDK MUST set
+ * this to the wall-clock time at the moment {@link #emit()} is called.
+ */
+ AuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit);
+
+ /** Sets the epoch observed-timestamp using the given {@link Instant}. */
+ AuditRecordBuilder setObservedTimestamp(Instant instant);
+
+ /** Sets the schema version of the audit payload, e.g. {@code "1.0.0"}. */
+ AuditRecordBuilder setSchemaVersion(String schemaVersion);
+
+ /**
+ * Sets the object upon which the action was performed, e.g. a file path, database row, or
+ * structured resource descriptor.
+ */
+ AuditRecordBuilder setTargetResource(Value> targetResource);
+
+ /** Sets the source network address of the auditable action, e.g. {@code "203.0.113.42"}. */
+ AuditRecordBuilder setSourceIp(String sourceIp);
+
+ /** Sets free-form additional information about the audit event. */
+ AuditRecordBuilder setBody(Value> body);
+
+ /** Convenience overload of {@link #setBody(Value)} accepting a plain string. */
+ default AuditRecordBuilder setBody(String body) {
+ return setBody(Value.of(body));
+ }
+
+ /**
+ * Sets an attribute on this record. If the record already contains a mapping for the key, the
+ * old value is replaced.
+ *
+ *
Providing a {@code null} value is a no-op and does not remove previously set values.
+ */
+ AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value);
+
+ /**
+ * Sets an asymmetric digital signature over the canonical serialization of this record and the
+ * algorithm used (e.g. {@code "ES256"}). MUST NOT be set together with {@link
+ * #setHmac(byte[], String)}.
+ */
+ AuditRecordBuilder setSignature(byte[] signature, String algorithm);
+
+ /**
+ * Sets the DER-encoded X.509 public-key certificate corresponding to the signing key. Only
+ * meaningful when {@link #setSignature(byte[], String)} is also set.
+ */
+ AuditRecordBuilder setCertificate(byte[] certificate);
+
+ /**
+ * Sets a symmetric HMAC over the canonical serialization of this record and the algorithm used
+ * (e.g. {@code "HMAC-SHA256"}). MUST NOT be set together with {@link
+ * #setSignature(byte[], String)}.
+ */
+ AuditRecordBuilder setHmac(byte[] hmac, String algorithm);
+
+ /**
+ * Sets the monotonically increasing sequence number for hash-chain continuity. When set,
+ * receivers can detect gaps that indicate lost or deleted records.
+ */
+ AuditRecordBuilder setSequenceNo(long sequenceNo);
+
+ /**
+ * Sets the {@code IntegrityHash} of the immediately preceding record in the same audit stream,
+ * enabling hash-chain validation.
+ */
+ AuditRecordBuilder setPrevHash(String prevHash);
+
+ // ── Terminal ──────────────────────────────────────────────────────────────
+
+ /**
+ * Emits the audit record and blocks until the audit sink acknowledges receipt.
+ *
+ *
Returns an {@link AuditReceipt} containing the sink-assigned {@code RecordId}, {@code
+ * IntegrityHash}, and {@code SinkTimestamp}.
+ *
+ *
If the sink cannot be reached within the configured timeout and the retry budget is
+ * exhausted, this method MUST throw a runtime exception and MUST NOT return silently.
+ *
+ * @throws AuditDeliveryException if the audit sink cannot be reached and all retries are
+ * exhausted
+ */
+ AuditReceipt emit();
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java
new file mode 100644
index 00000000000..162d4288323
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditLogger.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Value;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+class DefaultAuditLogger implements AuditLogger {
+
+ private static final AuditLogger INSTANCE = new DefaultAuditLogger();
+ private static final AuditRecordBuilder NOOP_BUILDER = new NoopAuditRecordBuilder();
+ private static final AuditReceipt NOOP_RECEIPT = AuditReceipt.create("", "", 0);
+
+ private DefaultAuditLogger() {}
+
+ static AuditLogger getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public AuditRecordBuilder auditRecordBuilder() {
+ return NOOP_BUILDER;
+ }
+
+ private static final class NoopAuditRecordBuilder implements AuditRecordBuilder {
+
+ private NoopAuditRecordBuilder() {}
+
+ @Override
+ public AuditRecordBuilder setRecordId(String recordId) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setTimestamp(long timestamp, TimeUnit unit) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setTimestamp(Instant instant) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setEventName(String eventName) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setActor(Value> actor) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setActorType(ActorType actorType) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setAction(String action) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setOutcome(Outcome outcome) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setObservedTimestamp(Instant instant) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSchemaVersion(String schemaVersion) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setTargetResource(Value> targetResource) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSourceIp(String sourceIp) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setBody(Value> body) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setAttribute(AttributeKey key, @Nullable T value) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSignature(byte[] signature, String algorithm) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setCertificate(byte[] certificate) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setHmac(byte[] hmac, String algorithm) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setSequenceNo(long sequenceNo) {
+ return this;
+ }
+
+ @Override
+ public AuditRecordBuilder setPrevHash(String prevHash) {
+ return this;
+ }
+
+ @Override
+ public AuditReceipt emit() {
+ return NOOP_RECEIPT;
+ }
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java
new file mode 100644
index 00000000000..e42810c6627
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/DefaultAuditProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+class DefaultAuditProvider implements AuditProvider {
+
+ private static final AuditProvider INSTANCE = new DefaultAuditProvider();
+ private static final AuditLoggerBuilder NOOP_BUILDER = new NoopAuditLoggerBuilder();
+
+ private DefaultAuditProvider() {}
+
+ static AuditProvider getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public AuditLoggerBuilder auditLoggerBuilder(String name) {
+ return NOOP_BUILDER;
+ }
+
+ private static class NoopAuditLoggerBuilder implements AuditLoggerBuilder {
+
+ @Override
+ public AuditLoggerBuilder setSchemaUrl(String schemaUrl) {
+ return this;
+ }
+
+ @Override
+ public AuditLoggerBuilder setInstrumentationVersion(String instrumentationVersion) {
+ return this;
+ }
+
+ @Override
+ public AuditLogger build() {
+ return DefaultAuditLogger.getInstance();
+ }
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java
new file mode 100644
index 00000000000..e5dbdf2fcac
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/GlobalAuditProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Global singleton holder for the process-wide {@link AuditProvider}.
+ *
+ *
In most applications there is only one {@link AuditProvider}. {@link #set(AuditProvider)}
+ * SHOULD be called once, early in the application lifecycle (for example, in the same place where
+ * the OpenTelemetry SDK is initialised).
+ *
+ *
If no provider is registered, {@link #get()} returns the no-op provider from {@link
+ * AuditProvider#noop()}.
+ */
+public final class GlobalAuditProvider {
+
+ private static final AtomicReference globalProvider =
+ new AtomicReference<>(AuditProvider.noop());
+
+ private GlobalAuditProvider() {}
+
+ /** Returns the globally registered {@link AuditProvider}, or the no-op instance if none set. */
+ public static AuditProvider get() {
+ return globalProvider.get();
+ }
+
+ /**
+ * Sets the globally registered {@link AuditProvider}.
+ *
+ * @param auditProvider the provider to register; MUST NOT be null
+ * @throws IllegalArgumentException if {@code auditProvider} is null
+ */
+ public static void set(AuditProvider auditProvider) {
+ if (auditProvider == null) {
+ throw new IllegalArgumentException("auditProvider must not be null");
+ }
+ globalProvider.set(auditProvider);
+ }
+
+ /** Resets the global provider to the no-op implementation. Intended for use in tests only. */
+ public static void resetForTest() {
+ globalProvider.set(AuditProvider.noop());
+ }
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java b/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java
new file mode 100644
index 00000000000..7f9e891d8f7
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/Outcome.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.api.audit;
+
+/**
+ * The result of an auditable action.
+ *
+ * @see AuditRecordBuilder#setOutcome(Outcome)
+ */
+public enum Outcome {
+
+ /** The action completed successfully. */
+ SUCCESS,
+
+ /** The action was attempted but did not complete successfully. */
+ FAILURE,
+
+ /** The outcome could not be determined at the time of emission. */
+ UNKNOWN
+}
diff --git a/api/all/src/main/java/io/opentelemetry/api/audit/package-info.java b/api/all/src/main/java/io/opentelemetry/api/audit/package-info.java
new file mode 100644
index 00000000000..67bbe297c0b
--- /dev/null
+++ b/api/all/src/main/java/io/opentelemetry/api/audit/package-info.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** OpenTelemetry Audit Logging API. */
+@ParametersAreNonnullByDefault
+package io.opentelemetry.api.audit;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/exporters/otlp/audit/build.gradle.kts b/exporters/otlp/audit/build.gradle.kts
new file mode 100644
index 00000000000..d03630a23c2
--- /dev/null
+++ b/exporters/otlp/audit/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("otel.java-conventions")
+ id("otel.publish-conventions")
+ id("otel.animalsniffer-conventions")
+}
+
+description = "OpenTelemetry OTLP Audit Exporter"
+otelJava.moduleName.set("io.opentelemetry.exporter.otlp.audit")
+
+dependencies {
+ api(project(":sdk:audit"))
+ api(project(":sdk:logs"))
+ implementation(project(":exporters:otlp:common"))
+ implementation(project(":exporters:sender:okhttp"))
+
+ testImplementation(project(":exporters:otlp:testing-internal"))
+ testImplementation("com.linecorp.armeria:armeria-junit5")
+}
diff --git a/exporters/otlp/audit/gradle.properties b/exporters/otlp/audit/gradle.properties
new file mode 100644
index 00000000000..4476ae57e31
--- /dev/null
+++ b/exporters/otlp/audit/gradle.properties
@@ -0,0 +1 @@
+otel.release=alpha
diff --git a/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java
new file mode 100644
index 00000000000..0be32c753ec
--- /dev/null
+++ b/exporters/otlp/audit/src/main/java/io/opentelemetry/exporter/otlp/http/audit/AuditLogRecordDataAdapter.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.otlp.http.audit;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.logs.Severity;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.sdk.audit.AuditRecordData;
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.logs.data.LogRecordData;
+import io.opentelemetry.sdk.resources.Resource;
+import javax.annotation.Nullable;
+
+/**
+ * Adapts an {@link AuditRecordData} to the {@link LogRecordData} interface so that the existing
+ * OTLP log marshaling infrastructure can serialize audit records to the {@code
+ * ExportLogsServiceRequest} protobuf message.
+ *
+ *
Mappings per the Audit Logging specification:
+ *
+ *
+ *
{@code SeverityNumber} MUST remain unset ({@code null}).
+ *
{@code InstrumentationScope} MUST be empty.
+ *
Mandatory audit fields are stored as {@code Attributes} with well-known keys.
+ *
+ */
+final class AuditLogRecordDataAdapter implements LogRecordData {
+
+ private static final String ATTR_RECORD_ID = "audit.record_id";
+ private static final String ATTR_ACTOR = "audit.actor";
+ private static final String ATTR_ACTOR_TYPE = "audit.actor_type";
+ private static final String ATTR_ACTION = "audit.action";
+ private static final String ATTR_OUTCOME = "audit.outcome";
+ private static final String ATTR_TARGET_RESOURCE = "audit.target_resource";
+ private static final String ATTR_SOURCE_IP = "audit.source_ip";
+ private static final String ATTR_SCHEMA_VERSION = "audit.schema_version";
+ private static final String ATTR_SEQUENCE_NO = "audit.sequence_no";
+ private static final String ATTR_PREV_HASH = "audit.prev_hash";
+
+ private final AuditRecordData audit;
+ private final Attributes mergedAttributes;
+
+ AuditLogRecordDataAdapter(AuditRecordData audit) {
+ this.audit = audit;
+ this.mergedAttributes = buildAttributes(audit);
+ }
+
+ private static Attributes buildAttributes(AuditRecordData a) {
+ AttributesBuilder b = Attributes.builder();
+ // Mandatory audit fields as attributes
+ b.put(AttributeKey.stringKey(ATTR_RECORD_ID), a.getRecordId());
+ b.put(AttributeKey.stringKey(ATTR_ACTOR), a.getActor().asString());
+ b.put(AttributeKey.stringKey(ATTR_ACTOR_TYPE), a.getActorType().name());
+ b.put(AttributeKey.stringKey(ATTR_ACTION), a.getAction());
+ b.put(AttributeKey.stringKey(ATTR_OUTCOME), a.getOutcome().name());
+ // Optional audit fields
+ if (a.getTargetResource() != null) {
+ b.put(AttributeKey.stringKey(ATTR_TARGET_RESOURCE), a.getTargetResource().asString());
+ }
+ if (a.getSourceIp() != null) {
+ b.put(AttributeKey.stringKey(ATTR_SOURCE_IP), a.getSourceIp());
+ }
+ if (a.getSchemaVersion() != null) {
+ b.put(AttributeKey.stringKey(ATTR_SCHEMA_VERSION), a.getSchemaVersion());
+ }
+ if (a.getSequenceNo() != 0) {
+ b.put(AttributeKey.longKey(ATTR_SEQUENCE_NO), a.getSequenceNo());
+ }
+ if (a.getPrevHash() != null) {
+ b.put(AttributeKey.stringKey(ATTR_PREV_HASH), a.getPrevHash());
+ }
+ // User-supplied attributes (merged last so they can override if needed)
+ a.getAttributes()
+ .forEach(
+ (key, value) -> {
+ @SuppressWarnings("unchecked")
+ AttributeKey