bannertrackrdocs

Developer API

Programmatic access to your BannerTrackr data

Developer API

Access your BannerTrackr data programmatically. Use the API to integrate with your own dashboards, automate workflows, or build custom integrations.

The Developer API is available on Starter, Growth, and Pro plans.

Quick Start

1. Create an API Key

  1. Go to Settings → Developer API in your dashboard
  2. Click Create Key
  3. Give your key a name and select permissions:
    • Read Only — View sites, placements, and stats
    • Read & Write — Also create, update, and delete resources
  4. Copy your key immediately — it won't be shown again

2. Make Your First Request

curl -X GET "https://bannertrackr.com/api/public/v1/me" \
  -H "Authorization: Bearer bt_live_your_key_here"

Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer bt_live_xxxxxxxxxxxxx

Keys are prefixed with bt_live_ for easy identification.

Rate Limits

Rate limits are based on your plan:

PlanRead RequestsWrite Requests
Starter60/min10/min
Growth120/min30/min
Pro300/min60/min

Rate limit headers are included in every response:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1704067200

When rate limited, you'll receive a 429 response with a Retry-After header.

Pagination

List endpoints use cursor-based pagination:

GET /api/public/v1/placements?limit=50&cursor=abc123

Response:

{
  "data": [...],
  "pagination": {
    "nextCursor": "xyz789",
    "hasMore": true
  }
}
  • limit: 1-100, default 50
  • cursor: Pass nextCursor from previous response

Endpoints

Account

Get Current User

GET /api/public/v1/me

Returns your account info, plan, and usage limits.

Response:

{
  "data": {
    "id": "user_123",
    "email": "you@example.com",
    "name": "Your Name",
    "plan": "growth",
    "createdAt": "2024-01-15T10:30:00Z",
    "usage": {
      "sites": 5,
      "sitesLimit": 10,
      "apiKeys": 2,
      "apiKeysLimit": 5
    },
    "limits": {
      "impressionsPerMonth": 500000,
      "clicksPerMonth": 50000,
      "apiRequestsPerMinute": 120
    }
  }
}

Sites

List Sites

GET /api/public/v1/sites

Query Parameters:

  • limit — Results per page (1-100, default 50)
  • cursor — Pagination cursor

Response:

{
  "data": [
    {
      "id": "site_123",
      "name": "My Newsletter",
      "type": "newsletter",
      "domain": "newsletter.example.com",
      "placementCount": 5,
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-20T14:00:00Z"
    }
  ],
  "pagination": {
    "nextCursor": null,
    "hasMore": false
  }
}

Get Site

GET /api/public/v1/sites/:id

Returns the same fields as the list response for a single site.

Create Site

POST /api/public/v1/sites

Requires: read_write scope

Body:

{
  "name": "My Newsletter",
  "type": "newsletter",
  "domain": "newsletter.example.com"
}
FieldRequiredDescription
nameYesSite name
typeYes"website" or "newsletter"
domainNoOptional domain

Update Site

PUT /api/public/v1/sites/:id

Requires: read_write scope

Body: Any fields from Create Site (all optional for updates).

Delete Site

DELETE /api/public/v1/sites/:id

Requires: read_write scope

Response:

{
  "success": true,
  "message": "Site deleted",
  "deletedPlacements": 5
}

Deleting a site also deletes all its placements and tracking data.


Placements

List Placements

GET /api/public/v1/placements

Query Parameters:

  • siteId — Filter by site
  • status — Filter by status (active, paused, expired)
  • limit — Results per page (1-100, default 50)
  • cursor — Pagination cursor

Response:

{
  "data": [
    {
      "id": "plc_123",
      "name": "Header Banner - Acme Corp",
      "pixelId": "abc123def456",
      "destinationUrl": "https://acme.com",
      "hostedImageUrl": "https://cdn.bannertrackr.com/...",
      "status": "active",
      "startDate": "2024-01-01T00:00:00Z",
      "endDate": "2024-03-31T23:59:59Z",
      "renewalDate": "2024-03-15T00:00:00Z",
      "rate": 500,
      "billingModel": "flat",
      "currency": "USD",
      "utm": {
        "source": "newsletter",
        "medium": "email",
        "campaign": "q1-2024",
        "term": null,
        "content": null
      },
      "site": {
        "id": "site_123",
        "name": "My Newsletter"
      },
      "advertiser": {
        "id": "adv_123",
        "name": "John Smith",
        "email": "john@acme.com",
        "companyName": "Acme Corp"
      },
      "trackingUrls": {
        "pixel": "https://bannertrackr.com/api/track/pixel.gif?p=abc123def456",
        "image": "https://bannertrackr.com/api/track/img/abc123def456",
        "click": "https://bannertrackr.com/api/track/click/abc123def456"
      },
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-15T00:00:00Z"
    }
  ],
  "pagination": {
    "nextCursor": null,
    "hasMore": false
  }
}

Get Placement

GET /api/public/v1/placements/:id

Returns the same fields as the list response:

{
  "data": {
    "id": "plc_123",
    "name": "Header Banner - Acme Corp",
    "pixelId": "abc123def456",
    "trackingUrls": {
      "pixel": "https://bannertrackr.com/api/track/pixel.gif?p=abc123",
      "image": "https://bannertrackr.com/api/track/img/abc123",
      "click": "https://bannertrackr.com/api/track/click/abc123"
    }
  }
}

Tracking URL Types:

  • pixel — 1x1 transparent GIF for impression tracking (use in <img> tags)
  • image — Serves your hosted image while tracking impressions (for newsletters)
  • click — Click tracking with redirect to destination URL (wrap your links)

