> For the complete documentation index, see [llms.txt](https://docs.adreform.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.adreform.com/reference/api-reference/create-an-ad.md).

# Create an Ad

{% hint style="info" %}
This feature is currently in beta. Contact <support@adreform.com> if you're interested in trying the beta.
{% endhint %}

{% openapi src="/files/QzHD2SJJhBbr3N2fe3uR" path="/api/v1/orgs/{organization\_slug}/ads" method="post" %}
[ad-reform-swagger.yaml](https://2718083151-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWXKnh5nreVaQejzmqBUM%2Fuploads%2Fgit-blob-62087cc88de5585e2f431015233e37e65369350e%2Fad-reform-swagger.yaml?alt=media)
{% endopenapi %}

## Overview

This endpoint allows you to create ads independently from the screenshot request workflow. This is useful when you want to:

* Pre-create ads before requesting screenshots
* Manage ad inventory separately from screenshot generation
* Build integrations that separate ad upload from screenshot workflows

## Idempotent Behavior

This endpoint is fully idempotent when using `lookup_key`:

* If an ad with the specified `lookup_key` already exists in the campaign, we'll return the existing ad with a `200 OK` status
* The `media` in the request is ignored when returning an existing ad
* New ads are created with a `201 Created` status

This makes it safe to retry requests without risking duplicate ad creation.

## Disabled organizations

When an organization is disabled (i.e. it does not have an active subscription), the API will return a 403 Forbidden response:

```json
{
  "status": "failure",
  "errors": {
    "organization": ["Your organization is disabled. Upgrade to keep using the API: https://adreform.com/o/_/billing"]
  }
}
```

## The Campaign object

The `campaign` object tells us where to store the ad. You must include either `campaign.id` *or* `campaign.name` in the request.

{% hint style="info" %}
Using `campaign.name` is the most common approach, as we will find *or* create a Campaign using this key.
{% endhint %}

### `campaign.id`

The Ad Reform ID for an existing Campaign. Use this if you know the specific ID for the campaign you want to use.

### `campaign.name`

The name you want to use for the Campaign. If a Campaign already exists with this name, we'll use that, otherwise we'll create a new one.

{% hint style="info" %}
Note: Campaign names cannot be longer than 250 characters. A `campaign.name` longer than 250 characters will be truncated automatically.
{% endhint %}

## The Ad object

The `ad` object contains the ad creative data.

### `ad.lookup_key` (optional)

A unique identifier for this ad. Use this to make requests idempotent - if an ad already exists with this `lookup_key` in the campaign, we'll return that ad instead of creating a new one.

### `ad.name` (optional)

A human-readable name for the ad.

### `ad.media` (required for new ads)

The ad creative content. Contains:

* `type`: Either `"url"` or `"html_tag"`
* `content`: The URL or HTML tag content

{% hint style="warning" %}
The `html_tag` content cannot exceed 1MB in size.
{% endhint %}

## Media Types

### URL (`type: "url"`)

Use this for ad tag URLs that resolve to a creative. The URL will be fetched and rendered to capture the ad preview.

```json
{
  "media": {
    "type": "url",
    "content": "https://example.com/ad-tag?size=300x250"
  }
}
```

### HTML Tag (`type: "html_tag"`)

Use this for raw HTML ad tags (like script tags or iframe embeds).

```json
{
  "media": {
    "type": "html_tag",
    "content": "<script src=\"https://example.com/ad.js\"></script>"
  }
}
```

## Subscribers (optional)

You can optionally subscribe to notifications when the ad preview is captured by including a `subscribers` array.

### Webhook Subscriber

Receive an `ad.uploaded` webhook event when the ad preview is ready:

```json
{
  "subscribers": [
    {
      "webhook": {
        "url": "https://your-endpoint.com/webhooks"
      }
    }
  ]
}
```

{% hint style="warning" %}
Webhook subscribers only receive notifications when a **new ad is created** (201 Created response). If an existing ad is returned via `lookup_key` (200 OK response), no webhook is sent. The existing ad data is returned in the API response, so you can use it directly.
{% endhint %}

### S3 Subscriber

Push the ad preview image to your S3 bucket when ready:

```json
{
  "subscribers": [
    {
      "s3": {
        "bucket": "your-bucket-name",
        "region": "us-east-1",
        "key_prefix": "ads/"
      }
    }
  ]
}
```

{% hint style="info" %}
S3 subscriptions require an S3 connector to be configured for your organization.
{% endhint %}

## Response

### Success Response (201 Created)

For newly created ads:

```json
{
  "type": "ad",
  "id": "uuid-of-created-ad",
  "name": "Ad Name",
  "lookup_key": "your-lookup-key",
  "status": "pending",
  "category": "display",
  "kind": "url",
  "campaign_id": "uuid-of-campaign",
  "created_at": "2026-01-17T10:30:00Z",
  "html_url": "https://app.adreform.com/o/your-org/campaigns/uuid/ads/uuid"
}
```

### Success Response (200 OK)

When an existing ad is returned via `lookup_key`, the response contains the ad's current state:

```json
{
  "type": "ad",
  "id": "uuid-of-existing-ad",
  "name": "Ad Name",
  "lookup_key": "your-lookup-key",
  "status": "uploaded",
  "category": "display",
  "kind": "url",
  "campaign_id": "uuid-of-campaign",
  "created_at": "2026-01-15T10:30:00Z",
  "html_url": "https://app.adreform.com/o/your-org/campaigns/uuid/ads/uuid"
}
```

### Status Values

| Status             | Description                             |
| ------------------ | --------------------------------------- |
| `pending`          | Ad created, preview capture in progress |
| `uploaded`         | Preview capture completed successfully  |
| `not_found`        | Media URL could not be found            |
| `net_read_timeout` | Network timeout during capture          |
| `generic_error`    | Other capture error                     |

### Error Response (422 Unprocessable Entity)

```json
{
  "status": "failure",
  "errors": {
    "media.content": ["exceeds maximum size of 1MB"]
  }
}
```

## Example Requests

### Create ad with URL media

```bash
curl -X POST "https://app.adreform.com/api/v1/orgs/your-org/ads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": {
      "name": "Q1 2026 Campaign"
    },
    "ad": {
      "name": "Banner 300x250",
      "lookup_key": "banner-300x250-v1",
      "media": {
        "type": "url",
        "content": "https://example.com/ads/banner.png"
      }
    }
  }'
```

### Create ad with HTML tag and webhook subscriber

```bash
curl -X POST "https://app.adreform.com/api/v1/orgs/your-org/ads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": {
      "name": "Q1 2026 Campaign"
    },
    "ad": {
      "name": "Rich Media Ad",
      "media": {
        "type": "html_tag",
        "content": "<script src=\"https://example.com/rich-media.js\"></script>"
      }
    },
    "subscribers": [
      {
        "webhook": {
          "url": "https://your-endpoint.com/ad-webhook"
        }
      }
    ]
  }'
```

### Idempotent request (returns existing ad)

```bash
# First request creates the ad (201 Created)
curl -X POST "https://app.adreform.com/api/v1/orgs/your-org/ads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": {"name": "My Campaign"},
    "ad": {
      "lookup_key": "my-unique-key",
      "media": {"type": "url", "content": "https://example.com/ad.png"}
    }
  }'

# Retry request returns existing ad (200 OK)
curl -X POST "https://app.adreform.com/api/v1/orgs/your-org/ads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": {"name": "My Campaign"},
    "ad": {
      "lookup_key": "my-unique-key",
      "media": {"type": "url", "content": "https://different-url.com"}
    }
  }'
# ^ Returns the existing ad, ignores the different media
```
