NORMA Ads API (1.0.0)

Download OpenAPI specification:

The NORMA Ads API enables programmatic campaign management and reporting for sports bettor push notification inventory.

NORMA delivers ads at the exact moment users have active financial stakes in live games — generating 12–18% CTR on high-intent moments like Bet Resolved, Overtime, and Spread Alert.

This API is designed for use by AI advertising agents and DSP integrations. It supports OAuth 2.0 Client Credentials flow and is MCP-compatible via the norma-ads-mcp server package.

MCP Server: https://mcp.getnorma.app Developer Docs: https://getnorma.app/developers Agent Discovery: https://getnorma.app/adagents.json

Authentication

OAuth 2.0 Client Credentials token issuance and JWKS discovery

Issue OAuth 2.0 access token

Implements RFC 6749 §4.4 Client Credentials grant. Returns a short-lived RS256-signed JWT bearer token. Rate limited to 10 requests/minute per IP.

Request Body schema: application/x-www-form-urlencoded
required
grant_type
required
string
Value: "client_credentials"
client_id
required
string
client_secret
required
string
scope
string

Space-separated list of requested scopes

Responses

Request samples

Content type
application/x-www-form-urlencoded
grant_type=client_credentials&client_id=norma_client_abc123&client_secret=base64url_secret_here&scope=campaigns%3Aread%20campaigns%3Awrite%20reporting%3Aread

Response samples

Content type
application/json
{
  • "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "token_type": "Bearer",
  • "expires_in": 3600,
  • "scope": "campaigns:read campaigns:write reporting:read"
}

Get JWKS public keys

Returns the RSA public key set used to verify NORMA-issued JWTs.

Responses

Response samples

Content type
application/json
{
  • "keys": [
    ]
}

Inventory

Moment types, supply forecasts, and market pricing data

List all moment types

Returns all 11 NORMA moment types with floor CPM prices and typical CTR ranges. This endpoint is public (no authentication required) and cached for 1 hour.

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Get market pricing data

Returns floor prices and clearing price percentiles (p25/p50/p75/p90) for all moment types. Data is privacy-safe (no individual bids exposed) and cached for 15 minutes. Useful for bid strategy optimization.

Authorizations:
oauth2apiKey

Responses

Response samples

Content type
application/json
{
  • "as_of": "2019-08-24T14:15:22Z",
  • "moment_types": [
    ]
}

Campaigns

Create, read, update, and control ad campaigns

List campaigns

Returns all campaigns for the authenticated advertiser, newest first.

Authorizations:
oauth2apiKey
query Parameters
status
string
Enum: "active" "paused" "ended" "cancelled"

Filter by campaign status

page
integer >= 1
Default: 1
per_page
integer [ 1 .. 100 ]
Default: 20

Responses

Response samples

Content type
application/json
{
  • "campaigns": [
    ],
  • "pagination": {
    }
}

Create a campaign

Creates a new ad campaign with an initial creative. The campaign is set to active status immediately. The icon_url must be a publicly reachable HTTPS URL (verified via HEAD request at creation time). Bid CPM must meet or exceed the floor price for all selected moment types.

Authorizations:
oauth2apiKey
Request Body schema: application/json
required
name
required
string
moment_types
required
Array of strings non-empty
Items Enum: "bet_resolved" "close_game" "overtime" "spread_alert" "moneyline_alert" "total_alert" "prop_alert" "position_alert" "foul_trouble" "follow_alert" "prediction_resolved"
sports
required
Array of strings non-empty
Items Enum: "ncaa_basketball" "nba" "nfl" "mlb"
bid_cpm_usd
required
number <float>

CPM bid in USD — must meet or exceed floor for all selected moment types

daily_budget_usd
required
number <float> >= 5
total_budget_usd
required
number <float> >= 10
start_date
required
string <date>

Campaign start date (must be >= today)

end_date
string or null <date>

Campaign end date (must be after start_date)

target_cpa_usd
number or null <float>

Enables CPA auto-bidding via Thompson Sampling

postback_url
string or null <uri>
required
object (CreativeInput)

Responses

Request samples

Content type
application/json
{
  • "name": "DraftKings March Madness",
  • "moment_types": [
    ],
  • "sports": [
    ],
  • "bid_cpm_usd": 0.8,
  • "daily_budget_usd": 100,
  • "total_budget_usd": 1000,
  • "start_date": "2025-03-01",
  • "end_date": "2025-04-07",
  • "creative": {}
}

Response samples

