Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.trygravity.ai/llms.txt

Use this file to discover all available pages before exploring further.

Installation

pip install gravity-sdk
Set GRAVITY_API_KEY in your server environment. Only dependency is httpx.

Quick start

Add a few lines to your existing streaming chat endpoint. The ad request runs in parallel with your LLM call — zero added latency.
+ import asyncio
+ from gravity_sdk import Gravity

+ gravity = Gravity(production=True)

  @app.post("/api/chat")
  async def chat(request: Request):
      body = await request.json()
      messages = body["messages"]

+     ad_task = asyncio.create_task(
+         gravity.get_ads(request, messages, [{"placement": "below_response", "placement_id": "main"}])
+     )

      async def event_stream():
          async for token in stream_your_llm(messages):
              yield f"data: {json.dumps({'type': 'chunk', 'content': token})}\n\n"

-         yield f"data: {json.dumps({'type': 'done'})}\n\n"
+         ad_result = await ad_task
+         ads = [a.to_dict() for a in ad_result.ads]
+         yield f"data: {json.dumps({'type': 'done', 'ads': ads})}\n\n"

      return StreamingResponse(event_stream(), media_type="text/event-stream")
Works with FastAPI, Starlette, Django, and Flask.

Constructor

Gravity(*, api_key=None, api_url=None, timeout=3.0, production=False, relevancy=0.2)
ParameterTypeDefaultDescription
api_keystrGRAVITY_API_KEY env varYour Gravity API key
api_urlstrProduction URLGravity API endpoint
timeoutfloat3.0Request timeout in seconds
productionboolFalseFalse returns test ads (no billing)
relevancyfloat0.2Minimum relevancy threshold, 0.0-1.0. Lower = more ads with weaker contextual matches
The client reuses its HTTP connection pool across calls. Use async with Gravity() as g: or call await gravity.close() for explicit cleanup.

get_ads()

await gravity.get_ads(request, messages, placements, *, production=None, relevancy=None)
ParameterTypeDefaultDescription
requestframework requestYour server’s request object (FastAPI, Django, Flask, etc.)
messageslist[dict]Conversation messages [{"role": ..., "content": ...}]
placementslist[dict]Ad placements, e.g. [{"placement": "chat", "placement_id": "main"}]
productionbool | NoneNoneOverride the constructor-level production flag for this call
relevancyfloat | NoneNoneOverride the constructor-level relevancy threshold for this call

Return types

@dataclass
class AdResult:
    ads: list[AdResponse]   # Parsed ad objects
    status: int             # 200, 204, 0 (error)
    elapsed_ms: str         # e.g. "142"
    request_body: dict | None
    error: str | None

@dataclass
class AdResponse:
    ad_text: str
    title: str | None
    cta: str | None
    brand_name: str | None
    url: str | None
    favicon: str | None
    imp_url: str | None
    click_url: str | None
    placement: str | None
    placement_id: str | None
    campaign_id: str | None
    # Experiment / composition fields (present when an experiment is active)
    variant: str | None
    experiment_id: str | None
    composition_id: str | None
    renderer_key: str | None
    composition_mode: str | None
    composition: dict | None
    # Lead-form envelope (present only for lead_form ad units)
    lead_form: LeadFormConfig | None
Both have .to_dict() methods that serialize to the camelCase JSON shape renderers expect.

Message handling

The SDK sends the last 2 conversational messages to the Gravity API for contextual ad matching. Only messages with recognized roles are included:
  • user, assistant, system, developer, model (Gemini’s alias for assistant)
Messages with other roles (e.g. tool, function, ipython) are filtered out — they typically contain structured data rather than natural language.
gravity.get_ads() never raises. On any failure, it returns AdResult(ads=[]). Safe to fire-and-forget in your stream.

PII hashing

The SDK provides hash_pii() for SHA-256 hashing of emails and phone numbers, matching the normalization used by the Gravity publisher pixel and the advertiser-side conversion pipeline.
from gravity_sdk import hash_pii

hashed = hash_pii(email=user.email, phone=user.phone)
# Include hashed PII in the gravity_context.user sent from your client:
# { "gravity_context": { "user": { "id": "u123", "hashed_email": "...", "hashed_phone": "..." } } }
Normalization rules:
  • Email: strip().lower()
  • Phone: digits only (e.g. "+1 (555) 123-4567""15551234567")
Returns a HashedIdentity dict with only the keys that successfully hashed (hashed_email, hashed_phone), so it’s safe to spread into a user dict.

Lead forms

When a campaign is configured as a lead_form ad unit, the AdResponse includes a lead_form field:
@dataclass
class LeadFormConfig:
    fields: list[LeadFormField]
    submit_url: str
    headline: str | None
    description: str | None
    cta_text: str | None
    success_message: str | None
    privacy_text: str | None
    consent_text: str | None
    success_redirect_url: str | None
    display: str              # "inline" | "modal-center" | "modal-bottom-right"
    prefill: LeadFormPrefill | None

@dataclass
class LeadFormField:
    name: str
    label: str
    type: str                 # "text", "email", "tel", etc.
    required: bool
    placeholder: str | None
    options: list[str] | None

@dataclass
class LeadFormPrefill:
    email: bool
    phone: bool
When present, render the form and POST the submitted values to lead_form.submit_url.

Experiments

When an experiment is active, every AdResponse carries the assigned variant, experiment_id, and composition_id. Impression and click URLs already encode the assignment — no extra tracking work.
ad = result.ads[0]
if ad.experiment_id:
    # Forward to whatever renderer you use. For React, pass ad.variant to
    # the <GravityAd /> component; for custom renderers, read ad.renderer_key
    # and ad.composition.
    ...