Skip to main content
POST
/
api
/
v1
/
ssp
/
bid-request
{
  "userId": "user_123",
  "chatId": "chat_456",
  "messages": [
    {
      "role": "user",
      "content": "Looking for running shoes",
      "timestamp": "2025-11-23T10:00:00Z"
    },
    {
      "role": "assistant",
      "content": "What's your budget?",
      "timestamp": "2025-11-23T10:00:15Z"
    }
  ],
  "config": {
    "ad_offset": 3,
    "max_frequency": 5,
    "max_headline_chars": 60,
    "experiment_tag": "variant_a"
  },
  "turn_number": 4,
  "turns_from_last_ad": 6,
  "force": false
}
{
  "requestId": "api_req_123",
  "timestamp": "2025-11-24T21:51:52.240297Z",
  "totalTime": 0.123,
  "status": "success",
  "message": "Bid successful",
  "data": {
    "bid": {
      "ad_format": "sponsored_message",
      "price": 5.50,
      "advertiser": "Nike",
      "domain": "nike.com",
      "headline": "Premium Running Shoes",
      "description": "Perfect for marathon training. Lightweight and comfortable.",
      "cta_text": "Shop Now",
      "url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=abc123",
      "image_url": "https://cdn.example.com/nike-shoes.jpg",
      "dsp": "thrad_dsp",
      "bidId": "bid_abc123"
    }
  },
  "error": null
}

Authorizations

thrad-api-key
string
required
Your API key for authentication. The key’s environment (staging or production) determines how ads are served — see Staging vs. Production.
Content-Type
string
required
Must be application/json

Geolocation Headers

The server resolves the user’s location from request headers. These are optional but strongly recommended — sending them skips the server-side IP lookup and reduces latency.
X-User-Country
string
ISO 3166-1 alpha-2 country code (e.g. "US", "DE"). When present, X-User-Region, X-User-City, and X-User-Timezone are also read.
X-User-Region
string
Region or state (e.g. "CA", "Bavaria").
X-User-City
string
City name (e.g. "San Francisco").
X-User-Timezone
string
IANA timezone (e.g. "America/Los_Angeles").
If none of the above headers are sent, the server falls back to IP-based geolocation using X-Forwarded-For. For direct calls (browser or mobile app → our API), this is handled automatically by the load balancer. If you’re proxying requests through your own backend, you must forward the user’s IP via X-Forwarded-For — otherwise the server sees your server’s IP and geo will be wrong.
If unsure about which headers to send, contact marco@thrads.ai and we’ll help you set it up.

Body

userId
string
required
Unique, anonymous user identifier (e.g. a UUID like user_a1b2c3d4-...). Must be stable across sessions for the same user. Do not use email, name, or other PII. On web, store in localStorage; on mobile, use the platform’s persistent storage (e.g. UserDefaults on iOS, SharedPreferences on Android).
chatId
string
contextual only Conversation identifier. One unique ID per conversation, not per user. Reset when user starts a new chat. Required when request_type is "contextual".
messages
array
contextual only Conversation history. Required when request_type is "contextual". Each message must have a role ("user" or "assistant") and content. PII (names, emails, addresses, etc.) should be masked or removed before sending.
config
object
Publisher configuration for pacing, creative constraints, and analytics. All fields are optional — omitted fields fall back to defaults set on your chatbot in the platform dashboard.
request_type
string
default:"contextual"
Pipeline selector. Determines which ad pipeline runs and what fields are required.
  • "contextual" (default) — in-chat ads. Requires chatId and messages.
  • "opener" — pre-chat ads (e.g. sponsored prompts, banners). chatId and messages are not required.
ad_formats
array
Ad formats the publisher can render. The server picks the best match from this list.
  • Contextual: ["sponsored_message"] (default). More formats coming soon — see Ad Formats & Rendering.
  • Opener: ["sponsored_message"], ["sponsored_prompt"], or both. Defaults to ["sponsored_prompt"] when omitted.