Content type
application/json
{
  • "id": "101",
  • "status": "active",
  • "estimated_daily_impressions": 3200,
  • "estimated_daily_spend_usd": 2.56,
  • "created_at": "2025-03-01T00:00:00Z"
}

Get campaign details

Returns full campaign detail including the primary creative.

Authorizations:
oauth2apiKey
path Parameters
id
required
string
Example: 101

Responses

Response samples

Content type
application/json
{
  • "id": "101",
  • "name": "DraftKings March Madness",
  • "status": "active",
  • "moment_types": [
    ],
  • "sports": [
    ],
  • "bid_cpm_usd": 0.8,
  • "daily_budget_usd": 100,
  • "total_budget_usd": 1000,
  • "target_cpa_usd": 25,
  • "start_date": "2025-03-01",
  • "end_date": "2025-04-01",
  • "spend_to_date_usd": 42.5,
  • "impressions_to_date": 8500,
  • "creative": {},
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update a campaign

Updates mutable campaign fields. The following fields are immutable after creation and cannot be changed: moment_types, sports, start_date, creative. Bid changes propagate to the bids table synchronously.

Authorizations:
oauth2apiKey
path Parameters
id
required
string
Example: 101
Request Body schema: application/json
required
name
string
bid_cpm_usd
number <float>
daily_budget_usd
number <float>
total_budget_usd
number <float>
target_cpa_usd
number or null <float>
end_date
string or null <date>
status
string
Enum: "active" "paused" "ended"
postback_url
string or null <uri>

Responses

Request samples

Content type
application/json
{
  • "bid_cpm_usd": 0.95,
  • "daily_budget_usd": 150
}

Response samples

Content type
application/json
{
  • "id": "101",
  • "name": "DraftKings March Madness",
  • "status": "active",
  • "moment_types": [
    ],
  • "sports": [
    ],
  • "bid_cpm_usd": 0.8,
  • "daily_budget_usd": 100,
  • "total_budget_usd": 1000,
  • "target_cpa_usd": 25,
  • "start_date": "2025-03-01",
  • "end_date": "2025-04-01",
  • "spend_to_date_usd": 42.5,
  • "impressions_to_date": 8500,
  • "creative": {},
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Pause a campaign

Transitions an active campaign to paused status. No-ops if already paused.

Authorizations:
oauth2apiKey
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "paused": true,
  • "id": "101"
}

Resume a paused campaign

Transitions a paused campaign back to active. Fails if budget is exhausted.

Authorizations:
oauth2apiKey
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "resumed": true,
  • "id": "101"
}

Add A/B creative variant

Adds a new creative variant to an existing campaign for A/B testing. Traffic is split equally across all approved variants. Variants are labeled variant_a, variant_b, variant_c, etc. automatically.

Authorizations:
oauth2apiKey
path Parameters
id
required
string
Request Body schema: application/json
required
headline
required
string <= 60 characters
body
required
string <= 120 characters
icon_url
required
string <uri>

Must be a publicly reachable HTTPS URL

action_url
required
string <uri>
cta_text
string or null <= 20 characters

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "43",
  • "variant_label": "variant_b",
  • "traffic_allocation": 0.5
}

Reporting

Campaign performance metrics, timeseries, and creative analytics

Get market pricing data

Returns floor prices and clearing price percentiles (p25/p50/p75/p90) for all moment types. Data is privacy-safe (no individual bids exposed) and cached for 15 minutes. Useful for bid strategy optimization.

Authorizations:
oauth2apiKey

Responses

Response samples

Content type
application/json
{
  • "as_of": "2019-08-24T14:15:22Z",
  • "moment_types": [
    ]
}

Get campaign performance report

Returns aggregated performance metrics for a campaign over a date range. Optional breakdown by moment_type or creative for granular analysis. Rate limited to 60 requests/minute per advertiser.

Authorizations:
oauth2apiKey
path Parameters
id
required
string
query Parameters
start_date
required
string <date>
Example: start_date=2025-03-01
end_date
required
string <date>
Example: end_date=2025-03-31
breakdown
string
Enum: "day" "moment_type" "sport" "creative" "hour_of_day"
Example: breakdown=moment_type
timezone
string
Default: "UTC"
Example: timezone=America/New_York

Responses

Response samples

Content type
application/json
{
  • "campaign_id": "101",
  • "period": {
    },
  • "totals": {
    }
}

Get campaign performance timeseries