Create Placement

POST /api/public/v1/placements

Requires: read_write scope

Body:

{
  "siteId": "site_123",
  "name": "Header Banner",
  "destinationUrl": "https://example.com",
  "status": "active",
  "startDate": "2024-01-01",
  "endDate": "2024-03-31",
  "renewalDate": "2024-03-15",
  "rate": 500,
  "billingModel": "flat",
  "currency": "USD",
  "advertiserId": "adv_123",
  "utm": {
    "source": "newsletter",
    "medium": "email",
    "campaign": "spring-2024",
    "term": "banner",
    "content": "header"
  }
}
FieldRequiredDescription
siteIdYesID of the site this placement belongs to
nameYesPlacement name
destinationUrlNoWhere clicks redirect to
statusNo"active" or "paused" (default: "active")
startDateNoCampaign start date (ISO 8601)
endDateNoCampaign end date (ISO 8601)
renewalDateNoRenewal reminder date (ISO 8601)
rateNoBilling rate (e.g., 500)
billingModelNo"flat", "cpm", "cpc", or "trade"
currencyNoCurrency code (default: "USD")
advertiserIdNoLink to an advertiser (must exist in your account)
utmNoUTM parameters to append to click URLs

The advertiserId must reference an advertiser you've already created in your dashboard. Advertisers cannot be created via the API.

Update Placement

PUT /api/public/v1/placements/:id

Requires: read_write scope

Body: Any fields from Create Placement except siteId (all optional). Set a field to null to clear it.

Delete Placement

DELETE /api/public/v1/placements/:id

Requires: read_write scope

Response:

{
  "success": true,
  "message": "Placement deleted"
}

Statistics

Get Placement Stats

GET /api/public/v1/placements/:id/stats

Query Parameters:

  • from — Start date (YYYY-MM-DD), default: 30 days ago
  • to — End date (YYYY-MM-DD), default: today
  • breakdowns — Include device/country breakdowns (true/false)

Response:

{
  "data": {
    "placementId": "plc_123",
    "placementName": "Header Banner",
    "period": {
      "from": "2024-01-01",
      "to": "2024-01-31"
    },
    "summary": {
      "impressions": 50000,
      "clicks": 1500,
      "uniqueVisitors": 35000,
      "ctr": 3.0
    },
    "daily": [
      {
        "date": "2024-01-01",
        "impressions": 1200,
        "clicks": 36,
        "uniqueVisitors": 900,
        "ctr": 3.0
      }
    ],
    "breakdowns": {
      "byDevice": [
        { "device": "desktop", "impressions": 30000, "clicks": 900 },
        { "device": "mobile", "impressions": 18000, "clicks": 540 },
        { "device": "tablet", "impressions": 2000, "clicks": 60 }
      ],
      "byCountry": [
        { "country": "US", "impressions": 25000, "clicks": 750 },
        { "country": "GB", "impressions": 10000, "clicks": 300 }
      ]
    }
  }
}

The breakdowns field is only included when breakdowns=true is passed. Country breakdown returns the top 10 countries by impressions.


Error Handling

All errors return a consistent format:

{
  "error": "Error message here",
  "hint": "Optional helpful suggestion"
}

HTTP Status Codes

CodeDescription
200Success
201Created
400Bad request (validation error)
401Unauthorized (invalid/missing key)
403Forbidden (wrong scope or plan limit)
404Not found
429Rate limit exceeded
500Server error

Limit Exceeded Errors

When you hit a plan limit (sites, placements, etc.), you'll receive a 403 response with additional details:

{
  "error": "Site limit reached",
  "limit": 3,
  "current": 3,
  "upgradeUrl": "/pricing"
}

Scope Errors

If your API key doesn't have write permissions:

{
  "error": "This API key is read-only",
  "hint": "Create a key with read_write scope to perform this operation"
}

Code Examples

JavaScript / Node.js

const API_KEY = 'bt_live_your_key_here';
const BASE_URL = 'https://bannertrackr.com/api/public/v1';

async function getStats(placementId, from, to) {
  const params = new URLSearchParams({ from, to, breakdowns: 'true' });

  const response = await fetch(
    `${BASE_URL}/placements/${placementId}/stats?${params}`,
    {
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
      },
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || `API error: ${response.status}`);
  }

  return response.json();
}

// Usage
const stats = await getStats('plc_123', '2024-01-01', '2024-01-31');
console.log(`CTR: ${stats.data.summary.ctr}%`);

Python

import requests

API_KEY = 'bt_live_your_key_here'
BASE_URL = 'https://bannertrackr.com/api/public/v1'

def get_stats(placement_id, from_date, to_date):
    response = requests.get(
        f'{BASE_URL}/placements/{placement_id}/stats',
        headers={'Authorization': f'Bearer {API_KEY}'},
        params={
            'from': from_date,
            'to': to_date,
            'breakdowns': 'true'
        }
    )
    response.raise_for_status()
    return response.json()

# Usage
stats = get_stats('plc_123', '2024-01-01', '2024-01-31')
print(f"CTR: {stats['data']['summary']['ctr']}%")

Managing API Keys

  • Create up to 5 active keys per account
  • Revoke keys immediately from Settings → Developer API
  • Keys are scoped: use Read Only when you only need to fetch data
  • If you downgrade to the Free plan, all keys are automatically revoked

Use separate keys for different environments (production, staging, CI/CD).

On this page