Search Guide

Search all guide articles

Using the Tap API

API keys, programmatic access, and developer integration with the Tap platform.

Overview#

The Tap API gives you programmatic access to campaigns, proposals, platforms, and creatives. Whether you are building a custom integration, automating campaign workflows, or syncing data with your internal tools, the API provides a straightforward REST interface with JSON payloads.

This guide covers authentication, core endpoints, example requests, and best practices for building reliable integrations.

Getting Your API Key#

Open Account Settings

Log in to Tap and navigate to Settings from the sidebar. Select the API tab.

Generate a new key

Click Generate API Key. Give it a descriptive name (e.g., "CRM Integration" or "Reporting Dashboard") so you can identify its purpose later.

Copy and store securely

Your API key will be displayed once. Copy it immediately and store it in a secure location — a password manager, secrets vault, or environment variable. You will not be able to view the full key again.

Set permissions (optional)

If your account supports scoped keys, select only the permissions your integration needs. For example, a reporting tool only needs read access to campaigns and proposals — it does not need write access to creatives.

Keep your API keys secure. Never commit them to source control, embed them in client-side code, or share them in plain text. If you suspect a key has been compromised, revoke it immediately from your Account Settings and generate a new one.

Authentication#

All API requests must include your API key in the Authorization header using the Bearer scheme:

bash
Authorization: Bearer your_api_key_here

Requests without a valid key will receive a 401 Unauthorized response. Requests with a valid key that lacks permission for the requested resource will receive a 403 Forbidden response.

Example Authenticated Request#

bash
curl -X GET https://api.tap.ad/v1/campaigns \
  -H "Authorization: Bearer tap_live_abc123def456" \
  -H "Content-Type: application/json"

Core API Endpoints#

The Tap API is organized around the main resources you work with on the platform.

Campaigns#

MethodEndpointDescription
GET/v1/campaignsList all campaigns
GET/v1/campaigns/:idGet a specific campaign
POST/v1/campaignsCreate a new campaign
PATCH/v1/campaigns/:idUpdate a campaign
DELETE/v1/campaigns/:idDelete a draft campaign

Proposals#

MethodEndpointDescription
GET/v1/proposalsList all proposals
GET/v1/proposals/:idGet a specific proposal
POST/v1/proposalsCreate a new proposal
PATCH/v1/proposals/:idUpdate a proposal

Platforms#

MethodEndpointDescription
GET/v1/platformsList available platforms
GET/v1/platforms/:idGet platform details and inventory
GET/v1/platforms/searchSearch platforms by market, format, and audience

Creatives#

MethodEndpointDescription
GET/v1/creativesList all creatives
GET/v1/creatives/:idGet a specific creative
POST/v1/creativesUpload or generate a creative
DELETE/v1/creatives/:idDelete a creative

Example Requests#

List Your Campaigns#

Retrieve all campaigns for your account, with optional filtering by status:

bash
curl -X GET "https://api.tap.ad/v1/campaigns?status=active&limit=20" \
  -H "Authorization: Bearer tap_live_abc123def456" \
  -H "Content-Type: application/json"

Response:

json
{
  "data": [
    {
      "id": "camp_8x7k2m",
      "name": "Q2 Brand Awareness - Dallas",
      "status": "active",
      "budget": 25000,
      "currency": "USD",
      "startDate": "2026-04-01",
      "endDate": "2026-04-28",
      "markets": ["Dallas-Fort Worth"],
      "createdAt": "2026-03-15T10:30:00Z"
    },
    {
      "id": "camp_3p9n1v",
      "name": "Spring Podcast Push",
      "status": "active",
      "budget": 12000,
      "currency": "USD",
      "startDate": "2026-03-20",
      "endDate": "2026-05-15",
      "markets": ["Los Angeles", "San Francisco"],
      "createdAt": "2026-03-10T14:22:00Z"
    }
  ],
  "pagination": {
    "total": 2,
    "limit": 20,
    "offset": 0
  }
}

Create a Proposal#

Submit a new proposal to a platform with line item details:

bash
curl -X POST https://api.tap.ad/v1/proposals \
  -H "Authorization: Bearer tap_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "camp_8x7k2m",
    "platformId": "plat_radio_kdfw",
    "lineItems": [
      {
        "format": "audio_30s",
        "quantity": 40,
        "flightStart": "2026-04-01",
        "flightEnd": "2026-04-28",
        "dayparts": ["morning_drive", "evening_drive"],
        "targetAudience": "adults_25_54"
      }
    ],
    "notes": "Looking for morning and evening drive time placement. Flexible on exact time slots."
  }'

Response:

json
{
  "data": {
    "id": "prop_4m2k8x",
    "campaignId": "camp_8x7k2m",
    "platformId": "plat_radio_kdfw",
    "status": "pending",
    "lineItems": [
      {
        "id": "li_9v3n1p",
        "format": "audio_30s",
        "quantity": 40,
        "flightStart": "2026-04-01",
        "flightEnd": "2026-04-28",
        "estimatedCost": 6800,
        "currency": "USD"
      }
    ],
    "createdAt": "2026-03-19T09:15:00Z"
  }
}

