---
title: "PAI Palooza Advertising API"
description: "PAI Palooza advertising API — sponsor placements, analytics, and campaign management"
canonical: "https://docs.ainative.studio/docs/community/pai-palooza"
last-updated: "2026-10-22T17:48:46.689Z"
---

# PAI Palooza Advertising API

Source: https://docs.ainative.studio/docs/community/pai-palooza

> PAI Palooza advertising API — sponsor placements, analytics, and campaign management

# PAI Palooza Advertising API

PAI Palooza is AINative Studio's event advertising platform. Event organizers monetize their event pages by selling ad slots; sponsors purchase ad credits and run targeted campaigns. The API covers the complete advertising stack: sponsor registration, creative upload, campaign management, ad serving, impression/click tracking, and payouts.

**Base path:** `https://api.ainative.studio/api/v1/paipalooza`

**Authentication:** All endpoints require `Authorization: Bearer <your_api_key>` unless explicitly marked as public.

---

## Sponsors

Sponsors are companies or individuals that purchase ad credits to run campaigns on event pages.

### Create sponsor profile

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/sponsors`

Register as a sponsor. Creates a Stripe customer account and initializes a credit balance of 0.

#### Request body

```json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "company_name": "Acme AI Corp",
  "industry": "Artificial Intelligence",
  "website_url": "https://acmeai.example.com",
  "contact_email": "ads@acmeai.example.com"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `user_id` | `string` (UUID) | Yes | Must match the authenticated user's ID |
| `company_name` | `string` | Yes | Company or advertiser name |
| `industry` | `string` | No | Industry category |
| `website_url` | `string` | No | Company website |
| `contact_email` | `string` | Yes | Contact email for billing and notifications |

#### Response `201 Created`

```json
{
  "id": "sponsor_abc123",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "company_name": "Acme AI Corp",
  "industry": "Artificial Intelligence",
  "website_url": "https://acmeai.example.com",
  "contact_email": "ads@acmeai.example.com",
  "stripe_customer_id": "cus_abc123xyz",
  "credit_balance": 0,
  "auto_reload_enabled": false,
  "auto_reload_threshold": null,
  "auto_reload_amount": null,
  "created_at": "2026-04-25T14:00:00Z"
}
```

---

### Get sponsor profile

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/sponsors/{sponsor_id}`

Retrieve sponsor profile including current credit balance.

#### Path parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `sponsor_id` | `string` | Yes | Sponsor ID |

#### Response `200 OK`

Returns a `SponsorResponse` object (same shape as create response).

---

### Update sponsor profile

<span className="badge badge--patch">PATCH</span> `/api/v1/paipalooza/sponsors/{sponsor_id}`

Update sponsor information. Only the sponsor owner or an admin can update.

Updatable fields: `company_name`, `industry`, `website_url`, `contact_email`, `auto_reload_enabled`, `auto_reload_threshold`, `auto_reload_amount`.

---

### Purchase ad credits

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/sponsors/{sponsor_id}/purchase-credits`

Purchase ad credits via Stripe. 1 credit = $1 USD.

The flow:
1. This endpoint creates a Stripe payment intent and returns a `client_secret`.
2. Your frontend completes the payment using Stripe.js.
3. Credits are automatically added to the sponsor's balance via Stripe webhook.

#### Request body

```json
{
  "sponsor_id": "sponsor_abc123",
  "amount": 500,
  "payment_method_id": "pm_stripe_test_method"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `sponsor_id` | `string` | Yes | Must match the path parameter |
| `amount` | `integer` | Yes | Number of credits to purchase (= USD amount) |
| `payment_method_id` | `string` | Yes | Stripe payment method ID |

#### Response `200 OK`

```json
{
  "payment_intent_id": "pi_abc123",
  "client_secret": "pi_abc123_secret_xyz",
  "amount": 500,
  "status": "requires_confirmation"
}
```

---

### Get ad credits balance

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/sponsors/{sponsor_id}/ad-credits`

Get current credit balance and usage summary.

#### Response `200 OK`

```json
{
  "sponsor_id": "sponsor_abc123",
  "total_credits_purchased": 1500,
  "credits_used": 342,
  "remaining_balance": 1158,
  "total_usd_spent": 342.00,
  "auto_reload_enabled": false,
  "auto_reload_threshold": null,
  "low_balance_alert": false
}
```

---

