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:
Authorization: Bearer your_api_key_hereRequests 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#
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#
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/campaigns | List all campaigns |
GET | /v1/campaigns/:id | Get a specific campaign |
POST | /v1/campaigns | Create a new campaign |
PATCH | /v1/campaigns/:id | Update a campaign |
DELETE | /v1/campaigns/:id | Delete a draft campaign |
Proposals#
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/proposals | List all proposals |
GET | /v1/proposals/:id | Get a specific proposal |
POST | /v1/proposals | Create a new proposal |
PATCH | /v1/proposals/:id | Update a proposal |
Platforms#
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/platforms | List available platforms |
GET | /v1/platforms/:id | Get platform details and inventory |
GET | /v1/platforms/search | Search platforms by market, format, and audience |
Creatives#
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/creatives | List all creatives |
GET | /v1/creatives/:id | Get a specific creative |
POST | /v1/creatives | Upload or generate a creative |
DELETE | /v1/creatives/:id | Delete a creative |
Example Requests#
List Your Campaigns#
Retrieve all campaigns for your account, with optional filtering by status:
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:
{
"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:
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:
{
"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:
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:
{
"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.
| Plan | Rate Limit | Burst Limit |
|---|---|---|
| Standard | 100 requests/minute | 20 requests/second |
| Professional | 300 requests/minute | 50 requests/second |
| Enterprise | Custom | Custom |
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:
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#
| Event | Description |
|---|---|
proposal.accepted | A publisher accepted your proposal |
proposal.rejected | A publisher declined your proposal |
proposal.countered | A publisher sent a counter-offer |
campaign.activated | Your campaign has gone live |
campaign.completed | Your campaign has finished running |
creative.approved | A publisher approved your creative |
creative.rejected | A 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#
{
"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:
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:
{
"error": {
"code": "validation_error",
"message": "Invalid flight dates: startDate must be in the future.",
"field": "lineItems[0].flightStart"
}
}Common Error Codes#
| Status | Code | Description |
|---|---|---|
400 | validation_error | Request body failed validation |
401 | unauthorized | Missing or invalid API key |
403 | forbidden | API key lacks required permissions |
404 | not_found | Resource does not exist |
409 | conflict | Resource state conflict (e.g., editing an accepted proposal) |
429 | rate_limited | Too many requests |
500 | internal_error | Server error — retry with backoff |
Best Practices for Error Handling#
- Always check the status code before parsing the response body
- Log error responses including the full error object for debugging
- Retry on 5xx errors with exponential backoff (up to 3 retries)
- Do not retry on 4xx errors — these indicate a problem with your request that must be fixed before resending
- Handle
409 Conflictgracefully — fetch the latest resource state and reconcile
Pagination#
List endpoints return paginated results. Use the limit and offset query parameters to page through results:
# First page
GET /v1/campaigns?limit=20&offset=0
# Second page
GET /v1/campaigns?limit=20&offset=20Every paginated response includes a pagination object:
{
"pagination": {
"total": 47,
"limit": 20,
"offset": 20
}
}Integration Best Practices#
- Use environment variables for API keys — never hardcode them
- Implement idempotency — Use the
Idempotency-Keyheader onPOSTrequests 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-Remainingheader 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.