Search for Platforms#

Find available inventory by market and media type:

bash
curl -X GET "https://api.tap.ad/v1/platforms/search?market=Chicago&type=podcast&audience=adults_18_49" \
  -H "Authorization: Bearer tap_live_abc123def456" \
  -H "Content-Type: application/json"

Response:

json
{
  "data": [
    {
      "id": "plat_pod_chitalk",
      "name": "Chicago Talk Daily",
      "type": "podcast",
      "market": "Chicago",
      "formats": ["audio_30s", "audio_60s", "host_read"],
      "audience": {
        "demographic": "adults_18_49",
        "estimatedReach": 45000
      },
      "pricing": {
        "audio_30s": { "cpm": 22.50 },
        "audio_60s": { "cpm": 35.00 },
        "host_read": { "cpm": 48.00 }
      }
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 20,
    "offset": 0
  }
}

Rate Limiting#

The Tap API enforces rate limits to ensure platform stability for all users.

PlanRate LimitBurst Limit
Standard100 requests/minute20 requests/second
Professional300 requests/minute50 requests/second
EnterpriseCustomCustom

When you exceed the rate limit, the API returns a 429 Too Many Requests response with the following headers:

X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1711008060

Handling Rate Limits#

Implement exponential backoff in your integration:

javascript
async function apiRequest(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const resetTime = response.headers.get("X-RateLimit-Reset");
      const waitMs = resetTime
        ? (parseInt(resetTime) * 1000) - Date.now()
        : Math.pow(2, attempt) * 1000;

      await new Promise((resolve) => setTimeout(resolve, waitMs));
      continue;
    }

    return response;
  }

  throw new Error("Max retries exceeded");
}

Webhook Integration#

Webhooks let you receive real-time notifications when events occur on Tap, instead of polling the API for changes.

Supported Events#

EventDescription
proposal.acceptedA publisher accepted your proposal
proposal.rejectedA publisher declined your proposal
proposal.counteredA publisher sent a counter-offer
campaign.activatedYour campaign has gone live
campaign.completedYour campaign has finished running
creative.approvedA publisher approved your creative
creative.rejectedA publisher rejected your creative with feedback

Setting Up Webhooks#

Register your endpoint

In Settings > API > Webhooks, click Add Endpoint. Enter the URL where Tap should send event notifications. This must be an HTTPS endpoint that returns a 200 response within 5 seconds.

Select events

Choose which events you want to receive. Start with just the events your integration needs — you can add more later.

Copy the signing secret

Tap signs every webhook payload with a secret key. Copy this secret and use it to verify that incoming requests are genuinely from Tap.

Webhook Payload Format#

json
{
  "id": "evt_7k2m8x3p",
  "type": "proposal.accepted",
  "createdAt": "2026-03-19T14:30:00Z",
  "data": {
    "proposalId": "prop_4m2k8x",
    "campaignId": "camp_8x7k2m",
    "platformName": "KDFW Radio Dallas"
  }
}

Verifying Webhook Signatures#

Always verify the signature before processing a webhook:

javascript
import crypto from "crypto";

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Error Handling#

The API uses standard HTTP status codes. All error responses follow a consistent format:

json
{
  "error": {
    "code": "validation_error",
    "message": "Invalid flight dates: startDate must be in the future.",
    "field": "lineItems[0].flightStart"
  }
}

Common Error Codes#

StatusCodeDescription
400validation_errorRequest body failed validation
401unauthorizedMissing or invalid API key
403forbiddenAPI key lacks required permissions
404not_foundResource does not exist
409conflictResource state conflict (e.g., editing an accepted proposal)
429rate_limitedToo many requests
500internal_errorServer error — retry with backoff

Best Practices for Error Handling#

  1. Always check the status code before parsing the response body
  2. Log error responses including the full error object for debugging
  3. Retry on 5xx errors with exponential backoff (up to 3 retries)
  4. Do not retry on 4xx errors — these indicate a problem with your request that must be fixed before resending
  5. Handle 409 Conflict gracefully — fetch the latest resource state and reconcile

Pagination#

List endpoints return paginated results. Use the limit and offset query parameters to page through results:

bash
# First page
GET /v1/campaigns?limit=20&offset=0

# Second page
GET /v1/campaigns?limit=20&offset=20

Every paginated response includes a pagination object:

json
{
  "pagination": {
    "total": 47,
    "limit": 20,
    "offset": 20
  }
}

Integration Best Practices#

  • Use environment variables for API keys — never hardcode them
  • Implement idempotency — Use the Idempotency-Key header on POST requests to prevent duplicate resource creation
  • Cache platform data — Platform and inventory details change infrequently. Cache search results for 15-30 minutes to reduce API calls
  • Monitor your usage — Check the X-RateLimit-Remaining header to stay within limits
  • Use webhooks over polling — Webhooks are more efficient and provide faster notifications than polling endpoints on a schedule

Need help with your integration? Contact the Tap developer support team at developers@tap.ad or visit the API reference documentation for full endpoint details.