### Get usage breakdown by campaign

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/sponsors/{sponsor_id}/usage-breakdown`

Get per-campaign credit usage for a given time period.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `time_period` | `string` | `last_30_days` | `last_7_days`, `last_30_days`, `last_90_days`, or `all_time` |

#### Response `200 OK`

```json
{
  "sponsor_id": "sponsor_abc123",
  "time_period": "last_30_days",
  "campaigns": [
    {
      "campaign_id": "camp_abc123",
      "campaign_name": "AI Conference Q1 2026",
      "credits_used": 200,
      "impressions": 10000,
      "clicks": 320,
      "ctr": 0.032,
      "average_cpm": 20.00
    }
  ]
}
```

---

## Ad Creatives

Creatives are the ad assets (video, banner, or native) that run inside campaigns. All creatives start in `pending_review` status and must be approved before they can serve.

### Creative statuses

| Status | Meaning |
|---|---|
| `pending_review` | Awaiting admin approval |
| `approved` | Active and available for serving |
| `rejected` | Rejected by admin; cannot serve |

### Upload video creative

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/ad-creatives/video`

Upload a video ad. The pipeline transcodes the video to 720p and 1080p, extracts a thumbnail, and generates a VAST 4.2 XML document.

**Content-Type:** `multipart/form-data`

#### Form fields

| Field | Type | Required | Description |
|---|---|---|---|
| `video_file` | `file` | Yes | MP4, MOV, or WebM; max 500 MB |
| `title` | `string` | Yes | Creative title (3–200 characters) |
| `call_to_action` | `string` | Yes | CTA text (e.g., `Register Now`, `Learn More`, `Watch Now`) |
| `destination_url` | `string` | Yes | Click destination (must be HTTPS) |
| `description` | `string` | No | Creative description (max 1000 characters) |
| `duration_seconds` | `integer` | No | Video duration in seconds (5–120); auto-detected if omitted |

#### Response `201 Created`

```json
{
  "id": "creative_def456",
  "sponsor_id": "sponsor_abc123",
  "type": "video",
  "status": "pending_review",
  "title": "Acme AI — Register Now",
  "call_to_action": "Register Now",
  "destination_url": "https://acmeai.example.com/register",
  "duration_seconds": 30,
  "video_url": "https://r2.example.com/videos/creative_def456_original.mp4",
  "transcoded_urls": {
    "720p": "https://r2.example.com/videos/creative_def456_720p.mp4",
    "1080p": "https://r2.example.com/videos/creative_def456_1080p.mp4"
  },
  "thumbnail_url": "https://r2.example.com/thumbnails/creative_def456.jpg",
  "vast_xml_url": "https://api.ainative.studio/api/v1/paipalooza/ad-creatives/creative_def456/vast.xml",
  "vast_tracking_urls": {
    "impression": "https://api.ainative.studio/api/v1/paipalooza/tracking/impression",
    "start": "...",
    "complete": "..."
  },
  "created_at": "2026-04-25T14:00:00Z"
}
```

#### Example

```bash
curl -X POST https://api.ainative.studio/api/v1/paipalooza/ad-creatives/video \
  -H "Authorization: Bearer $API_KEY" \
  -F "video_file=@/path/to/ad.mp4" \
  -F "title=Acme AI — Register Now" \
  -F "call_to_action=Register Now" \
  -F "destination_url=https://acmeai.example.com/register"
```

```python
with open("/path/to/ad.mp4", "rb") as f:
    resp = requests.post(
        "https://api.ainative.studio/api/v1/paipalooza/ad-creatives/video",
        headers={"Authorization": f"Bearer {api_key}"},
        data={
            "title": "Acme AI — Register Now",
            "call_to_action": "Register Now",
            "destination_url": "https://acmeai.example.com/register",
        },
        files={"video_file": f}
    )
creative = resp.json()
```

---

### Upload banner creative

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/ad-creatives/banner`

Upload a banner image. Dimensions must exactly match the declared IAB standard size.

**Content-Type:** `multipart/form-data`

#### IAB standard sizes

| `ad_size` value | Dimensions | Common use |
|---|---|---|
| `300x250` | 300 × 250 | Medium Rectangle — inline content |
| `728x90` | 728 × 90 | Leaderboard — top of page |
| `320x50` | 320 × 50 | Mobile Banner — bottom sticky |
| `160x600` | 160 × 600 | Wide Skyscraper — sidebar |
| `970x250` | 970 × 250 | Billboard — homepage takeover |

#### Form fields

| Field | Type | Required | Description |
|---|---|---|---|
| `image_file` | `file` | Yes | JPG, PNG, or GIF; max 5 MB |
| `title` | `string` | Yes | Creative title (3–200 characters) |
| `call_to_action` | `string` | Yes | CTA text |
| `destination_url` | `string` | Yes | Click destination (HTTPS) |
| `ad_size` | `string` | Yes | One of the IAB sizes above |
| `description` | `string` | No | Creative description |

#### Response `201 Created`

```json
{
  "id": "creative_banner789",
  "sponsor_id": "sponsor_abc123",
  "type": "banner",
  "status": "pending_review",
  "title": "Acme AI Leaderboard",
  "ad_size": "728x90",
  "width": 728,
  "height": 90,
  "banner_url": "https://r2.example.com/banners/creative_banner789.jpg",
  "is_animated": false,
  "destination_url": "https://acmeai.example.com/register",
  "created_at": "2026-04-25T14:05:00Z"
}
```

---

### Create native creative

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/ad-creatives/native`

