> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pixalate.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pixalate.com/_mcp/server.

# Enrich many OpenRTB bid requests in one call

POST https://epg-api.pixalate.com/v1/enrich/batch
Content-Type: application/json

An array of OpenRTB 2.6 bid requests in; an array of passthrough responses out,
same order, one-to-one. An invalid item occupies its slot with the `{code,
message}` error object its single-request 400 would have carried — the one
sanctioned non-OpenRTB element in this route's 2xx responses — while valid items
resolve normally. Exceeding the documented item cap fails the whole batch with
400\. Each item counts as one unit against your rate limit and your usage. No
query parameters; no latency SLA on batch. Per-item diagnostic signaling on this route
is provisional — a batch caller that needs a per-item outcome, confidence, or
reason should use the parameterized lookup batch, whose envelope carries them.

Reference: https://docs.pixalate.com/api-reference/openepg/enrich-batch

## Authentication

- `x-api-key` header (required) — Your Pixalate-provided API key, in the request header on every route. Credentials are never accepted in query parameters. An API key may be obtained by signing up for an account on the [Pixalate developer website](https://developer.pixalate.com).
- `Authorization` header (basic auth, required) — Username/password alternative to the API-key header. Headers only — credentials are never accepted in query parameters.

## Servers

- `https://epg-api.pixalate.com/v1` (Production., default)
- `https://dev-epg-api.pixalate.com/v1` (Development sandbox.)

## Request

### Body (application/json)

This endpoint expects a list of OpenRtbBidRequest.

- `list of OpenRtbBidRequest`

## Response

### 200

Batch processed; inspect each slot for the per-item outcome — an enriched or unchanged bid request, or an error object for an invalid item.

- `list of EnrichBatchPostResponsesContentApplicationJsonSchemaItems`

## Errors

### 400 Bad Request Error

Malformed request — a non-conforming body, a missing or empty required field, a read field of the wrong type, a timestamp violation (unparseable, more than 5 minutes in the future, or older than the 7-day window), a non-null `ext.pixalate` that is not itself an object, or an exceeded batch cap.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### 401 Unauthorized Error

Missing or unknown credential.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### 403 Forbidden Error

Known credential without entitlement to this product.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### 413 Content Too Large Error

The batch body exceeded the maximum number of bytes the route reads. The item cap bounds how many items a batch may carry; this bounds how large those items may be in total, and the two are enforced independently. Split the batch and retry.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### 429 Too Many Requests Error

Rate limit exceeded. `Retry-After` says when the window clears.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### 503 Service Unavailable Error

Serving infrastructure degraded — the API fails closed and never masquerades as a data miss. Retry after the indicated delay.

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

## Types

### OpenRtbBidRequest

A standard OpenRTB 2.6 bid request. The service reads only the input subset below, from their canonical OpenRTB paths, and validates those read fields strictly (a required read field missing or empty, or any read field of the wrong type → 400; a malformed optional read field is treated as absent); all other fields are permitted, ignored, and preserved in the response. OpenRTB's own required fields (`id`, `imp`) are not enforced — a lookup-only body may omit them.

- `app` (OpenRtbBidRequestApp, required)
- `device` (OpenRtbBidRequestDevice, required)
- `ext` (OpenRtbBidRequestExt, optional)

### EnrichBatchPostResponsesContentApplicationJsonSchemaItems

### OpenRtbBidRequestApp

- `bundle` (string, required) — Required. App store bundle/ID. Alone it fully resolves single-channel apps; on an aggregator (multi-channel) app with no channel or show identifier alongside it, the result is a structured CHANNEL_UNRESOLVED miss, never a guess.
- `content` (OpenRtbBidRequestAppContent, optional)

### OpenRtbBidRequestDevice

- `ua` (string, required) — Required. Client User-Agent; the platform/OS signal is derived from it when device.os gives no signal.
- `os` (string, optional) — Explicit OS; overrides the UA-derived value. Unrecognized values yield no OS signal (all-platform weighting) rather than an error — unlike the parameterized lookup route, where an out-of-vocabulary os is rejected.
- `devicetype` (integer, optional) — Optional OpenRTB device type. Accepted and retained for forward compatibility; not consulted by resolution during the beta.

### OpenRtbBidRequestExt

- `pixalate` (OpenRtbBidRequestExtPixalate, optional)

### Error

- `code` (enum, required) — Machine-readable error code. A code names a product condition the HTTP status cannot express; where there is no such distinction the code is the status name, so branch on the status first and on the code only for the values below. RATE_LIMITED is a 429 from a request-rate ceiling and clears within the window. BATCH_TOO_LARGE means the batch exceeded the documented item cap — split and retry. TIMESTAMP_OUT_OF_WINDOW means the instant requested lies outside the 7-day coverage window or more than 5 minutes in the future.
  - Allowed values: `BAD_REQUEST`, `BATCH_TOO_LARGE`, `TIMESTAMP_OUT_OF_WINDOW`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`
- `message` (string, required)

### OpenRtbBidRequestAppContent

- `title` (string, optional) — Show/program title. Fuzzy match, scoped to the declared app's catalog; a resolved show always carries its channel association. When the channel path and the show path disagree, the channel + timestamp result wins and this input is marked CONFLICT in verbose debug (on the lookup route). Capped at 256 characters; longer values are truncated.
- `channel` (OpenRtbBidRequestAppContentChannel, optional)

### OpenRtbBidRequestExtPixalate

- `timestamp` (datetime, optional) — ISO 8601 UTC instant to resolve against. Absent means now. Up to 5 minutes in the future is clamped to now with a `TIMESTAMP_CLAMPED` notice; older than 7 days, or further ahead than the clamp, is a 400 carrying `TIMESTAMP_OUT_OF_WINDOW`. The lookup route reads the same instant from its own flat `timestamp` field.

### OpenRtbBidRequestAppContentChannel

- `id` (string, optional) — Native/platform channel identifier, matched exactly against known identifiers (substring containment as a fallback). Strictly supersedes channel.name — when present, the name is not consulted.
- `name` (string, optional) — Channel display name (fuzzy name match). Consulted only when channel.id is absent. Capped at 256 characters; longer values are truncated.

## Examples

**Request**

```json
[
  {
    "app": {
      "bundle": "com.pluto.tv"
    },
    "device": {
      "ua": "Roku/DVP-13.0.0"
    }
  }
]
```

**Response**

```json
[
  {
    "app": {
      "bundle": "com.pluto.tv",
      "content": {
        "channel": {
          "id": "espn",
          "name": "ESPN"
        },
        "title": "SportsCenter"
      }
    },
    "device": {
      "devicetype": 3,
      "os": "Roku OS",
      "ua": "Roku/DVP-13.0.0"
    },
    "ext": {
      "pixalate": {
        "timestamp": "2026-08-28T18:30:00Z"
      }
    }
  }
]
```

**SDK Code**

```python
import requests

url = "https://epg-api.pixalate.com/v1/enrich/batch"

payload = [
    {
        "app": { "bundle": "com.pluto.tv" },
        "device": { "ua": "Roku/DVP-13.0.0" }
    }
]
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://epg-api.pixalate.com/v1/enrich/batch';
const options = {
  method: 'POST',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '[{"app":{"bundle":"com.pluto.tv"},"device":{"ua":"Roku/DVP-13.0.0"}}]'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://epg-api.pixalate.com/v1/enrich/batch"

	payload := strings.NewReader("[\n  {\n    \"app\": {\n      \"bundle\": \"com.pluto.tv\"\n    },\n    \"device\": {\n      \"ua\": \"Roku/DVP-13.0.0\"\n    }\n  }\n]")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://epg-api.pixalate.com/v1/enrich/batch")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "[\n  {\n    \"app\": {\n      \"bundle\": \"com.pluto.tv\"\n    },\n    \"device\": {\n      \"ua\": \"Roku/DVP-13.0.0\"\n    }\n  }\n]"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://epg-api.pixalate.com/v1/enrich/batch")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("[\n  {\n    \"app\": {\n      \"bundle\": \"com.pluto.tv\"\n    },\n    \"device\": {\n      \"ua\": \"Roku/DVP-13.0.0\"\n    }\n  }\n]")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://epg-api.pixalate.com/v1/enrich/batch', [
  'body' => '[
  {
    "app": {
      "bundle": "com.pluto.tv"
    },
    "device": {
      "ua": "Roku/DVP-13.0.0"
    }
  }
]',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://epg-api.pixalate.com/v1/enrich/batch");
var request = new RestRequest(Method.POST);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "[\n  {\n    \"app\": {\n      \"bundle\": \"com.pluto.tv\"\n    },\n    \"device\": {\n      \"ua\": \"Roku/DVP-13.0.0\"\n    }\n  }\n]", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  [
    "app": ["bundle": "com.pluto.tv"],
    "device": ["ua": "Roku/DVP-13.0.0"]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://epg-api.pixalate.com/v1/enrich/batch")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```