Returns impression, click, and spend data over time. Use granularity=day for daily aggregates (reads from materialized view, fast) or granularity=hour for hourly data (reads raw impressions, slower).

Authorizations:
oauth2apiKey
path Parameters
id
required
string
query Parameters
start_date
required
string <date>
end_date
required
string <date>
granularity
string
Default: "day"
Enum: "hour" "day"

Responses

Response samples

Content type
application/json
{
  • "campaign_id": "101",
  • "granularity": "hour",
  • "series": [
    ]
}

Get creative performance

Returns impression, click, and conversion metrics for a specific creative.

Authorizations:
oauth2apiKey
path Parameters
id
required
string
Example: 42

Responses

Response samples

Content type
application/json
{
  • "creative_id": "42",
  • "impressions": 2100,
  • "clicks": 315,
  • "ctr": 0.15,
  • "conversions": 12,
  • "spend_usd": 157.5,
  • "traffic_allocation": 0.5
}

Webhooks

Register and manage webhook endpoints for real-time event delivery

List webhook endpoints

Authorizations:
oauth2apiKey

Responses

Response samples

Content type
application/json
{
  • "endpoints": [
    ]
}

Register a webhook endpoint

Registers a URL to receive NORMA event notifications. A 32-byte hex signing secret is generated and returned once — store it immediately. Use the X-Norma-Signature header (sha256=<hex>) to verify delivery.

NORMA signs with HMAC-SHA256 using the shared secret:

X-Norma-Signature: sha256=<hmac_hex>
Authorizations:
oauth2apiKey
Request Body schema: application/json
required
url
required
string <uri>
events
required
Array of strings (WebhookEvent) non-empty
Items Enum: "impression.served" "click.recorded" "conversion.recorded" "campaign.budget_50pct" "campaign.budget_90pct" "campaign.ended" "campaign.bid_adjusted"
batch_impressions
boolean
Default: false

Batch impression.served events instead of sending one per impression

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  • "events": [
    ],
  • "is_active": true,
  • "batch_impressions": true,
  • "failure_count": 0,
  • "last_delivered_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "secret": "a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
  • "warning": "Save this secret — it will not be shown again. Use it to verify X-Norma-Signature headers."
}

Deactivate a webhook endpoint

Sets the endpoint to inactive. Delivery will stop. Cannot be re-activated via API.

Authorizations:
oauth2apiKey
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "deactivated": true,
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
}

Send a test event to a webhook

Fires a conversion.recorded test event to the endpoint. Useful for verifying connectivity and signature verification before going live.

Authorizations:
oauth2apiKey
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "status_code": 200,
  • "duration_ms": 87,
  • "message": "Test event delivered successfully"
}

Postbacks

Inbound conversion signals from mobile attribution partners

Record a conversion (GET)

Records a conversion event for a click that originated from a NORMA ad. The GET method is standard for mobile attribution partners who use URL macros in postback URLs.

Deduplication is enforced: if converted=true is already set on the click, returns { status: "already_recorded" }. Use idempotency_key to safely retry without creating duplicates.

Conversions outside the 7-day attribution window are rejected.

query Parameters
campaign_id
required
string
Example: campaign_id=101
click_id
required
string
Example: click_id=clk_abc123def456
event_type
required
string
Enum: "install" "registration" "deposit" "purchase" "custom"
Example: event_type=deposit
event_value_usd
number <float>
Example: event_value_usd=100
event_name
string
Example: event_name=first_bet
idempotency_key
string
Example: idempotency_key=dep_xyz789

Responses

Response samples

Content type
application/json
Example
{
  • "status": "recorded"
}

Record a conversion (POST)

Records a conversion event via JSON body. Equivalent to the GET endpoint but accepts a JSON body instead of query parameters. Both formats are supported for maximum partner compatibility.

Request Body schema: application/json
required
campaign_id
required
string

Campaign ID the click belongs to

click_id
required
string

NORMA-issued click identifier from the ad deep link

event_type
required
string
Enum: "install" "registration" "deposit" "purchase" "custom"
event_value_usd
number or null <float>

Monetary value of the conversion event

event_name
string or null

Required when event_type is custom

idempotency_key
string or null

Prevents duplicate recording of the same conversion

Responses

Request samples

Content type
application/json
{
  • "campaign_id": "101",
  • "click_id": "clk_abc123def456",
  • "event_type": "deposit",
  • "event_value_usd": 100,
  • "idempotency_key": "dep_xyz789"
}

Response samples

Content type
application/json
{
  • "status": "recorded"
}