Create a text-based native ad that blends into content feeds. An optional image can be uploaded.

**Content-Type:** `multipart/form-data`

#### Form fields

| Field | Type | Required | Constraints |
|---|---|---|---|
| `title` | `string` | Yes | 10–60 characters |
| `description` | `string` | Yes | 30–200 characters |
| `call_to_action` | `string` | Yes | Max 20 characters |
| `destination_url` | `string` | Yes | HTTPS |
| `sponsor_name` | `string` | No | Max 50 characters |
| `sponsor_logo_url` | `string` | No | HTTPS URL |
| `image_file` | `file` | No | JPG, PNG, or GIF; max 5 MB |

#### Response `201 Created`

```json
{
  "id": "creative_native001",
  "sponsor_id": "sponsor_abc123",
  "type": "native",
  "status": "pending_review",
  "title": "Build smarter AI agents",
  "description": "AINative Studio gives you the tools to ship AI-native apps in days, not months.",
  "call_to_action": "Start Free Trial",
  "destination_url": "https://ainative.studio/signup",
  "sponsor_name": "AINative Studio",
  "image_url": null,
  "created_at": "2026-04-25T14:08:00Z"
}
```

---

### List creatives

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/ad-creatives`

List your creatives with optional filtering.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `status` | `string` | — | `pending_review`, `approved`, or `rejected` |
| `type` | `string` | — | `video`, `banner`, or `native` |
| `skip` | `integer` | `0` | Pagination offset |
| `limit` | `integer` | `20` | Results per page |

#### Response `200 OK`

```json
{
  "creatives": [
    {
      "id": "creative_def456",
      "type": "video",
      "status": "approved",
      "title": "Acme AI — Register Now"
    }
  ],
  "total": 3,
  "skip": 0,
  "limit": 20
}
```

---

### Get creative

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/ad-creatives/{creative_id}`

Get full details for a specific creative. You must own the creative.

---

### Update creative metadata

<span className="badge badge--put">PUT</span> `/api/v1/paipalooza/ad-creatives/{creative_id}`

Update title, description, CTA, or destination URL. Only `pending_review` creatives can be updated; approved or rejected creatives are locked.

#### Request body

```json
{
  "title": "Acme AI — Join Us Today",
  "destination_url": "https://acmeai.example.com/join"
}
```

---

### Delete creative

<span className="badge badge--delete">DELETE</span> `/api/v1/paipalooza/ad-creatives/{creative_id}`

Delete a creative. Only `pending_review` creatives can be deleted. Returns `204 No Content`.

---

### Get VAST 4.2 XML

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/ad-creatives/{creative_id}/vast.xml`

**Public endpoint — no auth required.** Serves an IAB VAST 4.2 compliant XML document for video ad players. Only works for `approved` creatives.

**Response Content-Type:** `application/xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<VAST version="4.2">
  <Ad id="creative_def456">
    <InLine>
      <AdTitle>Acme AI — Register Now</AdTitle>
      <Creatives>
        <Creative>
          <Linear>
            <Duration>00:00:30</Duration>
            <MediaFiles>
              <MediaFile type="video/mp4" width="1280" height="720">
                https://r2.example.com/videos/creative_def456_720p.mp4
              </MediaFile>
            </MediaFiles>
            <VideoClicks>
              <ClickThrough>https://acmeai.example.com/register</ClickThrough>
            </VideoClicks>
          </Linear>
        </Creative>
      </Creatives>
    </InLine>
  </Ad>
</VAST>
```

---

## Campaigns

Campaigns tie together a sponsor, one or more approved creatives, a budget, a date range, and optional targeting rules.

### Campaign statuses

| Status | Description |
|---|---|
| `draft` | Not yet serving; can be edited |
| `active` | Serving ads; credits are being consumed |
| `paused` | Temporarily stopped; budget is still reserved |
| `completed` | End date reached or budget exhausted |
| `cancelled` | Manually cancelled |

### Create a campaign

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/campaigns/`

Create a new campaign. New campaigns start in `draft` status.

#### Request body

