{
  "openapi": "3.0.3",
  "info": {
    "title": "Audience",
    "description": "Audience event tracking and identity resolution service",
    "contact": {
      "name": "Immutable API support",
      "email": "support@immutable.com",
      "url": "https://support.immutable.com"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.immutable.com",
      "description": "Production"
    },
    {
      "url": "https://api.dev.immutable.com",
      "description": "Development"
    }
  ],
  "tags": [
    {
      "name": "ingest",
      "description": "Audience event ingestion endpoints",
      "x-displayName": "Ingest"
    },
    {
      "name": "tracking-consent",
      "description": "Tracking consent management endpoints",
      "x-displayName": "Tracking Consent"
    },
    {
      "name": "data",
      "description": "Data erasure endpoints",
      "x-displayName": "Data Deletion"
    }
  ],
  "paths": {
    "/v1/audience/messages": {
      "post": {
        "tags": [
          "ingest"
        ],
        "summary": "Ingest audience messages",
        "operationId": "IngestMessages",
        "parameters": [
          {
            "$ref": "#/components/parameters/publishableKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request (400). Two distinct cases share this status: a malformed request (missing header, empty/oversized batch) returns the generic error envelope below; every message in the batch failing validation returns the same MessagesResponse shape as a 200, with accepted:0, so callers parse rejections identically regardless of status.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/APIError400"
                    },
                    {
                      "$ref": "#/components/schemas/MessagesResponse"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorised"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/audience/tracking-consent": {
      "get": {
        "tags": [
          "tracking-consent"
        ],
        "summary": "Get tracking consent status",
        "operationId": "GetTrackingConsent",
        "parameters": [
          {
            "$ref": "#/components/parameters/publishableKeyHeader"
          },
          {
            "in": "query",
            "name": "anonymousId",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackingConsentResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "put": {
        "tags": [
          "tracking-consent"
        ],
        "summary": "Update tracking consent status",
        "operationId": "UpdateTrackingConsent",
        "parameters": [
          {
            "$ref": "#/components/parameters/publishableKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTrackingConsentRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/audience/data": {
      "delete": {
        "tags": [
          "data"
        ],
        "summary": "Request erasure of all data for an identity",
        "description": "Accepts an erasure request for the specified identity. All linked identities are resolved via stored alias mappings and an erasure event is queued for async processing. Provide either anonymousId or userId (not both).\n\nRequires the project's secret API key, not the publishable key: this deletes another identity's data on the project's behalf, which a client holding only the (public) publishable key can never safely authorize.",
        "operationId": "DeleteAudienceData",
        "security": [
          {
            "ImmutableApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/secretApiKeyHeader"
          },
          {
            "in": "query",
            "name": "anonymousId",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          },
          {
            "in": "query",
            "name": "userId",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorised"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ImmutableApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-immutable-api-key"
      }
    },
    "parameters": {
      "publishableKeyHeader": {
        "in": "header",
        "name": "x-immutable-publishable-key",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "secretApiKeyHeader": {
        "in": "header",
        "name": "x-immutable-api-key",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad Request (400)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError400"
            }
          }
        }
      },
      "Unauthorised": {
        "description": "Unauthorised (401)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError401"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too Many Requests (429)",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the rate limit resets",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError429"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal Server Error (500)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/APIError500"
            }
          }
        }
      }
    },
    "schemas": {
      "EventContext": {
        "type": "object",
        "properties": {
          "library": {
            "type": "string",
            "description": "Required. Max 256 characters."
          },
          "libraryVersion": {
            "type": "string",
            "description": "Required. Max 256 characters."
          },
          "userAgent": {
            "type": "string",
            "description": "Max 512 characters."
          },
          "locale": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "timezone": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "screen": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "screenDensity": {
            "type": "number",
            "format": "float"
          },
          "pageUrl": {
            "type": "string",
            "description": "Max 2048 characters."
          },
          "pagePath": {
            "type": "string",
            "description": "Max 2048 characters."
          },
          "pageReferrer": {
            "type": "string",
            "description": "Max 2048 characters."
          },
          "pageTitle": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "browserLanguage": {
            "type": "string",
            "description": "Max 256 characters."
          }
        }
      },
      "IdentityType": {
        "type": "string",
        "description": "One of passport, steam, epic, google, apple, discord, email, custom."
      },
      "Surface": {
        "type": "string",
        "description": "One of web, pixel, unity, unreal."
      },
      "ConsentStatus": {
        "type": "string",
        "description": "One of not_set, none, anonymous, full."
      },
      "TrackingConsentResponse": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/ConsentStatus"
          }
        }
      },
      "UpdateTrackingConsentRequest": {
        "type": "object",
        "required": [
          "anonymousId",
          "status",
          "source"
        ],
        "properties": {
          "anonymousId": {
            "type": "string",
            "maxLength": 256
          },
          "status": {
            "type": "string",
            "enum": [
              "none",
              "anonymous",
              "full"
            ]
          },
          "source": {
            "type": "string",
            "maxLength": 128
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Required. One of track, page, screen, identify, alias."
          },
          "messageId": {
            "type": "string",
            "description": "Required. Client-generated identifier for this message. Expected to be a UUID, but validated per-message rather than at the request-parsing layer so a malformed value can be reported in `rejections` instead of failing the whole batch."
          },
          "eventTimestamp": {
            "type": "string",
            "description": "Required. Expected to be an RFC3339 timestamp, but validated per-message rather than at the request-parsing layer so a malformed value can be reported in `rejections` instead of failing the whole batch."
          },
          "anonymousId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "deviceId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "sessionId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "context": {
            "$ref": "#/components/schemas/EventContext"
          },
          "eventName": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "properties": {
            "type": "object",
            "additionalProperties": true
          },
          "userId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "identityType": {
            "$ref": "#/components/schemas/IdentityType"
          },
          "traits": {
            "type": "object",
            "additionalProperties": true
          },
          "fromId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "fromType": {
            "$ref": "#/components/schemas/IdentityType"
          },
          "toId": {
            "type": "string",
            "description": "Max 256 characters."
          },
          "toType": {
            "$ref": "#/components/schemas/IdentityType"
          },
          "surface": {
            "$ref": "#/components/schemas/Surface"
          },
          "consentLevel": {
            "$ref": "#/components/schemas/ConsentStatus"
          },
          "test": {
            "type": "boolean",
            "description": "When true, marks the event as test traffic. The flag is passed through to the CDP as-is."
          }
        }
      },
      "MessagesRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "minItems": 1,
            "maxItems": 100
          }
        }
      },
      "MessagesResponse": {
        "type": "object",
        "required": [
          "success",
          "accepted",
          "rejected",
          "rejections"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "accepted": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer",
            "description": "Count of rejected messages. accepted + rejected always equals the number of messages submitted. May exceed rejections.length: messages that share a duplicated messageId are each counted here but collapse into a single rejections entry for that id."
          },
          "rejections": {
            "type": "array",
            "description": "Always present, even when empty. One entry per distinct rejected messageId.",
            "items": {
              "$ref": "#/components/schemas/MessageRejection"
            }
          }
        }
      },
      "MessageRejection": {
        "type": "object",
        "required": [
          "messageId",
          "errors"
        ],
        "properties": {
          "messageId": {
            "type": "string",
            "description": "Echoes the client-supplied messageId verbatim, even if it is not a valid UUID."
          },
          "errors": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/RejectionError"
            }
          }
        }
      },
      "RejectionError": {
        "type": "object",
        "required": [
          "field",
          "code",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "Name of the offending property, or a message-level sentinel when no single field applies."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable, stable for programmatic matching. A small open set (e.g. MISSING_REQUIRED_FIELD, INVALID_ENUM, INVALID_FORMAT, INVALID_VALUE, UNSUPPORTED_TYPE, DUPLICATE_MESSAGE_ID) that may grow over time; not a closed enum so new values are additive, not breaking.",
            "example": "INVALID_FORMAT"
          },
          "message": {
            "type": "string",
            "description": "Human-readable, NOT contractual. Wording may change at any time."
          }
        }
      },
      "BasicAPIError": {
        "type": "object",
        "required": [
          "message",
          "link",
          "trace_id"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Error Message",
            "example": "all fields must be provided"
          },
          "link": {
            "type": "string",
            "description": "Link to Immutable documentation that can help resolve this error",
            "example": "https://docs.immutable.com"
          },
          "trace_id": {
            "type": "string",
            "description": "Trace ID of the initial request",
            "example": "e47634b79a5cd6894ddc9639ec4aad26"
          }
        }
      },
      "APIError400": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BasicAPIError"
          },
          {
            "type": "object",
            "required": [
              "code",
              "details"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Error Code",
                "enum": [
                  "VALIDATION_ERROR"
                ],
                "example": "VALIDATION_ERROR"
              },
              "details": {
                "type": "object",
                "nullable": true,
                "description": "Additional details to help resolve the error"
              }
            }
          }
        ]
      },
      "APIError401": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BasicAPIError"
          },
          {
            "type": "object",
            "required": [
              "code",
              "details"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Error Code",
                "enum": [
                  "UNAUTHORISED_REQUEST"
                ],
                "example": "UNAUTHORISED_REQUEST"
              },
              "details": {
                "type": "object",
                "nullable": true,
                "description": "Additional details to help resolve the error"
              }
            }
          }
        ]
      },
      "APIError429": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BasicAPIError"
          },
          {
            "type": "object",
            "required": [
              "code",
              "details"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Error Code",
                "enum": [
                  "TOO_MANY_REQUESTS_ERROR"
                ],
                "example": "TOO_MANY_REQUESTS_ERROR"
              },
              "details": {
                "type": "object",
                "nullable": true,
                "description": "Additional details to help resolve the error"
              }
            }
          }
        ]
      },
      "APIError500": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BasicAPIError"
          },
          {
            "type": "object",
            "required": [
              "code",
              "details"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Error Code",
                "enum": [
                  "INTERNAL_SERVER_ERROR"
                ],
                "example": "INTERNAL_SERVER_ERROR"
              },
              "details": {
                "type": "object",
                "nullable": true,
                "description": "Additional details to help resolve the error"
              }
            }
          }
        ]
      }
    }
  }
}