Skip to main content
POST
/
api
/
v1
/
ad
curl -X POST https://server.trygravity.ai/api/v1/ad \
  -H "Authorization: Bearer <your_publisher_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "How do I set up a PostgreSQL database?"},
      {"role": "assistant", "content": "Here are the steps to set up PostgreSQL..."}
    ],
    "sessionId": "sess_abc123",
    "placements": [
      {"placement": "below_response", "placement_id": "main"}
    ],
    "user": {"id": "user_789"},
    "device": {
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) ...",
      "ip": "203.0.113.42"
    }
  }'
The primary ad endpoint. Send conversation messages and get back a contextually matched ad with generated creative.

Headers

Authorization
string
required
Your Gravity API key. Format: Bearer <key>

Body

messages
array
required
Conversation history. Array of {role, content} message objects. The engine uses the last few messages for contextual matching.
sessionId
string
required
Session identifier. Used for frequency capping, experiment bucketing, and reporting. If the Gravity pixel is installed and you’re on @gravity-ai/api ≥ 1.1.7, the SDK auto-forwards the pixel’s gr_sess_-prefixed session ID (30-min idle timeout, 1-day max) via window.gravityPixel.getSessionId(). Pass your own sessionId to override if you have a better session scope.
placements
array
required
Ad placement configuration. 1–10 placements per request.
FieldTypeDescription
placementstringPlacement type. One of above_response, below_response, inline_response, left_response, right_response, search_result, top_page, bottom_page, center_page, left_page, right_page.
placement_idstringUnique slot identifier on your side. Ties dashboard analytics back to the spot in your UI.
user
object
Optional. User context for targeting and attribution. All fields are optional on the wire — the engine accepts any shape and passes extras through.
FieldTypeDescription
idstringStable per-user identifier on your side. Used for frequency capping, attribution, and identity linking. The SDK defaults this to "anonymous" if you don’t supply one.
ipstringUser’s IP address. Auto-populated by the SDK from the incoming request.
email_hashstringSHA-256 of email.strip().lower(). Canonical email field (matches the email_hash used by the Index /search API). Passing this significantly improves attribution; see Data quality. The legacy hashed_email alias and a raw email field are still accepted for backward compatibility.
hashed_phonestringSHA-256 of digits-only phone.
Extra fields (gender, age, subscription tier, interests, etc.) are accepted and stored as request context.
device
object
required
End-user device signals. ip and ua are required — requests missing either are rejected with HTTP 400 (see Errors). They power fraud/bot detection plus the Device and Geography breakdowns in your dashboard. The browser SDK auto-populates them via gravityContext(); server-side callers must forward the client-collected device (the request reaching Gravity otherwise carries your server’s UA/IP, not the end user’s).
FieldTypeRequiredDescription
uastringYesEnd-user User-Agent. Drives fraud detection and the mobile/tablet/desktop split. Send the end user’s real browser/app UA — not your server’s.
ipstringYesEnd-user IP. Drives fraud detection and geo-targeting. Auto-filled by the SDK from the incoming request; set it explicitly for direct HTTP.
countrystringNo2-letter ISO country code. Optional override — also derived from ip.
osstringNoOperating system (e.g. iOS, Android, Windows).
ifastringNoMobile advertising ID (IDFA / GAID), for app publishers.
idstringNoYour stable per-device identifier.
Extra fields (timezone, locale, browser, device_model, …) are accepted and stored. Native-app/non-browser clients should send their real client UA (CFNetwork/Darwin, okhttp, ktor-client, …) plus the end user’s IP. See Request ads → Device signals.
relevancy
number
Optional. Minimum relevancy threshold, 0.0–1.0. When omitted, the engine falls back to the publisher baseline configured in your dashboard. Both SDKs default to 0.2.
excludedTopics
array
Optional. Array of topic strings to exclude from matching (e.g. ["politics"]).
testAd
boolean
default:"false"
Optional. When true, returns test creative and skips billing/metrics. The SDKs set this from the inverse of their production flag.
curl -X POST https://server.trygravity.ai/api/v1/ad \
  -H "Authorization: Bearer <your_publisher_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "How do I set up a PostgreSQL database?"},
      {"role": "assistant", "content": "Here are the steps to set up PostgreSQL..."}
    ],
    "sessionId": "sess_abc123",
    "placements": [
      {"placement": "below_response", "placement_id": "main"}
    ],
    "user": {"id": "user_789"},
    "device": {
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) ...",
      "ip": "203.0.113.42"
    }
  }'

Response

On a successful match the endpoint returns HTTP 200 with a JSON array of ad objects — one per requested placement:
[
  {
    "adText": "Serverless Postgres that scales to zero. Start free.",
    "title": "Neon Serverless Postgres",
    "brandName": "Neon",
    "cta": "Try Neon Free",
    "url": "https://neon.tech",
    "favicon": "https://icons.duckduckgo.com/ip3/neon.tech.ico",
    "clickUrl": "https://api.trygravity.ai/track/click?p=...",
    "impUrl": "https://api.trygravity.ai/ack?p=...",
    "placement": "below_response",
    "placement_id": "main"
  }
]
The SDKs wrap this array in a convenience object — { ads, status, elapsed } in JS, AdResult(ads, status, elapsed_ms, ...) in Python — but that envelope is SDK-only and does not appear on the wire.

Ad object

FieldTypeDescription
adTextstringGenerated ad copy, contextually matched
titlestringProduct/campaign title
brandNamestringAdvertiser brand name
ctastringCall to action text
urlstringLanding page URL
faviconstringBrand favicon URL
clickUrlstringTracked click URL — use this for links
impUrlstringImpression pixel URL — fire when ad is visible
placementstringEchoes back the placement type this ad filled
placement_idstringEchoes back your slot correlation ID
campaignIdstringCampaign identifier for the matched ad
leadFormobjectLead form configuration, when the campaign has a lead form attached
renderer_specobjectServer-delivered ad-unit layout — see Server-rendered ads
Null fields are omitted from the response.

renderer_spec

renderer_spec is attached per placement, configured on Gravity’s side — there is no request parameter to opt in or out. When your placement has a default design set up, every ad served on it carries the spec; when it doesn’t, the field is absent and you render your own look (or the SDK’s built-in one). To get a design configured for your placement, email support@trygravity.ai with your placement_id. The spec format and rendering rules are documented in Server-rendered ads. When an experiment is active, the ad object also includes:
FieldTypeDescription
variantstringHuman-readable arm label
experiment_idstringCanonical experiment ID
composition_idstringPer-render composition ID (unique per impression)
renderer_keystringWhich renderer to use
compositionobjectTokens and props for the assigned renderer
Experiment identity is already baked into impUrl and clickUrl — downstream analytics join automatically.
Always use clickUrl for ad links (not url directly) and fire impUrl when the ad becomes visible. This ensures accurate tracking and billing.

No ad available

When no ad matches the context (or the request is filtered as a bot, times out, or hits an unrecoverable error), the endpoint returns an HTTP 204 No Content with an empty body — there is no JSON payload. Your UI should gracefully hide the slot in that case.

Errors

Requests missing required fields are rejected with HTTP 400 and a JSON body naming each missing field. device.ip and device.ua are validated on every ad request (blank/whitespace-only values count as missing):
{
  "detail": {
    "errors": [
      "Field 'device.ip' is required",
      "Field 'device.ua' is required"
    ]
  }
}
Fix the request by forwarding the end user’s real IP and User-Agent — see Request ads → Forwarding device server-side.