```json
{
  "sponsor_id": "sponsor_abc123",
  "name": "AI Conference Q1 2026",
  "creative_ids": ["creative_def456"],
  "budget_credits": 1000,
  "start_date": "2026-05-01T00:00:00Z",
  "end_date": "2026-06-30T23:59:59Z",
  "targeting": {
    "event_types": ["conference", "hackathon"],
    "topics": ["AI", "machine learning"],
    "locations": ["San Francisco", "New York"],
    "min_attendees": 50,
    "max_attendees": null
  }
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `sponsor_id` | `string` | Yes | Sponsor ID (must own the sponsor account) |
| `name` | `string` | Yes | Campaign name |
| `creative_ids` | `array[string]` | Yes | One or more approved creative IDs |
| `budget_credits` | `integer` | Yes | Total credits to allocate (1 credit = $1) |
| `start_date` | `datetime` | Yes | Campaign start (must be in the future) |
| `end_date` | `datetime` | Yes | Campaign end (must be after start) |
| `targeting` | `object` | No | Targeting rules — null means target all events |
| `targeting.event_types` | `array[string]` | No | Match events by type |
| `targeting.topics` | `array[string]` | No | Match events by topic via semantic similarity (threshold ≥ 0.7) |
| `targeting.locations` | `array[string]` | No | Match events by location (case-insensitive) |
| `targeting.min_attendees` | `integer` | No | Minimum attendee threshold |
| `targeting.max_attendees` | `integer` | No | Maximum attendee threshold |

#### Response `201 Created`

```json
{
  "id": "camp_abc123",
  "sponsor_id": "sponsor_abc123",
  "name": "AI Conference Q1 2026",
  "status": "draft",
  "creative_ids": ["creative_def456"],
  "budget_credits": 1000,
  "spent_credits": 0,
  "remaining_credits": 1000,
  "start_date": "2026-05-01T00:00:00Z",
  "end_date": "2026-06-30T23:59:59Z",
  "targeting": {
    "event_types": ["conference", "hackathon"],
    "topics": ["AI", "machine learning"],
    "locations": ["San Francisco", "New York"]
  },
  "created_at": "2026-04-25T14:00:00Z"
}
```

#### Errors

| Code | Reason |
|---|---|
| `400` | Invalid dates, or start date is in the past |
| `402` | Insufficient credits |
| `403` | Creative does not belong to this sponsor |
| `404` | Sponsor or creative not found |

#### Example

```bash
curl -X POST https://api.ainative.studio/api/v1/paipalooza/campaigns/ \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sponsor_id": "sponsor_abc123",
    "name": "AI Conference Q1 2026",
    "creative_ids": ["creative_def456"],
    "budget_credits": 1000,
    "start_date": "2026-05-01T00:00:00Z",
    "end_date": "2026-06-30T23:59:59Z"
  }'
```

```python
resp = requests.post(
    "https://api.ainative.studio/api/v1/paipalooza/campaigns/",
    headers={"Authorization": f"Bearer {api_key}"},
    json={
        "sponsor_id": "sponsor_abc123",
        "name": "AI Conference Q1 2026",
        "creative_ids": ["creative_def456"],
        "budget_credits": 1000,
        "start_date": "2026-05-01T00:00:00Z",
        "end_date": "2026-06-30T23:59:59Z",
    }
)
campaign = resp.json()
```

---

### Get campaign

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/{campaign_id}`

Retrieve full campaign details including budget tracking.

---

### List campaigns

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/`

List campaigns with filtering, sorting, and pagination.

#### Query parameters

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `sponsor_id` | `string` | Yes | — | Filter by sponsor |
| `status` | `string` | No | — | `draft`, `active`, `paused`, `completed`, or `cancelled` |
| `creative_id` | `string` | No | — | Filter to campaigns using this creative |
| `event_type` | `string` | No | — | Filter by event type targeting |
| `start_date_from` | `datetime` | No | — | Campaigns starting on or after (ISO 8601) |
| `start_date_to` | `datetime` | No | — | Campaigns starting on or before (ISO 8601) |
| `search` | `string` | No | — | Search in campaign name |
| `sort_by` | `string` | No | `created_at` | `created_at`, `start_date`, `budget_credits`, or `name` |
| `sort_order` | `string` | No | `desc` | `asc` or `desc` |
| `offset` | `integer` | No | `0` | Pagination offset |
| `limit` | `integer` | No | `20` | Results per page (max 100) |

#### Response `200 OK`

```json
{
  "campaigns": [...],
  "total": 5,
  "limit": 20,
  "offset": 0,
  "has_more": false
}
```

---

### Update campaign

<span className="badge badge--patch">PATCH</span> `/api/v1/paipalooza/campaigns/{campaign_id}`

Update a `draft` or `paused` campaign. Active and completed campaigns cannot be modified. You can increase `budget_credits` or extend `end_date`, but cannot shorten the campaign or change the `start_date`.

---

### Activate campaign

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/campaigns/{campaign_id}/activate`

Transition a `draft` campaign to `active`. The campaign will begin serving ads immediately based on its targeting configuration.

**Requirements:** Campaign must be in `draft` status; all creatives must still be approved.

---

### Pause campaign

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/campaigns/{campaign_id}/pause`

Stop a running campaign. No new impressions will be served, but the budget remains reserved.

#### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `sponsor_id` | `string` | Yes | Sponsor ID for authorization |

---

### Resume campaign

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/campaigns/{campaign_id}/resume`

