{
  "openapi": "3.0.0",
  "paths": {
    "/api/v2/api-keys/current": {
      "get": {
        "description": "Returns the account, permissions and advertisers of the key in the Authorization header. Useful for checking that a key works, and for offering a choice of advertiser.",
        "operationId": "apiKeyCurrent",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The key is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentApiKeyDto"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Describe the API key making this request",
        "tags": [
          "API Keys"
        ]
      }
    },
    "/api/v2/audiences": {
      "get": {
        "description": "Every audience the key can see, newest first: those kept up to date through this API, uploaded lists, Klaviyo imports, lookalikes, web audiences and data-marketplace audiences. `source` says which each is, and narrows the list. Archived audiences are left out.\n\nRequired permission: `audiences:read`.",
        "operationId": "audiencesList",
        "parameters": [
          {
            "name": "advertiserId",
            "required": false,
            "in": "query",
            "description": "Only audiences of this advertiser.",
            "schema": {
              "minimum": 1,
              "example": 42,
              "type": "number"
            }
          },
          {
            "name": "externalId",
            "required": false,
            "in": "query",
            "description": "Only the audience created with this `externalId`.",
            "schema": {
              "pattern": "EXTERNAL_ID_PATTERN",
              "example": "crm-list-8841",
              "type": "string"
            }
          },
          {
            "name": "source",
            "required": false,
            "in": "query",
            "description": "Only audiences from these sources. Repeat the parameter, or separate values with commas. Every source when left out.",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/AudienceSource"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiAudienceListDto"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "List audiences",
        "tags": [
          "Audiences"
        ]
      },
      "post": {
        "description": "Creates an empty audience for one advertiser. Add people with the member endpoints; it can be used on a strategy once its `status` is `ready`.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesCreate",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiAudienceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiAudienceDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation. `message` lists each problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The advertiser was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The advertiser already has an audience with this `externalId` (`code` is `EXTERNAL_ID_IN_USE`, and `errors.audienceId` is that audience), or it has as many audiences as it may (`TOO_MANY_AUDIENCES`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Create an audience",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/upload": {
      "post": {
        "description": "Creates an audience from a CSV file, sent as `multipart/form-data` with `advertiserId`, `name` and `file`. The file needs a header row with an `email` column, a `phone` column, or both; other columns are ignored. A row is used when its email or its phone (a US number) is usable. The rest are counted in `upload.skipped`, and the first of them are listed by line in `upload.skippedRows`. Only a file with no usable row is refused. Emails and phone numbers are hashed as they are read and the file is not kept. Up to 500,000 rows and 100 MB. An uploaded audience does not change afterwards: to update it, upload a new one, or create an `api` audience and keep it current with the member endpoints.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesUpload",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "advertiserId",
                  "name",
                  "file"
                ],
                "properties": {
                  "advertiserId": {
                    "type": "integer",
                    "description": "The advertiser the audience is for.",
                    "example": 42
                  },
                  "name": {
                    "type": "string",
                    "description": "Name of the new audience.",
                    "example": "Cart abandoners (30d)"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The customer list: a CSV with an email column, a phone column, or both."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedAudienceDto"
                }
              }
            }
          },
          "400": {
            "description": "No file, a file that is not a CSV with an `email` or `phone` column, more than 500,000 rows, or no usable row (`errors.rows` describes the first ones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The advertiser was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The advertiser's audiences hold as many identifiers as they may between them (`code` is `ADVERTISER_AUDIENCES_FULL`). The upload is refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Upload a customer list",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/lookalike": {
      "post": {
        "description": "Creates an audience of new people who resemble one of the advertiser's audiences, at the chosen similarity (90, 80, 70, 60, 50 percent). The source must be `ready`, come from this API, an upload or Klaviyo, and hold email addresses. People already in the source are left out. The lookalike is built once, in the background: its `status` is `processing` until it can be used.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesCreateLookalike",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLookalikeAudienceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiAudienceDto"
                }
              }
            }
          },
          "400": {
            "description": "The source cannot be modeled on: it is a lookalike, a web or data-marketplace audience, or holds no email address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The advertiser or the source audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The source audience is not `ready` yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Create a lookalike",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/{audienceId}": {
      "get": {
        "description": "One audience, with its status and counts. Poll this to see when an audience becomes `ready`.\n\nRequired permission: `audiences:read`.",
        "operationId": "audiencesGet",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiAudienceDto"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Get an audience",
        "tags": [
          "Audiences"
        ]
      },
      "patch": {
        "description": "Changes the name shown in Bronco. Nothing else can be edited. Web audiences are built in and keep their names.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesUpdate",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateApiAudienceDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiAudienceDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation. `message` lists each problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The audience is archived (`code` is `AUDIENCE_ARCHIVED`) or a web audience (`AUDIENCE_BUILT_IN`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Rename an audience",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/{audienceId}/archive": {
      "post": {
        "description": "Hides the audience from new strategies and stops it changing. For an `api`, `upload` or `klaviyo` audience, the identifiers stored for it are deleted. Strategies that already use it keep the people it had; it no longer updates. This cannot be undone. Archiving an audience that is already archived succeeds. Web audiences are built in and cannot be archived.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesArchive",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The audience is archived."
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "A web audience (`code` is `AUDIENCE_BUILT_IN`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Archive an audience",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/{audienceId}/retry": {
      "post": {
        "description": "Resumes the build of a lookalike whose `status` is `failed`, from where it stopped. Every other kind of audience is retried automatically.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesRetry",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The build has started again."
          },
          "400": {
            "description": "Not a lookalike.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The lookalike has not failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Retry a failed lookalike",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/{audienceId}/members": {
      "post": {
        "description": "Adds people by email or US phone number. Values are hashed on arrival and only the hashes are stored. People the audience already holds are left as they are, so sending the same request twice is safe. Members that cannot be used are skipped and reported in `problems`; the rest are added. New people are passed on to ad delivery every five minutes; `lastDeliveredAt` on the audience says when that last happened.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesAddMembers",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Up to 10,000 people. Send them wrapped in `members`, as a plain list, or send a single person as one flat object, whichever your tool can produce.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AudienceMembersDto"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AudienceMemberDto"
                    }
                  },
                  {
                    "$ref": "#/components/schemas/AudienceMemberDto"
                  }
                ]
              },
              "examples": {
                "wrapped": {
                  "summary": "Several people",
                  "value": {
                    "members": [
                      {
                        "email": "jane@example.com",
                        "phone": "+1 (310) 555-1234"
                      },
                      {
                        "email": "sam@example.com"
                      }
                    ]
                  }
                },
                "single": {
                  "summary": "One person, flat",
                  "value": {
                    "email": "jane@example.com",
                    "phone": "3105551234"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudienceMembersAddedDto"
                }
              }
            }
          },
          "400": {
            "description": "The body is not one of the accepted shapes, has more members than allowed, or none of its members has a usable email or phone number (`code` is `NO_USABLE_MEMBERS`, and `errors` says what was wrong with each).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The audience is archived (`code` is `AUDIENCE_ARCHIVED`), holds as many identifiers as it may (`AUDIENCE_FULL`), or was not created through this API (`NOT_AN_API_AUDIENCE`); or the advertiser's audiences hold as many identifiers as they may between them (`ADVERTISER_AUDIENCES_FULL`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Add people to an audience",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/audiences/{audienceId}/members/remove": {
      "post": {
        "description": "Removes people by the same email or phone they were added with. People the audience does not hold are ignored, so sending the same request twice is safe. A removal is recorded at once and shows in the counts. Someone already in ad delivery takes longer to leave it: removals are passed on once an hour and can take up to 24 hours more to apply. Until then they are counted in `removalsPending` on the audience.\n\nRequired permission: `audiences:write`.",
        "operationId": "audiencesRemoveMembers",
        "parameters": [
          {
            "name": "audienceId",
            "required": true,
            "in": "path",
            "description": "Id of the audience.",
            "schema": {
              "example": 1287,
              "type": "number"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Up to 10,000 people. Send them wrapped in `members`, as a plain list, or send a single person as one flat object, whichever your tool can produce.",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AudienceMembersDto"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AudienceMemberDto"
                    }
                  },
                  {
                    "$ref": "#/components/schemas/AudienceMemberDto"
                  }
                ]
              },
              "examples": {
                "wrapped": {
                  "summary": "Several people",
                  "value": {
                    "members": [
                      {
                        "email": "jane@example.com",
                        "phone": "+1 (310) 555-1234"
                      },
                      {
                        "email": "sam@example.com"
                      }
                    ]
                  }
                },
                "single": {
                  "summary": "One person, flat",
                  "value": {
                    "email": "jane@example.com",
                    "phone": "3105551234"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudienceMembersRemovedDto"
                }
              }
            }
          },
          "400": {
            "description": "The body is not one of the accepted shapes, has more members than allowed, or none of its members has a usable email or phone number (`code` is `NO_USABLE_MEMBERS`, and `errors` says what was wrong with each).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The audience was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The audience is archived (`code` is `AUDIENCE_ARCHIVED`) or was not created through this API (`NOT_AN_API_AUDIENCE`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Remove people from an audience",
        "tags": [
          "Audiences"
        ]
      }
    },
    "/api/v2/reports/query": {
      "post": {
        "description": "Returns up to 10000 rows of spend, delivery and conversions, grouped by the requested dimensions for a date range. Spend is what the advertiser is billed. Days are calendar days in America/New_York. When `truncated` is true, more rows matched: narrow the date range or use fewer dimensions.\n\nRequired permission: `reports:read`.",
        "operationId": "reportsRunQuery",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportQueryDto"
              },
              "examples": {
                "preset": {
                  "summary": "Last 7 days by campaign",
                  "value": {
                    "dimensions": [
                      "date",
                      "campaign"
                    ],
                    "metrics": [
                      "spend",
                      "impressions",
                      "purchases",
                      "revenue",
                      "roas"
                    ],
                    "dateRangePreset": "last7Days"
                  }
                },
                "dates": {
                  "summary": "A date range, with ids, for one advertiser",
                  "value": {
                    "dimensions": [
                      "campaign",
                      "creative"
                    ],
                    "metrics": [
                      "spend",
                      "impressions",
                      "cpm"
                    ],
                    "startDate": "2026-09-01",
                    "endDate": "2026-09-11",
                    "advertiserIds": [
                      42
                    ],
                    "includeIds": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The report rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportQueryResultDto"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation. `message` lists each problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Run a report",
        "tags": [
          "Reports"
        ]
      }
    },
    "/api/v2/reports/query/export.csv": {
      "post": {
        "description": "Same rows as POST /reports/query (up to 10000) as a CSV attachment. The X-Report-Truncated header is 1 when more rows matched.\n\nRequired permission: `reports:read`.",
        "operationId": "reportsExportQuery",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportQueryDto"
              },
              "examples": {
                "preset": {
                  "summary": "Last 7 days by campaign",
                  "value": {
                    "dimensions": [
                      "date",
                      "campaign"
                    ],
                    "metrics": [
                      "spend",
                      "impressions",
                      "purchases",
                      "revenue",
                      "roas"
                    ],
                    "dateRangePreset": "last7Days"
                  }
                },
                "dates": {
                  "summary": "A date range, with ids, for one advertiser",
                  "value": {
                    "dimensions": [
                      "campaign",
                      "creative"
                    ],
                    "metrics": [
                      "spend",
                      "impressions",
                      "cpm"
                    ],
                    "startDate": "2026-09-01",
                    "endDate": "2026-09-11",
                    "advertiserIds": [
                      42
                    ],
                    "includeIds": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The report as a CSV file. The header row uses the column labels.",
            "headers": {
              "Content-Disposition": {
                "description": "Suggested file name, built from `name` and the dates.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Report-Truncated": {
                "description": "1 when more rows matched than were returned, else 0.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "0",
                    "1"
                  ]
                }
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The request body failed validation. `message` lists each problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid, expired or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required permission, or asked for an advertiser it is not limited to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit for this key was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before trying again.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "api-key": []
          }
        ],
        "summary": "Download a report as CSV",
        "tags": [
          "Reports"
        ]
      }
    }
  },
  "info": {
    "title": "Bronco API",
    "description": "The Bronco API for partners and customers.\n\n## Authentication\n\nSend your API key as a bearer token:\n\n```\nAuthorization: Bearer brk_live_...\n```\n\nA key belongs to one Bronco account and can be limited to specific advertisers.\nIts permissions are chosen when it is created, and each endpoint lists the one\nit needs. Account admins create and revoke keys in Settings → API Keys; a\nrevoked key stops working at once.\n\n## Rate limits\n\nRequests are limited per key: 120 a minute, plus a separate 600 a minute for\nadding and removing audience members. Every response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`\n(seconds) for the allowance the request counted against. Past the limit the\nAPI answers `429` with a `Retry-After` header.\n\n## Dates\n\nReporting days are calendar days in America/New_York.\n\n## Sandbox\n\nTo build an integration before you have live campaigns, ask Bronco for a key\non a demo account. It returns generated sample data instead of real delivery:\nthree advertisers and five campaigns covering the year up to today, with the\nsame numbers on every call. If the key is limited to advertisers, their ids\nstand in for the sample advertisers.\n\nAudiences on a demo account behave like real ones without any matching taking\nplace: an audience becomes `ready` once it holds 1,000 emails or 1,000 phone\nnumbers, and `notEnoughMatches` if it is still smaller ten minutes after its\nfirst people were added. A removal is applied within about an hour.\n\n## Stability\n\nFields and enum values may be added to these endpoints. Existing ones are not\nrenamed or removed.",
    "version": "2.0",
    "contact": {}
  },
  "tags": [],
  "components": {
    "schemas": {
      "ApiScope": {
        "type": "string",
        "enum": [
          "reports:read",
          "audiences:read",
          "audiences:write"
        ]
      },
      "ApiKeyAdvertiserDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Id of the advertiser, as used across the API.",
            "example": 42
          },
          "name": {
            "type": "string",
            "description": "Name of the advertiser in Bronco.",
            "example": "Acme Outdoors"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "CurrentApiKeyDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name given to the key when it was created.",
            "example": "Reporting dashboard"
          },
          "tenantId": {
            "type": "number",
            "description": "The account the key belongs to.",
            "example": 1024
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiScope"
            },
            "description": "What the key may do.",
            "example": [
              "reports:read"
            ]
          },
          "advertiserIds": {
            "nullable": true,
            "description": "Advertisers the key is limited to; null means all.",
            "example": [
              42
            ],
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "advertisers": {
            "description": "The advertisers the key can use right now, by name: the ones it is limited to, or every advertiser in the account when it is not limited.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeyAdvertiserDto"
            }
          }
        },
        "required": [
          "name",
          "tenantId",
          "scopes",
          "advertiserIds",
          "advertisers"
        ]
      },
      "AudienceSource": {
        "type": "string",
        "enum": [
          "api",
          "upload",
          "klaviyo",
          "lookalike",
          "web",
          "thirdParty"
        ],
        "description": "Where the members come from. `api`: kept up to date through this API, the only audiences whose members it can add and remove. `upload`: made from an uploaded customer list. `klaviyo`: synced from Klaviyo every day. `lookalike`: new people who resemble another audience. `web`: built by Bronco from what visitors do on the advertiser's site. `thirdParty`: made from data-marketplace segments. Other values may be added."
      },
      "AudienceStatus": {
        "type": "string",
        "enum": [
          "empty",
          "processing",
          "ready",
          "notEnoughMatches",
          "failed"
        ],
        "description": "`empty`: no members yet. `processing`: members are being matched for the first time. `ready`: can be used on a strategy, and stays ready while members come and go. `notEnoughMatches`: too few members could be matched; it is checked again as the audience grows. `failed`: could not be set up."
      },
      "AudienceIdentifierCountsDto": {
        "type": "object",
        "properties": {
          "email": {
            "type": "number",
            "description": "Email addresses the audience holds.",
            "example": 128402
          },
          "phone": {
            "type": "number",
            "description": "Phone numbers the audience holds.",
            "example": 96411
          }
        },
        "required": [
          "email",
          "phone"
        ]
      },
      "KlaviyoResourceKind": {
        "type": "string",
        "enum": [
          "list",
          "segment"
        ],
        "description": "Whether the audience mirrors a Klaviyo list or a segment."
      },
      "KlaviyoSyncStatus": {
        "type": "string",
        "enum": [
          "syncing",
          "ready",
          "failed",
          "sourceDeleted"
        ],
        "description": "Where the sync stands. `failed`: the last sync failed or was not applied, and `syncError` says why. `sourceDeleted`: the list or segment was deleted in Klaviyo and syncing has ended. Null before the first sync starts."
      },
      "AudienceKlaviyoDto": {
        "type": "object",
        "properties": {
          "kind": {
            "description": "Whether the audience mirrors a Klaviyo list or a segment.",
            "example": "segment",
            "allOf": [
              {
                "$ref": "#/components/schemas/KlaviyoResourceKind"
              }
            ]
          },
          "syncStatus": {
            "nullable": true,
            "description": "Where the sync stands. `failed`: the last sync failed or was not applied, and `syncError` says why. `sourceDeleted`: the list or segment was deleted in Klaviyo and syncing has ended. Null before the first sync starts.",
            "example": "ready",
            "allOf": [
              {
                "$ref": "#/components/schemas/KlaviyoSyncStatus"
              }
            ]
          },
          "profiles": {
            "type": "number",
            "nullable": true,
            "description": "Profiles in the list or segment at the last sync that finished. Some may have neither an email nor a US phone number, so `people` can be lower.",
            "example": 128402
          },
          "syncProgress": {
            "type": "number",
            "nullable": true,
            "description": "Profiles pulled so far by the sync that is running. Null when none is.",
            "example": null
          },
          "syncTotal": {
            "type": "number",
            "nullable": true,
            "description": "Profiles Klaviyo reported for the sync that is running. Null when none is, or when Klaviyo gave no count.",
            "example": null
          },
          "lastSyncedAt": {
            "type": "string",
            "nullable": true,
            "description": "When the last sync finished.",
            "format": "date-time",
            "example": "2026-10-14T00:12:40.000Z"
          },
          "syncError": {
            "type": "string",
            "nullable": true,
            "description": "Why the last sync failed or was not applied. Null when it worked.",
            "example": null
          },
          "sourceDeletedAt": {
            "type": "string",
            "nullable": true,
            "description": "When Bronco found the list or segment deleted in Klaviyo. Null while it exists.",
            "format": "date-time",
            "example": null
          }
        },
        "required": [
          "kind",
          "syncStatus",
          "profiles",
          "syncProgress",
          "syncTotal",
          "lastSyncedAt",
          "syncError",
          "sourceDeletedAt"
        ]
      },
      "AudienceLookalikeDto": {
        "type": "object",
        "properties": {
          "sourceAudienceId": {
            "type": "number",
            "nullable": true,
            "description": "Id of the audience it was modeled on. Null if that audience no longer exists.",
            "example": 1287
          },
          "sourceAudienceName": {
            "type": "string",
            "nullable": true,
            "description": "Name of the audience it was modeled on.",
            "example": "Current customers"
          },
          "similarity": {
            "type": "number",
            "description": "How closely the new people resemble the source, in percent. Higher keeps fewer, closer matches.",
            "example": 90
          }
        },
        "required": [
          "sourceAudienceId",
          "sourceAudienceName",
          "similarity"
        ]
      },
      "ApiAudienceDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Id of the audience. Use it in the member endpoints.",
            "example": 1287
          },
          "advertiserId": {
            "type": "number",
            "description": "The advertiser the audience belongs to.",
            "example": 42
          },
          "name": {
            "type": "string",
            "description": "Name shown in Bronco, on Settings → Audiences and in the strategy editor.",
            "example": "Current customers"
          },
          "advertiserName": {
            "type": "string",
            "description": "Name of the advertiser the audience belongs to.",
            "example": "Acme Outdoors"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Your own id for the list this audience mirrors, if you gave one when creating it.",
            "example": "crm-list-8841"
          },
          "source": {
            "description": "Where the members come from. `api`: kept up to date through this API, the only audiences whose members it can add and remove. `upload`: made from an uploaded customer list. `klaviyo`: synced from Klaviyo every day. `lookalike`: new people who resemble another audience. `web`: built by Bronco from what visitors do on the advertiser's site. `thirdParty`: made from data-marketplace segments. Other values may be added.",
            "example": "api",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceSource"
              }
            ]
          },
          "status": {
            "description": "`empty`: no members yet. `processing`: members are being matched for the first time. `ready`: can be used on a strategy, and stays ready while members come and go. `notEnoughMatches`: too few members could be matched; it is checked again as the audience grows. `failed`: could not be set up.",
            "example": "ready",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceStatus"
              }
            ]
          },
          "people": {
            "type": "number",
            "nullable": true,
            "description": "How many people the audience holds. An `api`, `upload` or `klaviyo` audience holds emails and phone numbers rather than people, so this is the larger of the two counts. A `lookalike` is the number of people it was built with, null until it is built. Null for `web` and `thirdParty` audiences, whose size Bronco does not hold.",
            "example": 1250
          },
          "identifierCounts": {
            "description": "How many identifiers the audience holds, by kind. A person sent with an email and a phone counts once in each. Zero for audiences whose members Bronco does not hold (`lookalike`, `web`, `thirdParty`).",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "lastSyncedAt": {
            "type": "string",
            "nullable": true,
            "description": "When members were last written: the last add or remove request accepted, whether or not it changed anything, the upload, or the last Klaviyo sync. Null until the first time, and for audiences whose members Bronco does not hold.",
            "format": "date-time",
            "example": "2026-10-08T02:14:09.120Z"
          },
          "lastDeliveredAt": {
            "type": "string",
            "nullable": true,
            "description": "When additions were last applied to ad delivery. Null until the first time.",
            "format": "date-time",
            "example": "2026-10-08T02:19:40.000Z"
          },
          "removalsPending": {
            "type": "number",
            "description": "Removed identifiers that ad delivery still holds. A removal is recorded at once, passed on within the hour, and can take up to 24 hours more to apply. Back to 0 when every removal has been applied.",
            "example": 0
          },
          "archived": {
            "type": "boolean",
            "description": "An archived audience no longer accepts changes and its stored members are deleted.",
            "example": false
          },
          "createdAt": {
            "type": "string",
            "description": "When the audience was created.",
            "format": "date-time",
            "example": "2026-09-30T17:02:11.000Z"
          },
          "klaviyo": {
            "nullable": true,
            "description": "The Klaviyo list or segment it is synced from. Null unless `source` is `klaviyo`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceKlaviyoDto"
              }
            ]
          },
          "lookalike": {
            "nullable": true,
            "description": "What it was modeled on. Null unless `source` is `lookalike`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceLookalikeDto"
              }
            ]
          }
        },
        "required": [
          "id",
          "advertiserId",
          "name",
          "advertiserName",
          "externalId",
          "source",
          "status",
          "people",
          "identifierCounts",
          "lastSyncedAt",
          "lastDeliveredAt",
          "removalsPending",
          "archived",
          "createdAt",
          "klaviyo",
          "lookalike"
        ]
      },
      "AudienceMemberDto": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "description": "Email address. Trimmed and lowercased, then stored only as a SHA-256 hash.",
            "example": "jane@example.com"
          },
          "emailSha256": {
            "type": "string",
            "description": "For callers that must not send the address itself: the SHA-256 of the trimmed, lowercased email, as 64 hexadecimal characters.",
            "example": "8c87b489ce35cf2e2f39f80e282cb2e804932a56a213983eeeb428407d43b52d"
          },
          "phone": {
            "type": "string",
            "description": "US phone number in any common format, with or without the country code. Stored only as SHA-256 hashes. There is no hashed form of this field: send the number.",
            "example": "+1 (310) 555-1234"
          }
        }
      },
      "AudienceMembersDto": {
        "type": "object",
        "properties": {
          "members": {
            "description": "The people to add or remove, 1 to 10,000 of them. Each needs an email, an emailSha256 or a phone; other fields are ignored.",
            "minItems": 1,
            "maxItems": 10000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AudienceMemberDto"
            }
          }
        },
        "required": [
          "members"
        ]
      },
      "ApiAudienceListDto": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The audiences, newest first.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiAudienceDto"
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "CreateApiAudienceDto": {
        "type": "object",
        "properties": {
          "advertiserId": {
            "type": "number",
            "minimum": 1,
            "description": "The advertiser to create the audience for. `GET /api-keys/current` lists the ones the key can use.",
            "example": 42
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Name shown in Bronco.",
            "example": "Current customers"
          },
          "externalId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:-]{1,128}$",
            "description": "Your own id for the list, such as a CRM list id. Unique per advertiser: creating a second audience with the same one is refused, which makes a create safe to retry.",
            "example": "crm-list-8841"
          }
        },
        "required": [
          "advertiserId",
          "name"
        ]
      },
      "AudienceUploadSkippedRowDto": {
        "type": "object",
        "properties": {
          "line": {
            "type": "number",
            "example": 14,
            "description": "Line of the row in the file. The header row is line 1."
          },
          "reason": {
            "type": "string",
            "enum": [
              "invalidEmail",
              "invalidPhone",
              "noIdentifier"
            ],
            "example": "invalidPhone",
            "description": "Why nothing in the row could be used: its email is not a valid address, its phone is not a US number, or it has neither. The value itself is never repeated."
          }
        },
        "required": [
          "line",
          "reason"
        ]
      },
      "AudienceUploadSummaryDto": {
        "type": "object",
        "properties": {
          "rows": {
            "type": "number",
            "example": 12840,
            "description": "Data rows read from the file, not counting empty lines."
          },
          "skipped": {
            "type": "number",
            "example": 37,
            "description": "Rows left out because neither their email nor their phone could be used. Every other row was added."
          },
          "skippedRows": {
            "description": "The first skipped rows, in file order.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AudienceUploadSkippedRowDto"
            }
          },
          "skippedRowsTruncated": {
            "type": "boolean",
            "example": true,
            "description": "Whether more rows were skipped than `skippedRows` lists."
          }
        },
        "required": [
          "rows",
          "skipped",
          "skippedRows",
          "skippedRowsTruncated"
        ]
      },
      "UploadedAudienceDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "description": "Id of the audience. Use it in the member endpoints.",
            "example": 1287
          },
          "advertiserId": {
            "type": "number",
            "description": "The advertiser the audience belongs to.",
            "example": 42
          },
          "name": {
            "type": "string",
            "description": "Name shown in Bronco, on Settings → Audiences and in the strategy editor.",
            "example": "Current customers"
          },
          "advertiserName": {
            "type": "string",
            "description": "Name of the advertiser the audience belongs to.",
            "example": "Acme Outdoors"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Your own id for the list this audience mirrors, if you gave one when creating it.",
            "example": "crm-list-8841"
          },
          "source": {
            "description": "Where the members come from. `api`: kept up to date through this API, the only audiences whose members it can add and remove. `upload`: made from an uploaded customer list. `klaviyo`: synced from Klaviyo every day. `lookalike`: new people who resemble another audience. `web`: built by Bronco from what visitors do on the advertiser's site. `thirdParty`: made from data-marketplace segments. Other values may be added.",
            "example": "api",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceSource"
              }
            ]
          },
          "status": {
            "description": "`empty`: no members yet. `processing`: members are being matched for the first time. `ready`: can be used on a strategy, and stays ready while members come and go. `notEnoughMatches`: too few members could be matched; it is checked again as the audience grows. `failed`: could not be set up.",
            "example": "ready",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceStatus"
              }
            ]
          },
          "people": {
            "type": "number",
            "nullable": true,
            "description": "How many people the audience holds. An `api`, `upload` or `klaviyo` audience holds emails and phone numbers rather than people, so this is the larger of the two counts. A `lookalike` is the number of people it was built with, null until it is built. Null for `web` and `thirdParty` audiences, whose size Bronco does not hold.",
            "example": 1250
          },
          "identifierCounts": {
            "description": "How many identifiers the audience holds, by kind. A person sent with an email and a phone counts once in each. Zero for audiences whose members Bronco does not hold (`lookalike`, `web`, `thirdParty`).",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "lastSyncedAt": {
            "type": "string",
            "nullable": true,
            "description": "When members were last written: the last add or remove request accepted, whether or not it changed anything, the upload, or the last Klaviyo sync. Null until the first time, and for audiences whose members Bronco does not hold.",
            "format": "date-time",
            "example": "2026-10-08T02:14:09.120Z"
          },
          "lastDeliveredAt": {
            "type": "string",
            "nullable": true,
            "description": "When additions were last applied to ad delivery. Null until the first time.",
            "format": "date-time",
            "example": "2026-10-08T02:19:40.000Z"
          },
          "removalsPending": {
            "type": "number",
            "description": "Removed identifiers that ad delivery still holds. A removal is recorded at once, passed on within the hour, and can take up to 24 hours more to apply. Back to 0 when every removal has been applied.",
            "example": 0
          },
          "archived": {
            "type": "boolean",
            "description": "An archived audience no longer accepts changes and its stored members are deleted.",
            "example": false
          },
          "createdAt": {
            "type": "string",
            "description": "When the audience was created.",
            "format": "date-time",
            "example": "2026-09-30T17:02:11.000Z"
          },
          "klaviyo": {
            "nullable": true,
            "description": "The Klaviyo list or segment it is synced from. Null unless `source` is `klaviyo`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceKlaviyoDto"
              }
            ]
          },
          "lookalike": {
            "nullable": true,
            "description": "What it was modeled on. Null unless `source` is `lookalike`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceLookalikeDto"
              }
            ]
          },
          "upload": {
            "description": "What became of the file's rows.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceUploadSummaryDto"
              }
            ]
          }
        },
        "required": [
          "id",
          "advertiserId",
          "name",
          "advertiserName",
          "externalId",
          "source",
          "status",
          "people",
          "identifierCounts",
          "lastSyncedAt",
          "lastDeliveredAt",
          "removalsPending",
          "archived",
          "createdAt",
          "klaviyo",
          "lookalike",
          "upload"
        ]
      },
      "CreateLookalikeAudienceDto": {
        "type": "object",
        "properties": {
          "advertiserId": {
            "type": "number",
            "minimum": 1,
            "description": "The advertiser the lookalike is for.",
            "example": 42
          },
          "sourceAudienceId": {
            "type": "number",
            "minimum": 1,
            "example": 1287,
            "description": "The audience to model the lookalike on: a `ready` audience of the same advertiser whose source is `api`, `upload` or `klaviyo`, and which holds email addresses."
          },
          "similarity": {
            "type": "number",
            "enum": [
              90,
              80,
              70,
              60,
              50
            ],
            "example": 90,
            "description": "How closely the lookalike resembles the source, in percent. Higher is more similar and reaches fewer people."
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "example": "VIP Customers Lookalike 90%",
            "description": "Name of the new audience."
          }
        },
        "required": [
          "advertiserId",
          "sourceAudienceId",
          "similarity",
          "name"
        ]
      },
      "UpdateApiAudienceDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "The new name.",
            "example": "Current customers (US)"
          }
        },
        "required": [
          "name"
        ]
      },
      "AudienceMemberField": {
        "type": "string",
        "enum": [
          "email",
          "emailSha256",
          "phone"
        ],
        "description": "The field at fault, or null when the member as a whole is."
      },
      "AudienceMemberProblemReason": {
        "type": "string",
        "enum": [
          "notAnObject",
          "noIdentifier",
          "invalidEmail",
          "invalidHash",
          "invalidPhone"
        ],
        "description": "`notAnObject`: the member is not a JSON object. `noIdentifier`: it has no email, emailSha256 or phone. `invalidEmail`, `invalidPhone`: the value is not a usable email or US phone number. `invalidHash`: emailSha256 is not 64 hexadecimal characters, or is the hash of an empty string."
      },
      "AudienceMemberProblemDto": {
        "type": "object",
        "properties": {
          "index": {
            "type": "number",
            "description": "Position of the member in the request, counting from 0.",
            "example": 2
          },
          "field": {
            "nullable": true,
            "description": "The field at fault, or null when the member as a whole is.",
            "example": "phone",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceMemberField"
              }
            ]
          },
          "reason": {
            "description": "`notAnObject`: the member is not a JSON object. `noIdentifier`: it has no email, emailSha256 or phone. `invalidEmail`, `invalidPhone`: the value is not a usable email or US phone number. `invalidHash`: emailSha256 is not 64 hexadecimal characters, or is the hash of an empty string.",
            "example": "invalidPhone",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceMemberProblemReason"
              }
            ]
          }
        },
        "required": [
          "index",
          "field",
          "reason"
        ]
      },
      "AudienceMembersAddedDto": {
        "type": "object",
        "properties": {
          "received": {
            "type": "number",
            "description": "Members in the request.",
            "example": 3
          },
          "accepted": {
            "type": "number",
            "description": "Members with at least one usable identifier.",
            "example": 2
          },
          "rejected": {
            "type": "number",
            "description": "Members with no usable identifier. They were skipped.",
            "example": 1
          },
          "invalid": {
            "description": "Values that could not be used, by kind. Includes unusable values on members that were accepted because of another field.",
            "example": {
              "email": 0,
              "phone": 1
            },
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "problems": {
            "description": "What was wrong, for the first 100 problems. The values themselves are never repeated.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AudienceMemberProblemDto"
            }
          },
          "problemsTruncated": {
            "type": "boolean",
            "description": "True when there were more problems than `problems` lists.",
            "example": false
          },
          "ignoredFields": {
            "description": "Names of member fields that were sent and are not used, so a misspelt field is easy to spot.",
            "example": [
              "firstName"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "identifierCounts": {
            "description": "What the audience holds after this request.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "added": {
            "description": "Identifiers this request added. Ones the audience already held are not counted.",
            "example": {
              "email": 2,
              "phone": 1
            },
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          }
        },
        "required": [
          "received",
          "accepted",
          "rejected",
          "invalid",
          "problems",
          "problemsTruncated",
          "ignoredFields",
          "identifierCounts",
          "added"
        ]
      },
      "AudienceMembersRemovedDto": {
        "type": "object",
        "properties": {
          "received": {
            "type": "number",
            "description": "Members in the request.",
            "example": 3
          },
          "accepted": {
            "type": "number",
            "description": "Members with at least one usable identifier.",
            "example": 2
          },
          "rejected": {
            "type": "number",
            "description": "Members with no usable identifier. They were skipped.",
            "example": 1
          },
          "invalid": {
            "description": "Values that could not be used, by kind. Includes unusable values on members that were accepted because of another field.",
            "example": {
              "email": 0,
              "phone": 1
            },
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "problems": {
            "description": "What was wrong, for the first 100 problems. The values themselves are never repeated.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AudienceMemberProblemDto"
            }
          },
          "problemsTruncated": {
            "type": "boolean",
            "description": "True when there were more problems than `problems` lists.",
            "example": false
          },
          "ignoredFields": {
            "description": "Names of member fields that were sent and are not used, so a misspelt field is easy to spot.",
            "example": [
              "firstName"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "identifierCounts": {
            "description": "What the audience holds after this request.",
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          },
          "removed": {
            "description": "Identifiers this request removed. Ones the audience did not hold are not counted.",
            "example": {
              "email": 2,
              "phone": 1
            },
            "allOf": [
              {
                "$ref": "#/components/schemas/AudienceIdentifierCountsDto"
              }
            ]
          }
        },
        "required": [
          "received",
          "accepted",
          "rejected",
          "invalid",
          "problems",
          "problemsTruncated",
          "ignoredFields",
          "identifierCounts",
          "removed"
        ]
      },
      "ReportDimension": {
        "type": "string",
        "enum": [
          "date",
          "advertiser",
          "campaign",
          "strategy",
          "creative",
          "publisher"
        ]
      },
      "ReportMetric": {
        "type": "string",
        "enum": [
          "spend",
          "impressions",
          "cpm",
          "pageViews",
          "purchases",
          "leads",
          "revenue",
          "cpa",
          "costPerLead",
          "roas"
        ]
      },
      "ReportDatePreset": {
        "type": "string",
        "enum": [
          "today",
          "yesterday",
          "thisWeek",
          "lastWeek",
          "last7Days",
          "last30Days",
          "thisMonth",
          "allTime"
        ],
        "description": "Preset range. Takes precedence over startDate/endDate."
      },
      "ReportQueryDto": {
        "type": "object",
        "properties": {
          "dimensions": {
            "uniqueItems": true,
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReportDimension"
            },
            "description": "Ordered group-by columns (1-4)."
          },
          "metrics": {
            "uniqueItems": true,
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReportMetric"
            },
            "description": "Ordered metric columns (at least one)."
          },
          "name": {
            "type": "string",
            "maxLength": 400,
            "description": "Report name. Only used to name the CSV download."
          },
          "advertiserIds": {
            "uniqueItems": true,
            "minItems": 1,
            "description": "Only include these advertisers. Leave out for every advertiser the caller can see. An API key limited to certain advertisers cannot ask for others.",
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 1
            }
          },
          "includeIds": {
            "type": "boolean",
            "description": "Add an id column next to each advertiser, campaign, strategy and creative name column (advertiserId, campaignId, strategyId, creativeId). Rows are then grouped by id, so two items that share a name stay separate. An id of 0 means the delivery could not be matched. Off unless set."
          },
          "dateRangePreset": {
            "description": "Preset range. Takes precedence over startDate/endDate.",
            "allOf": [
              {
                "$ref": "#/components/schemas/ReportDatePreset"
              }
            ]
          },
          "startDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-09-01",
            "description": "First day, YYYY-MM-DD. Required without dateRangePreset."
          },
          "endDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-09-11",
            "description": "Last day, inclusive, YYYY-MM-DD. Required without dateRangePreset."
          },
          "ianaTimeZone": {
            "type": "string",
            "example": "America/Los_Angeles",
            "description": "IANA zone used to resolve presets. Defaults to America/New_York."
          }
        },
        "required": [
          "dimensions",
          "metrics"
        ]
      },
      "ReportColumnDto": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Dimension or metric key",
            "example": "campaign"
          },
          "label": {
            "type": "string",
            "description": "Column heading, as used in the CSV download.",
            "example": "Campaign Name"
          },
          "kind": {
            "type": "string",
            "description": "Whether the column groups the rows (a dimension) or is a number measured for each group (a metric).",
            "enum": [
              "dimension",
              "metric"
            ],
            "example": "dimension"
          }
        },
        "required": [
          "key",
          "label",
          "kind"
        ]
      },
      "ReportDateRangeDto": {
        "type": "object",
        "properties": {
          "startDate": {
            "type": "string",
            "description": "First day of the report, as YYYY-MM-DD.",
            "example": "2026-09-05"
          },
          "endDate": {
            "type": "string",
            "description": "Last day of the report, as YYYY-MM-DD. Inclusive.",
            "example": "2026-09-11"
          }
        },
        "required": [
          "startDate",
          "endDate"
        ]
      },
      "ReportQueryResultDto": {
        "type": "object",
        "properties": {
          "columns": {
            "description": "The columns of each row, in the order requested.",
            "example": [
              {
                "key": "date",
                "label": "Date",
                "kind": "dimension"
              },
              {
                "key": "campaign",
                "label": "Campaign Name",
                "kind": "dimension"
              },
              {
                "key": "spend",
                "label": "Spend",
                "kind": "metric"
              },
              {
                "key": "impressions",
                "label": "Impressions",
                "kind": "metric"
              },
              {
                "key": "purchases",
                "label": "Purchases",
                "kind": "metric"
              }
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReportColumnDto"
            }
          },
          "rows": {
            "type": "array",
            "description": "One object per row keyed by column key. Dimension values are strings, metric values are numbers.",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "example": [
              {
                "date": "2026-09-05",
                "campaign": "Fall Launch",
                "spend": 412.5,
                "impressions": 18240,
                "purchases": 9
              },
              {
                "date": "2026-09-06",
                "campaign": "Fall Launch",
                "spend": 398.12,
                "impressions": 17611,
                "purchases": 7
              }
            ]
          },
          "rowCount": {
            "type": "number",
            "description": "Number of rows returned.",
            "example": 2
          },
          "truncated": {
            "type": "boolean",
            "description": "True when more rows matched than rowLimit.",
            "example": false
          },
          "rowLimit": {
            "type": "number",
            "description": "The most rows one call returns.",
            "example": 10000
          },
          "range": {
            "description": "The first and last day the report covers.",
            "allOf": [
              {
                "$ref": "#/components/schemas/ReportDateRangeDto"
              }
            ]
          },
          "generatedAt": {
            "format": "date-time",
            "type": "string",
            "description": "When the report was run.",
            "example": "2026-09-12T14:03:27.000Z"
          }
        },
        "required": [
          "columns",
          "rows",
          "rowCount",
          "truncated",
          "rowLimit",
          "range",
          "generatedAt"
        ]
      },
      "ApiError": {
        "type": "object",
        "description": "The body of every error response.",
        "properties": {
          "statusCode": {
            "type": "number",
            "description": "The HTTP status code, repeated in the body.",
            "example": 401
          },
          "message": {
            "description": "What went wrong. A list when several fields failed validation.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ],
            "example": "Invalid or revoked API key"
          },
          "code": {
            "type": "string",
            "description": "A stable, machine-readable reason. Present on errors a client may want to handle differently; branch on this rather than on `message`.",
            "example": "TENANT_DISABLED"
          },
          "errors": {
            "type": "object",
            "additionalProperties": true,
            "description": "Details that go with `message`, when there are any. The shape depends on the error."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "When the error happened, in UTC."
          },
          "path": {
            "type": "string",
            "description": "The path that was requested.",
            "example": "/api/v2/reports/query"
          }
        },
        "required": [
          "statusCode",
          "message",
          "timestamp",
          "path"
        ]
      }
    },
    "securitySchemes": {
      "api-key": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Bronco API key (starts with `brk_`). Create one in Settings → API Keys."
      }
    }
  },
  "servers": [
    {
      "url": "https://agentapi-prod-eastus-1.bronco.run",
      "description": "Production"
    },
    {
      "url": "https://agentapi-qa.bronco.run",
      "description": "QA"
    }
  ]
}