turn_number
integer
contextual only Current turn number in the conversation. Used for ad offset pacing and analytics. Must be >= 0. Only needed if you’re not calling the endpoint on every user-assistant exchange — if you are, the system derives this from the message count.
turns_from_last_ad
integer
contextual only Number of turns since the last ad was shown in this conversation. Used by the max frequency check. Only needed if you’re not calling the endpoint on every user-assistant exchange — if you are, the system tracks this automatically.
force
boolean
default:"false"
When true, the system serves an ad as a last resort even if the conversation context doesn’t strongly match any campaign. Hard filters (banned content, geo targeting, budget caps) still apply — force never overrides safety or budget constraints.We recommend starting without force (the default) and only enabling it if your fill rate is too low. See Maximising Fill Rate for guidance.
{
  "userId": "user_123",
  "chatId": "chat_456",
  "messages": [
    {
      "role": "user",
      "content": "Looking for running shoes",
      "timestamp": "2025-11-23T10:00:00Z"
    },
    {
      "role": "assistant",
      "content": "What's your budget?",
      "timestamp": "2025-11-23T10:00:15Z"
    }
  ],
  "config": {
    "ad_offset": 3,
    "max_frequency": 5,
    "max_headline_chars": 60,
    "experiment_tag": "variant_a"
  },
  "turn_number": 4,
  "turns_from_last_ad": 6,
  "force": false
}
{
  "requestId": "api_req_123",
  "timestamp": "2025-11-24T21:51:52.240297Z",
  "totalTime": 0.123,
  "status": "success",
  "message": "Bid successful",
  "data": {
    "bid": {
      "ad_format": "sponsored_message",
      "price": 5.50,
      "advertiser": "Nike",
      "domain": "nike.com",
      "headline": "Premium Running Shoes",
      "description": "Perfect for marathon training. Lightweight and comfortable.",
      "cta_text": "Shop Now",
      "url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=abc123",
      "image_url": "https://cdn.example.com/nike-shoes.jpg",
      "dsp": "thrad_dsp",
      "bidId": "bid_abc123"
    }
  },
  "error": null
}
A 200 OK response with "bid": null is not an error - it means the auction ran successfully but no DSP submitted a winning bid. This is normal behavior.

Response

requestId
string
Unique request identifier used in logs and tracing.
timestamp
string
ISO 8601 timestamp of when the response was generated.
totalTime
float
Total processing time for this request in seconds.
status
string
Response status: "success" or "error".
message
string
Human-readable message describing the result.
data
object
Response data payload.
error
object|null
Error details when status = "error" (otherwise null).

Status Codes

Status CodeMeaningScenario
200 OKSuccessAuction ran successfully (with or without winning bid), or pacing blocked
400 Bad RequestInvalid inputMalformed request body or missing required fields
401 UnauthorizedAuthentication failedMissing or invalid thrad-api-key
403 ForbiddenOrigin not allowedRequest from non-whitelisted domain (browser integration only)
429 Too Many RequestsRate limit exceededPublisher exceeded request quota
500 Internal Server ErrorServer errorDSP render failed, validation error, or internal exception

Experiments

Beta — available to all publishers.
Use config.experiment_tag to run A/B tests on your integration. Tag each request with a label (max 30 characters) and the tag is recorded on every impression. You can then compare performance metrics (impressions, clicks, CTR, revenue) across experiment variants in your dashboard. Example: test whether showing ads after turn 3 vs turn 5 performs better.
// Variant A: ads after turn 3
{
  "config": {
    "ad_offset": 3,
    "experiment_tag": "offset_3"
  }
}

// Variant B: ads after turn 5
{
  "config": {
    "ad_offset": 5,
    "experiment_tag": "offset_5"
  }
}
Split your traffic between the two variants and compare offset_3 vs offset_5 in your dashboard analytics. You can use experiment tags to test any parameter — ad offset, max frequency, headline length, image vs no-image, or anything else in your integration logic.