Resume a `paused` campaign. Requires sufficient credits for the daily budget.

#### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `sponsor_id` | `string` | Yes | Sponsor ID for authorization |

---

### Preview targeting

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/{campaign_id}/preview-targeting`

Before activating, preview which published events match the campaign's targeting criteria.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `limit` | `integer` | `50` | Max matching events to return (max 100) |

#### Response `200 OK`

```json
{
  "campaign_id": "camp_abc123",
  "targeting": {
    "event_types": ["conference"],
    "topics": ["AI"]
  },
  "matching_events": [
    {
      "id": "evt_001",
      "title": "AI Summit 2026",
      "event_type": "conference"
    }
  ],
  "total_matched": 12,
  "similarity_scores": {
    "evt_001": 0.92
  }
}
```

---

## Campaign & Creative Analytics

### Get campaign performance

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/{campaign_id}/performance`

Get comprehensive performance metrics for a campaign.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `time_range` | `string` | `7d` | `24h`, `7d`, `30d`, or `custom` |
| `start_date` | `datetime` | — | Required when `time_range=custom` |
| `end_date` | `datetime` | — | Required when `time_range=custom` |
| `include_creatives` | `boolean` | `true` | Include per-creative breakdown |
| `include_events` | `boolean` | `true` | Include per-event breakdown |

#### Response `200 OK`

```json
{
  "campaign": {
    "campaign_id": "camp_abc123",
    "impressions": 50000,
    "clicks": 1600,
    "completions": 8500,
    "ctr": 0.032,
    "completion_rate": 0.17,
    "total_spend": 100.00,
    "average_cpm": 2.00,
    "average_cpc": 0.063
  },
  "by_creative": [
    {
      "creative_id": "creative_def456",
      "impressions": 50000,
      "clicks": 1600,
      "ctr": 0.032
    }
  ],
  "by_event": [
    {
      "event_id": "evt_001",
      "impressions": 30000,
      "revenue": 60.00,
      "fill_rate": 0.85
    }
  ],
  "time_range": "7d",
  "generated_at": "2026-04-25T15:00:00Z"
}
```

---

### Export campaign performance as CSV

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/{campaign_id}/performance/export`

Export campaign performance as a CSV file. Accepts the same query parameters as the performance endpoint.

#### Response `200 OK`

```json
{
  "filename": "campaign_camp_abc123_7d_2026-04-25.csv",
  "content": "type,id,name,impressions,clicks,completions,ctr,completion_rate,spend\n...",
  "row_count": 12,
  "generated_at": "2026-04-25T15:00:00Z"
}
```

---

### Get creative performance

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/creative/{creative_id}/performance`

Get detailed performance analytics for a specific creative across all campaigns.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `time_range` | `string` | `7d` | `24h`, `7d`, `30d`, or `custom` |
| `campaign_id` | `string` | — | Scope to a specific campaign |
| `include_daily` | `boolean` | `true` | Include daily time series |
| `include_by_campaign` | `boolean` | `true` | Include breakdown by campaign |

---

### Compare creatives

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/creatives/compare`

Compare up to 10 creatives side-by-side, ranked by CTR.

#### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `creative_ids` | `array[string]` | Yes | List of creative IDs to compare (max 10) |
| `time_range` | `string` | No | `24h`, `7d`, `30d`, or `custom` |

#### Response `200 OK`

```json
{
  "creatives": [
    {
      "creative_id": "creative_def456",
      "impressions": 50000,
      "clicks": 1600,
      "ctr": 0.032,
      "rank": 1
    },
    {
      "creative_id": "creative_banner789",
      "impressions": 30000,
      "clicks": 450,
      "ctr": 0.015,
      "rank": 2
    }
  ],
  "best_performing": "creative_def456",
  "total_creatives": 2,
  "time_range": "7d",
  "generated_at": "2026-04-25T15:00:00Z"
}
```

---

### Get event performance (organizer view)

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/event/{event_id}/performance`

Ad revenue analytics for a specific event — designed for event organizers to see how much their event is earning from ad placements.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `time_range` | `string` | `7d` | `24h`, `7d`, `30d`, or `custom` |
| `include_daily` | `boolean` | `true` | Daily revenue time series |
| `include_sponsors` | `boolean` | `true` | Top sponsors by spend |

#### Response `200 OK`

```json
{
  "event": {
    "event_id": "evt_001",
    "impressions": 80000,
    "unique_impressions": 65000,
    "clicks": 2560,
    "completions": 12800,
    "ctr": 0.032,
    "completion_rate": 0.16,
    "total_revenue": 160.00,
    "average_rpm": 2.00,
    "fill_rate": 0.87
  },
  "daily_revenue": [
    { "date": "2026-04-19", "revenue": 22.40 },
    { "date": "2026-04-20", "revenue": 28.60 }
  ],
  "top_sponsors": [
    { "sponsor_id": "sponsor_abc123", "spend": 100.00 }
  ],
  "time_range": "7d",
  "generated_at": "2026-04-25T15:00:00Z"
}
```

