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"
}
}'
Endpoints
Contextual Ads
Get ads matched to conversation context.
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.
Null fields are omitted from the response.
Gravity configures server-rendered designs per placement. When a compatible design is available, the response includes
Experiment identity is already baked into
Fix the request by forwarding the end user’s real IP and User-Agent — see Request ads → Forwarding device server-side.
Headers
string
required
Your Gravity API key. Format:
Bearer <key>Body
array
required
Conversation history. Array of
{role, content} message objects. The engine uses the last few messages for contextual matching.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.array
required
Ad placement configuration. 1–10 placements per request.
Show placement object
Show placement object
string
required
Placement 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.string
required
Unique slot identifier on your side. Ties dashboard analytics back to the spot in your UI. Must be unique within a request — duplicates are rejected.
number
Cap on generated ad-copy length for this slot, 50–5000. Overrides the placement’s configured baseline.
object
Optional. User context for targeting and attribution. All fields are optional on the wire — the engine accepts any shape and passes extras through.
Extra fields (gender, age, subscription tier, interests, etc.) are accepted and stored as request context.
Show user fields
Show user fields
string
Stable per-user identifier on your side. Used for frequency capping, attribution, and identity linking. The JS SDK sends this from
user.userId and defaults it to "anonymous" if you don’t supply one.string
User’s IP address. Auto-populated by the SDK from the incoming request.
string
SHA-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.string
SHA-256 of digits-only phone.
object
required
End-user device signals.
Extra fields (
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).Show device fields
Show device fields
string
required
End-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.
string
required
End-user IP. Drives fraud detection and geo-targeting. Auto-filled by the SDK from the incoming request; set it explicitly for direct HTTP.
string
2-letter ISO country code. Optional override — also derived from
ip.string
Operating system (e.g.
iOS, Android, Windows).string
Mobile advertising ID (IDFA / GAID), for app publishers.
string
Your stable per-device identifier.
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.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.array
Optional. Array of topic strings to exclude from matching (e.g.
["politics"]).boolean
default:"false"
Optional. When
true, returns test creative and skips billing/metrics. The SDKs set this from the inverse of their production flag.string
Optional. The server-rendered design vocabulary your client supports. Official SDKs set this automatically.
Set
| Capability | Meaning |
|---|---|
"psp1" | Native iOS or Android renderer. |
"web1" | Web renderer. |
supportsSpec: false to opt out of server-rendered designs. The legacy supportsSpec: true value is treated as web support.object
Optional. Privacy/consent signals for the end user. When omitted, behavior is unchanged from today.
When
| Field | Type | Description |
|---|---|---|
gdprApplies | boolean | Whether GDPR applies to this user (EU/EEA/UK). |
tcfString | string | IAB TCF consent string collected by your CMP. |
usPrivacy | string | US privacy (CCPA/CPRA) string, e.g. "1YNN". |
gdprApplies is true and no valid tcfString is present, the engine serves contextual-only ads: it skips all processing keyed to pseudonymous user identifiers for that request (personalization, identity-keyed telemetry). Contextual matching — Gravity’s core model — needs no personal data, so ads still serve.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 HTTP200 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
| Field | Type | Description |
|---|---|---|
adText | string | Generated ad copy, contextually matched |
title | string | Product/campaign title |
brandName | string | Advertiser brand name |
cta | string | Call to action text |
url | string | Landing page URL |
favicon | string | Brand favicon URL |
clickUrl | string | Tracked click URL — use this for links |
impUrl | string | Impression pixel URL — fire when ad is visible |
placement | string | Echoes back the placement type this ad filled |
placement_id | string | Echoes back your slot correlation ID |
campaignId | string | Campaign identifier for the matched ad |
leadForm | object | Lead form configuration, when the campaign has a lead form attached |
renderer_spec | object | Server-delivered ad-unit layout — see Server-rendered ads |
feedbackPrompt | boolean | When true, the SDK shows a one-tap thumbs-up/down prompt under the ad — see Ad feedback. Absent on most ads. |
renderer_spec
Gravity configures server-rendered designs per placement. When a compatible design is available, the response includes renderer_spec; otherwise the SDK uses its built-in card. Official SDKs handle this automatically. Contact support@trygravity.ai to configure a custom placement design.
When an experiment is active, the ad object also includes:
| Field | Type | Description |
|---|---|---|
variant | string | Human-readable arm label |
experiment_id | string | Canonical experiment ID |
composition_id | string | Per-render composition ID (unique per impression) |
renderer_key | string | Which renderer to use |
composition | object | Tokens and props for the assigned renderer |
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 HTTP204 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 HTTP400 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"
]
}
}

