# Offers for Contents API

Source: https://docs.aon.pro/api/offers-for-contents

> Derived from the same AON Docs release as the source page.

API Reference · Discover

Get relevant offers for web pages, inline text, images, and video with a live Content Assistant placement.

**Base URL:** `https://api.aon.pro` · Content API 1.2.0

## Authentication

Send `X-AON-Placement-Id` with the public ID of a **live Content Assistant placement** from [Developer Portal](https://developer.aon.pro/) on every request. No API key is required. Anonymous requests and other placement types are not supported. Use `https://api.aon.pro` or `https://plugin.aon.pro`, not the documentation site.

## Submit content and get offers

1.  Send a web URL, inline text, or an image/video HTTPS URL to `POST /v1/content/offers`. Set `source.type` to `web`, `inline`, `image` or `video`. Set `language` explicitly; otherwise `Accept-Language` is used, then `en`.
2.  HTTP `202` with `data.status=pending`: wait `data.poll_after_ms`, then repeat the same POST with unchanged content and language and current viewer context. No task ID, resource ID or `Location` is needed or returned.
3.  HTTP `200` with `data.status=ready`: read `data.matches`. Each match contains an `intent`, its `offers` and a nullable video `segment`. Empty matches or offers are valid results.

## Using offers

Video segments use `start_ms` and `end_ms`; repeated appearances have separate matches. For other content, or video without known segments, `segment` is null. Keep each returned `offer_instance_id` and the entire `action` together and unchanged.

For server-side calls, supply the viewer's `context.user_profile.country` and, when available, `client.user_agent` so matching uses the viewer's context rather than your server's.

## Errors and retries

Errors return `data:null`. For `CONTENT_ANALYSIS_FAILED`, inspect `extra.reason`, `extra.retryable` and `extra.retry_at` before resubmitting. For HTTP `429`, honor `Retry-After` or `extra.retry_after_seconds` and use backoff. These are separate from pending-response polling. Keep `X-Request-Id` for support.

[Download OpenAPI JSON](https://docs.aon.pro/content-openapi.json)

## Operations

**POST** `/v1/content/offers`

Get offers for content

Authentication: Placement ID. Source: OpenAPI security.

### Request

Headers

#### `X-AON-Placement-Id`

Type: string. Requirement: required.

Send the public ID of a live Content Assistant placement in X-AON-Placement-Id exactly once on every request. Use 1–64 ASCII letters, digits, underscores or hyphens. A placement\_id in the body or query is not used. API keys, anonymous requests and other placement types are not supported.

-   example: plc\_YOUR\_PLACEMENT\_ID

#### `Content-Type`

Type: string. Requirement: required.

JSON request body.

-   example: application/json

#### `Accept-Language`

Type: string. Requirement: optional.

Preferred output language when language is omitted; defaults to en.

#### `x-aon-user-pseudo-id`

Type: string. Requirement: optional.

Optional pseudonymous viewer ID; not echoed in the response.

#### `x-aon-consent`

Type: string. Requirement: optional.

Optional viewer consent: personalization=granted|denied;jurisdiction=<code>;age=<value>.

Request Body

JSON body. Maximum 2097152 bytes. Omit: force\_refresh, resource\_id, task\_id, pagination.

#### `source`

Type: object. Requirement: required.

Exactly one of the source shapes below; never mix their fields.

#### `language`

Type: string. Requirement: optional.

Desired result language. Send it explicitly and keep it unchanged when repeating a pending request.

#### `context`

Type: object. Requirement: optional.

Current viewer and session context for offer matching.

#### `context.platform`

Type: object. Requirement: optional.

Application information.

#### `context.platform.name`

Type: string. Requirement: optional.

Application name.

-   minLength: 1

#### `context.platform.version`

Type: string. Requirement: optional.

Application version.

-   minLength: 1

#### `context.platform.channel`

Type: string. Requirement: optional.

Application channel.

-   minLength: 1

#### `context.session_id`

Type: string. Requirement: optional.

Pseudonymous session identifier.

-   minLength: 1
-   maxLength: 256

#### `context.conversation_id`

Type: string | number. Requirement: optional.

Conversation identifier.

#### `context.session`

Type: object. Requirement: optional.

Recent session context.

#### `context.session.previous_request_id`

Type: string. Requirement: optional.

Previous AON request ID.

-   format: uuid

#### `context.session.recent_topics`

Type: string\[\]. Requirement: optional.

Up to ten recent conversation topics. Maximum items: 10.

-   items: string

#### `context.user_profile`

Type: object. Requirement: optional.

Current viewer targeting information.

#### `context.user_profile.country`

Type: string. Requirement: optional.

Uppercase ISO country code, such as US. Supply the viewer's country for server-side calls; otherwise country inference may use your server's region. Resolvable location\_ids take precedence.

-   pattern: ^\[A-Z\]{2}$

#### `context.user_profile.location_ids`

Type: string\[\]. Requirement: optional.

Canonical AON location IDs for the viewer. Minimum items: 1. Maximum items: 10.

-   items: string

#### `context.user_profile.verified_age_over`

Type: integer. Requirement: optional.

Verified minimum age threshold, not an inferred or exact age.

-   minimum: 13
-   maximum: 120

#### `client`

Type: object. Requirement: optional.

Current viewer client information. Omit properties: ip.

#### `client.user_agent`

Type: string. Requirement: optional.

Viewer user agent. If omitted, the request's User-Agent header is used. No client IP field is supported. Maximum UTF-8 bytes: 2048.

-   minLength: 1

#### `constraints`

Type: object. Requirement: optional.

Optional filters for the returned offers.

#### `constraints.category_ids`

Type: string\[\]. Requirement: optional.

Include these category IDs.

-   items: string

#### `constraints.excluded_category_ids`

Type: string\[\]. Requirement: optional.

Exclude these category IDs.

-   items: string

#### `constraints.offer_types`

Type: string\[\]. Requirement: optional.

Allowed offer types.

-   items: string

#### `constraints.listing_source_names`

Type: string\[\]. Requirement: optional.

Allowed listing sources.

-   items: string

#### `force_offer`

Type: boolean. Requirement: optional.

Request a fallback recommendation when normal matching finds none.

#### `response_options`

Type: object. Requirement: optional.

Options for the returned offers.

#### `response_options.thinking_mode`

Type: boolean. Requirement: optional.

Whether offers include match reasons. If omitted, the service default applies.

Source: web

Fields for source.type = web. Omit: contents, media\_type.

#### `source`

Type: object. Requirement: required.

A publicly accessible HTTP or HTTPS web page. Omit properties: contents, media\_type.

#### `source.type`

Type: string. Requirement: required.

Content type: web, inline, image or video.

Allowed values: `web`

#### `source.uri`

Type: string. Requirement: required.

HTTP or HTTPS URL of the page, including any required query parameters. Maximum UTF-8 bytes: 8192.

Source: inline

Fields for source.type = inline. Omit: media\_type.

#### `source`

Type: object. Requirement: required.

Plain text supplied directly. The application URI identifies the text and is not fetched. Omit properties: media\_type.

#### `source.type`

Type: string. Requirement: required.

Content type: web, inline, image or video.

Allowed values: `inline`

#### `source.uri`

Type: string. Requirement: required.

Application URI, such as myapp://articles/123. This address is not fetched. Maximum UTF-8 bytes: 4096.

#### `source.contents`

Type: object\[\]. Requirement: required.

Original text entries in order. At least one nonblank entry is required. Keep the text and entry boundaries unchanged when repeating a pending request. Minimum items: 1. Maximum items: 32. Maximum decoded UTF-8 bytes: 262144.

-   items: object

#### `source.contents[].type`

Type: string. Requirement: required.

Text entry type; use text.

Allowed values: `text`

#### `source.contents[].text`

Type: string. Requirement: required.

Original plain text, not HTML.

Source: image

Fields for source.type = image. Omit: contents, media\_type.

#### `source`

Type: object. Requirement: required.

An image or video at an HTTPS URL. Set source.type to image or video; do not send media\_type. Omit properties: contents, media\_type.

#### `source.type`

Type: string. Requirement: required.

Content type: web, inline, image or video.

Allowed values: `image`

#### `source.uri`

Type: string. Requirement: required.

HTTPS URL of the image or video. Do not include credentials, control characters or surrounding whitespace.

-   maxLength: 2000

Source: video

Fields for source.type = video. Omit: contents, media\_type.

#### `source`

Type: object. Requirement: required.

An image or video at an HTTPS URL. Set source.type to image or video; do not send media\_type. Omit properties: contents, media\_type.

#### `source.type`

Type: string. Requirement: required.

Content type: web, inline, image or video.

Allowed values: `video`

#### `source.uri`

Type: string. Requirement: required.

HTTPS URL of the image or video. Do not include credentials, control characters or surrounding whitespace.

-   maxLength: 2000

Request Examples

#### web

```bash
curl -X POST 'https://api.aon.pro/v1/content/offers' \
  -H "X-AON-Placement-Id: plc_YOUR_PLACEMENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "source": {
    "type": "web",
    "uri": "https://publisher.example/article"
  },
  "language": "en",
  "context": {
    "user_profile": {
      "country": "US"
    }
  },
  "client": {
    "user_agent": "PublisherApp/1.0"
  }
}'
```

#### inline

```bash
curl -X POST 'https://api.aon.pro/v1/content/offers' \
  -H "X-AON-Placement-Id: plc_YOUR_PLACEMENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "source": {
    "type": "inline",
    "uri": "myapp://articles/123",
    "contents": [
      {
        "type": "text",
        "text": "Original article text."
      }
    ]
  },
  "language": "en"
}'
```

#### image

```bash
curl -X POST 'https://api.aon.pro/v1/content/offers' \
  -H "X-AON-Placement-Id: plc_YOUR_PLACEMENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "source": {
    "type": "image",
    "uri": "https://cdn.example.com/a.jpg"
  },
  "language": "en"
}'
```

#### video

```bash
curl -X POST 'https://api.aon.pro/v1/content/offers' \
  -H "X-AON-Placement-Id: plc_YOUR_PLACEMENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "source": {
    "type": "video",
    "uri": "https://cdn.example.com/a.mp4"
  },
  "language": "en"
}'
```

### Response

HTTP 200: ready matches. HTTP 202: pending; wait poll\_after\_ms and repeat POST with the same content and language. Error responses have data:null.

#### `code`

Type: string. Requirement: required.

Response code. SUCCESS does not mean offers are ready; inspect data.status. Machine-readable error code.

Allowed values: `SUCCESS`, `INVALID_JSON`, `UNSUPPORTED_MEDIA_TYPE`, `VALIDATION_ERROR`, `PAYLOAD_TOO_LARGE`, `PLACEMENT_REJECTED`, `RESULT_REVOKED`, `NOT_FOUND`, `API_NOT_FOUND`, `METHOD_NOT_ALLOWED`, `RESULT_EXPIRED`, `RATE_LIMITED`, `SERVICE_UNAVAILABLE`, `INTERNAL_ERROR`, `CONTENT_ANALYSIS_FAILED`, `OFFER_QUERY_TIMEOUT`, `RESULT_TOO_LARGE`

#### `message`

Type: string. Requirement: required.

Human-readable explanation. Use code and data.status for application logic.

#### `data`

Type: object | null. Requirement: required.

Ready matches or a pending status.

-   nullable: true

#### `data.status`

Type: string. Requirement: required.

ready when matching is complete. pending while the content is being prepared.

Allowed values: `ready`, `pending`

#### `data.source_type`

Type: string. Requirement: required when status = ready.

The submitted source.type.

Allowed values: `web`, `inline`, `image`, `video`

#### `data.matches`

Type: object\[\]. Requirement: required when status = ready.

Each entry pairs an intent and its offers with an optional video segment. Empty when no intents are found.

-   items: object

#### `data.matches[].segment`

Type: object | null. Requirement: required.

Video appearance interval, or null for web, inline text, images and videos without known segments. Repeated video appearances have separate matches.

-   nullable: true

#### `data.matches[].segment.start_ms`

Type: integer. Requirement: required.

Start of the appearance, in milliseconds.

-   minimum: 0
-   maximum: 600000

#### `data.matches[].segment.end_ms`

Type: integer. Requirement: required.

End of the appearance, in milliseconds.

-   minimum: 0
-   maximum: 600000

#### `data.matches[].intent`

Type: string. Requirement: required.

Product or commercial intent identified in the content.

-   minLength: 1

#### `data.matches[].offers`

Type: object\[\]. Requirement: required.

Up to twenty complete public Offer objects for this intent. An empty array means no offers are available. Offers are deduplicated within an intent, not across unrelated matches. Maximum items: 20.

-   items: object

#### `data.matches[].offers[].offer_id`

Type: string. Requirement: required.

Canonical Offer ID.

-   format: uuid

#### `data.matches[].offers[].offer_instance_id`

Type: string. Requirement: required.

Opaque ID for this served Offer. Preserve unchanged.

-   format: uuid

#### `data.matches[].offers[].version`

Type: string. Requirement: required.

Offer schema version.

-   minLength: 1

#### `data.matches[].offers[].offer_info`

Type: object. Requirement: required.

Offer details. Additional public fields may include pricing and creative assets.

#### `data.matches[].offers[].offer_info.title`

Type: string. Requirement: required.

Offer title.

-   minLength: 1

#### `data.matches[].offers[].offer_info.description`

Type: string. Requirement: required.

Offer description.

-   minLength: 1

#### `data.matches[].offers[].offer_info.category`

Type: object. Requirement: required.

Offer category.

#### `data.matches[].offers[].offer_info.category.id`

Type: string. Requirement: required.

Canonical category ID.

-   minLength: 1

#### `data.matches[].offers[].entity`

Type: object. Requirement: required.

Merchant or service provider.

#### `data.matches[].offers[].entity.id`

Type: string. Requirement: required.

Entity identifier.

-   minLength: 1

#### `data.matches[].offers[].entity.name`

Type: string. Requirement: required.

Entity display name.

-   minLength: 1

#### `data.matches[].offers[].action`

Type: object. Requirement: required.

Original action for this served Offer. Do not replace tracked links.

#### `data.matches[].offers[].action.type`

Type: string. Requirement: required.

Action type.

-   minLength: 1

#### `data.matches[].offers[].action.payload`

Type: object. Requirement: required.

Action details.

#### `data.matches[].offers[].action.payload.url`

Type: string. Requirement: required.

Tracked HTTPS destination. Preserve this URL unchanged.

-   format: uri
-   pattern: ^https://

#### `data.matches[].offers[].content_language`

Type: string. Requirement: optional.

Language of the Offer content.

#### `data.poll_after_ms`

Type: integer | null. Requirement: required.

Milliseconds to wait before repeating POST with the same content and language and current viewer context. Use each response's value.

-   nullable: true
-   minimum: 1

#### `extra`

Type: object. Requirement: required.

Additional response information.

#### `extra.retry_after_seconds`

Type: integer. Requirement: optional.

Seconds to wait before retrying a rate-limited request.

Allowed values: `60`

#### `extra.reason`

Type: string. Requirement: optional.

Reason the content could not be processed.

#### `extra.retryable`

Type: boolean. Requirement: optional.

Whether submitting again later may succeed.

#### `extra.retry_at`

Type: string | null. Requirement: optional.

Earliest time to submit again, if provided. Not a pending-response polling interval.

-   format: date-time
-   nullable: true

Response Examples

#### HTTP 200: ready

```json
{
  "code": "SUCCESS",
  "message": "success",
  "data": {
    "status": "ready",
    "source_type": "video",
    "matches": [
      {
        "segment": {
          "start_ms": 12000,
          "end_ms": 18500
        },
        "intent": "Wireless noise-cancelling headphones",
        "offers": [
          {
            "offer_id": "019ad760-1234-7000-8000-000000000101",
            "offer_instance_id": "019ad760-1234-7000-8000-000000000102",
            "version": "3.0",
            "content_language": "en",
            "offer_info": {
              "title": "Wireless noise-cancelling headphones",
              "description": "Comfortable over-ear headphones for travel and everyday listening.",
              "category": {
                "id": "others"
              },
              "commercial": {
                "price": {
                  "amount": "79.99",
                  "currency": "USD"
                }
              }
            },
            "entity": {
              "id": "example-merchant",
              "name": "Example Audio Store"
            },
            "action": {
              "type": "link",
              "payload": {
                "url": "https://track.example/offers/headphones?offer_instance_id=019ad760-1234-7000-8000-000000000102"
              }
            }
          }
        ]
      }
    ],
    "poll_after_ms": null
  },
  "extra": {}
}
```

#### HTTP 202: pending

```json
{
  "code": "SUCCESS",
  "message": "success",
  "data": {
    "status": "pending",
    "poll_after_ms": 2000
  },
  "extra": {}
}
```

### Errors

Errors and failure modes

#### 400: Invalid request or content

INVALID\_JSON, UNSUPPORTED\_MEDIA\_TYPE or VALIDATION\_ERROR: check the request. CONTENT\_ANALYSIS\_FAILED: the content could not be processed.

##### Recommended handling

-   Use application/json and check the documented fields. For CONTENT\_ANALYSIS\_FAILED, inspect extra.reason, retryable and retry\_at before submitting again.

#### 403: Access denied

PLACEMENT\_REJECTED: the placement is not permitted. RESULT\_REVOKED: the result is no longer available.

##### Recommended handling

-   Use a live Content Assistant placement. Stop using a revoked result.

#### 404: Result unavailable

RESULT\_EXPIRED or NOT\_FOUND: no available result was found.

##### Recommended handling

-   Check the content source. Do not treat an expired result as a pending response.

#### 405: Method not allowed

METHOD\_NOT\_ALLOWED: this endpoint requires POST.

##### Recommended handling

-   Use POST and check the Allow response header.

#### 413: Request too large

PAYLOAD\_TOO\_LARGE: the request exceeds the allowed size.

##### Recommended handling

-   Keep the JSON body within 2 MiB and the combined decoded inline text within 256 KiB.

#### 429: Rate limited

RATE\_LIMITED: the request cannot be served yet.

##### Recommended handling

-   Wait for Retry-After or extra.retry\_after\_seconds, then retry with backoff and jitter. This is separate from pending-response polling.

#### 500: Unable to return offers

CONTENT\_ANALYSIS\_FAILED, OFFER\_QUERY\_TIMEOUT, SERVICE\_UNAVAILABLE, INTERNAL\_ERROR or RESULT\_TOO\_LARGE: the request could not be completed.

##### Recommended handling

-   For CONTENT\_ANALYSIS\_FAILED, follow extra.retryable and retry\_at. RESULT\_TOO\_LARGE means the response exceeds 2 MiB; it is not silently truncated. Otherwise retry with backoff if appropriate. Keep X-Request-Id for support.

#### HTTP 429: RATE\_LIMITED

```json
{
  "code": "RATE_LIMITED",
  "message": "Content request capacity is exhausted",
  "data": null,
  "extra": {
    "retry_after_seconds": 60
  }
}
```

#### HTTP 500: SERVICE\_UNAVAILABLE

```json
{
  "code": "SERVICE_UNAVAILABLE",
  "message": "Offer service is temporarily unavailable",
  "data": null,
  "extra": {}
}
```

### Behavior, compatibility, and source notes

Submit content and current viewer context. HTTP 202 means pending: wait poll\_after\_ms and repeat this POST with the same source and language. HTTP 200 means ready, including empty matches or offers. Available matches may be returned even when some matching attempts fail; results do not promise exhaustive inventory or stable ranking. Keep each offer\_instance\_id and action unchanged. The response is limited to 2 MiB; oversized results return RESULT\_TOO\_LARGE, not truncated offers.

-   HTTP 202: data.status is pending. Wait data.poll\_after\_ms, then repeat POST with the same content and language and current viewer context. No Location or task ID is returned.

### Implementation checklist · 3 items

-   Create a live Content Assistant placement in Developer Portal and send its X-AON-Placement-Id on every request; other placement types and anonymous requests are not supported.
-   For HTTP 202, wait data.poll\_after\_ms, then repeat POST with the same content and language and current viewer context.
-   Keep each returned offer\_instance\_id and action unchanged for attribution.

## Related links

[

Content Assistant

Set up a Content Assistant placement before using the Offers for Contents API.

](https://docs.aon.pro/guides/content-assistant)