---

### Compare events

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/campaigns/events/compare`

Compare up to 10 events side-by-side, ranked by revenue.

#### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `event_ids` | `array[string]` | Yes | Event IDs to compare (max 10) |
| `time_range` | `string` | No | `24h`, `7d`, `30d`, or `custom` |

---

## Ad Serving

### Serve an ad

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/ads/serve`

**Public endpoint — no auth required.** Called by the frontend ad widget when an event page loads. Returns the best-matched ad for the given placement, or `204 No Content` if no eligible ads are available.

**Rate limit:** 10,000 requests/minute per IP.

#### Request body

```json
{
  "event_id": "evt_001",
  "placement": "header",
  "device_type": "desktop"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `event_id` | `string` | Yes | ID of the event page requesting an ad |
| `placement` | `string` | Yes | Ad slot: `header`, `sidebar`, `content`, or `video` |
| `device_type` | `string` | Yes | `desktop`, `mobile`, or `tablet` |

#### Response `200 OK` — ad found

```json
{
  "ad_id": "ad_xyz789abc123",
  "creative_id": "creative_def456",
  "campaign_id": "camp_abc123",
  "creative_type": "video",
  "creative_data": {
    "title": "AI Conference 2026",
    "video_url": "https://r2.example.com/videos/creative_def456_720p.mp4",
    "thumbnail_url": "https://r2.example.com/thumbnails/creative_def456.jpg",
    "duration_seconds": 30,
    "call_to_action": "Register Now",
    "destination_url": "https://acmeai.example.com/register"
  },
  "tracking_urls": {
    "impression_url": "https://api.ainative.studio/api/v1/paipalooza/tracking/impression",
    "click_url": "https://api.ainative.studio/api/v1/paipalooza/tracking/click",
    "completion_url": "https://api.ainative.studio/api/v1/paipalooza/tracking/completion"
  }
}
```

#### Response `204 No Content`

No eligible ads are available for this placement.

#### Example

```javascript
// Frontend ad widget
const resp = await fetch("https://api.ainative.studio/api/v1/paipalooza/ads/serve", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    event_id: "evt_001",
    placement: "header",
    device_type: "desktop"
  })
});

if (resp.status === 204) {
  // No ad available — hide ad slot
  return;
}

const ad = await resp.json();
// Render ad using ad.creative_data
// Fire impression beacon to ad.tracking_urls.impression_url
```

---

## Tracking

All tracking endpoints are **public** — no authentication required. They are designed to be called by ad widgets and VAST-compatible video players.

**Rate limit:** 1,000 requests/minute per IP for all tracking endpoints.

### Track impression

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/tracking/impression`

Record that an ad was displayed. Deduplicates within 24 hours per authenticated user.

#### Credit costs per placement

| Placement | Cost per impression |
|---|---|
| `header` | $0.02 |
| `sidebar` | $0.01 |
| `content` | $0.015 |
| `video` | $0.03 |
| `footer` | $0.005 |

#### Request body

```json
{
  "campaign_id": "camp_abc123",
  "creative_id": "creative_def456",
  "event_id": "evt_001",
  "placement": "header",
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `campaign_id` | `string` | Yes | Active campaign ID |
| `creative_id` | `string` | Yes | Approved creative ID assigned to the campaign |
| `event_id` | `string` | Yes | Event page where the ad was shown |
| `placement` | `string` | Yes | `header`, `sidebar`, `content`, `video`, or `footer` |
| `user_id` | `string` (UUID) | No | Authenticated viewer (for deduplication) |

#### Response `201 Created`

```json
{
  "impression_id": "imp_abc123xyz",
  "campaign_id": "camp_abc123",
  "creative_id": "creative_def456",
  "event_id": "evt_001",
  "placement": "header",
  "credits_deducted": 0.02,
  "created_at": "2026-04-25T15:00:00Z"
}
```

#### Errors

| Code | Reason |
|---|---|
| `400` | Campaign inactive or creative unapproved |
| `404` | Campaign, creative, or event not found |
| `409` | Duplicate impression within 24h for this user |

---

### Track click

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/tracking/click`

Record that a user clicked an ad. Must reference a valid impression.

#### Request body

```json
{
  "impression_id": "imp_abc123xyz",
  "campaign_id": "camp_abc123",
  "creative_id": "creative_def456",
  "destination_url": "https://acmeai.example.com/register"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `impression_id` | `string` | Yes | Impression to associate with this click |
| `campaign_id` | `string` | Yes | Campaign ID |
| `creative_id` | `string` | Yes | Creative ID |
| `destination_url` | `string` | Yes | Click destination URL |

#### Response `201 Created`

```json
{
  "click_id": "clk_def456abc",
  "impression_id": "imp_abc123xyz",
  "campaign_id": "camp_abc123",
  "redirect_url": "https://acmeai.example.com/register",
  "time_to_click_seconds": 12,
  "created_at": "2026-04-25T15:00:12Z"
}
```

#### Notes

- `time_to_click_seconds`: seconds elapsed between impression and click
- Clicks within 30 seconds earn an engagement quality bonus
- Duplicate clicks for the same impression are rejected with `409`

---

### Track video completion

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/tracking/completion`

Track video ad playback milestones. Only applies to video creatives.

#### Milestones

| Milestone | `milestone` value | Engagement score |
|---|---|---|
| Ad started | `0` | 0.0 |
| 25% watched | `25` | 0.25 |
| 50% watched | `50` | 0.50 |
| 75% watched | `75` | 0.75 |
| 100% watched | `100` | 1.0 |

Only 100% completion earns a $0.01 credit bonus.

#### Request body

```json
{
  "impression_id": "imp_abc123xyz",
  "campaign_id": "camp_abc123",
  "creative_id": "creative_def456",
  "milestone": 100
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `impression_id` | `string` | Yes | The originating impression |
| `campaign_id` | `string` | Yes | Campaign ID |
| `creative_id` | `string` | Yes | Must be a video creative |
| `milestone` | `integer` | Yes | `0`, `25`, `50`, `75`, or `100` |

#### Response `201 Created`

```json
{
  "completion_id": "cmp_ghi789jkl",
  "impression_id": "imp_abc123xyz",
  "campaign_id": "camp_abc123",
  "milestone": 100,
  "engagement_score": 1.0,
  "credits_bonus": 0.01,
  "created_at": "2026-04-25T15:00:30Z"
}
```

#### Errors

| Code | Reason |
|---|---|
| `400` | Creative is not video type, or campaign is inactive |
| `409` | Duplicate milestone for this impression |

---

## Content Inventory

Event organizers define ad slot inventory that sponsors can browse and campaigns can fill.

### Create inventory

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/content-inventory/events/{event_id}/inventory`

**Organizer auth required.** Define available ad slots for an event.

Wait — this endpoint path is constructed from the router prefix. Based on the source, the path is:

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/content-inventory`

With `event_id` as a query parameter.

#### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `event_id` | `string` | Yes | Event to attach inventory to |

#### Request body

```json
{
  "slot_type": "video_preroll",
  "format": "video",
  "total_slots": 10,
  "floor_price": 5.00,
  "suggested_price": 8.00
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `slot_type` | `string` | Yes | `video_preroll`, `banner_sidebar`, `email_header`, `content_native`, or `audio_preroll` |
| `format` | `string` | Yes | `video`, `banner`, `native`, or `audio` |
| `total_slots` | `integer` | Yes | Number of available slots (must be > 0) |
| `floor_price` | `number` | Yes | Minimum price per slot (USD, >= 0) |
| `suggested_price` | `number` | Yes | Recommended price per slot (>= floor_price) |

#### Response `201 Created`

```json
{
  "id": "inv_abc001",
  "event_id": "evt_001",
  "organizer_id": "organizer_xyz",
  "slot_type": "video_preroll",
  "format": "video",
  "total_slots": 10,
  "filled_slots": 0,
  "available_slots": 10,
  "floor_price": 5.00,
  "suggested_price": 8.00,
  "created_at": "2026-04-25T14:00:00Z"
}
```

---

### Get inventory

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/content-inventory/{inventory_id}`

**Public endpoint.** Get inventory details by ID.

---

### List event inventory

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/content-inventory/events/{event_id}/inventory`

**Public endpoint.** Browse available ad slots for an event — no auth required so sponsors can discover inventory before registering.

#### Path parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `event_id` | `string` | Yes | Event ID |

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `format` | `string` | — | Filter by format: `video`, `banner`, `native`, or `audio` |
| `sort_by` | `string` | `price` | `price` or `availability` |
| `sort_order` | `string` | `asc` | `asc` or `desc` |
| `limit` | `integer` | `25` | Results per page (1–100) |
| `offset` | `integer` | `0` | Pagination offset |

#### Response `200 OK`

```json
{
  "items": [
    {
      "id": "inv_abc001",
      "event_id": "evt_001",
      "slot_type": "video_preroll",
      "format": "video",
      "total_slots": 10,
      "filled_slots": 3,
      "available_slots": 7,
      "floor_price": 5.00,
      "suggested_price": 8.00
    }
  ],
  "total": 4,
  "has_more": false
}
```

---

## Organizers

Event organizers register to monetize their events through ad revenue.

### Create organizer

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/organizers`

Register as an event organizer. Creates a Stripe Connect Express account and sets default revenue share to 60%.

#### Request body

```json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "organization_name": "AI Events LLC",
  "contact_email": "hello@aievents.example.com",
  "contact_phone": "+14155551234"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `user_id` | `string` (UUID) | Yes | Must match authenticated user |
| `organization_name` | `string` | Yes | Organization name |
| `contact_email` | `string` | Yes | Used for Stripe Connect verification |
| `contact_phone` | `string` | No | Contact phone number |

#### Response `201 Created`

```json
{
  "id": "organizer_xyz",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "organization_name": "AI Events LLC",
  "contact_email": "hello@aievents.example.com",
  "stripe_connect_account_id": "acct_abc123",
  "revenue_share_percentage": 60,
  "status": "pending",
  "total_revenue_earned": 0.00,
  "total_payouts": 0.00,
  "created_at": "2026-04-25T14:00:00Z"
}
```

#### Errors

| Code | Reason |
|---|---|
| `400` | User already has an organizer account |
| `403` | `user_id` does not match authenticated user |

---

### Get organizer

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/organizers/{organizer_id}`

Get organizer profile. Accessible only by the organizer themselves or an admin.

---

### Update organizer

<span className="badge badge--patch">PATCH</span> `/api/v1/paipalooza/organizers/{organizer_id}`

Update `organization_name`, `contact_email`, or `contact_phone`. Revenue share percentage can only be changed by an admin.

---

### Get organizer revenue

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/organizers/{organizer_id}/revenue`

View revenue metrics and payout status for an organizer.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `days` | `integer` | `7` | Time range in days (1–365) |

#### Response `200 OK`

```json
{
  "organizer_id": "organizer_xyz",
  "total_revenue_earned": 1500.00,
  "total_payouts": 1000.00,
  "pending_balance": 500.00,
  "payout_eligible": true,
  "payout_threshold": 100.00,
  "revenue_by_event": [
    {
      "event_id": "evt_001",
      "event_title": "AI Summit 2026",
      "revenue": 800.00
    }
  ],
  "period_impressions": 400000,
  "period_revenue": 350.00,
  "period_days": 7
}
```

---

## Payouts

Organizers receive 60% of ad revenue (configurable per organizer). Payouts require a minimum pending balance of $100 and a valid Stripe Connect account.

### Check payout eligibility

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/payouts/eligibility/{organizer_id}`

Check whether an organizer is eligible for a payout.

#### Response `200 OK`

```json
{
  "organizer_id": "organizer_xyz",
  "pending_balance": 500.00,
  "payout_threshold": 100.00,
  "eligible": true,
  "reason": null
}
```

If not eligible:

```json
{
  "organizer_id": "organizer_xyz",
  "pending_balance": 42.00,
  "payout_threshold": 100.00,
  "eligible": false,
  "reason": "Pending balance $42.00 is below the $100.00 threshold"
}
```

---

### Get payout history

<span className="badge badge--get">GET</span> `/api/v1/paipalooza/payouts/history/{organizer_id}`

Retrieve payout history, sorted most recent first.

#### Query parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `limit` | `integer` | `100` | Maximum records to return (max 1000) |

#### Response `200 OK`

```json
[
  {
    "payout_id": "pay_abc001",
    "amount": 500.00,
    "status": "completed",
    "stripe_payout_id": "po_stripe_123",
    "created_at": "2026-04-20T10:00:00Z",
    "completed_at": "2026-04-22T08:00:00Z",
    "failure_reason": null
  }
]
```

---

### Process pending payouts (Admin only)

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/payouts/process`

**Admin only.** Batch process all eligible organizer payouts. Intended to be called by a scheduled job.

#### Response `200 OK`

```json
{
  "total_checked": 25,
  "eligible_count": 8,
  "successful_payouts": 7,
  "failed_payouts": 1,
  "total_amount_paid": 4200.00,
  "errors": [
    "Organizer organizer_zzz: Stripe Connect account not verified"
  ]
}
```

---

### Manual payout (Admin only)

<span className="badge badge--post">POST</span> `/api/v1/paipalooza/payouts/manual`

**Admin only.** Trigger a payout for a specific organizer, optionally bypassing the $100 minimum threshold.

#### Request body

```json
{
  "organizer_id": "organizer_xyz",
  "override_threshold": false
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `organizer_id` | `string` | Yes | Organizer to pay out |
| `override_threshold` | `boolean` | No | If `true`, bypass the $100 minimum balance requirement |

#### Response `201 Created`

```json
{
  "payout_id": "pay_abc002",
  "organizer_id": "organizer_xyz",
  "stripe_payout_id": "po_stripe_456",
  "amount": 500.00,
  "status": "completed",
  "created_at": "2026-04-25T15:00:00Z",
  "completed_at": "2026-04-25T15:00:05Z"
}
```

#### Errors

| Code | Reason |
|---|---|
| `400` | Organizer has no pending balance |
| `403` | Admin privileges required |
