JavaScript/TypeScript SDK for Gravity ads.
import { Gravity } from '@gravity-ai/api';
const gravity = new Gravity({ production: true }); // reads GRAVITY_API_KEY from env
const result = await gravity.getAds(req, messages, placements);Works with Express, Fastify, Next.js, Node http, Bun, and Deno.
npm install @gravity-ai/apiSet GRAVITY_API_KEY in your server environment.
+ import { gravityContext } from '@gravity-ai/api';
async function sendMessage(prompt) {
+ const gravity_context = gravityContext({
+ sessionId: chatSession.id,
+ user: { userId: currentUser.id },
+ });
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ messages }),
+ body: JSON.stringify({ messages, gravity_context }),
});
}
sessionIdanduserIdare required. Pass your chat/conversation session ID and your authenticated user's ID.
gravityContext() auto-detects the runtime (browser vs Node/Bun/Deno) and collects the appropriate signals — user-agent, screen size, timezone, locale, etc. Nothing is written to disk, localStorage, or cookies.
+ import { Gravity } from '@gravity-ai/api';
+ const gravity = new Gravity({ production: true });
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
+ const adPromise = gravity.getAds(req, messages, [
+ { placement: 'below_response', placement_id: 'main' },
+ ]);
// stream your LLM response as usual...
res.setHeader('Content-Type', 'text/event-stream');
for await (const token of streamYourLLM(messages)) {
res.write(`data: ${JSON.stringify({ type: 'chunk', content: token })}\n\n`);
}
+ const { ads } = await adPromise;
+ res.write(`data: ${JSON.stringify({ type: 'done', ads })}\n\n`);
res.end();
});gravity.getAds() reads gravity_context from req.body, extracts the end-user's IP from headers, and calls the Gravity API. It never throws — returns { ads: [] } on any failure so your chat flow is never blocked.
import { Gravity } from '@gravity-ai/api';
const gravity = new Gravity({ production: true });
export async function POST(request: Request) {
const body = await request.json();
const { ads } = await gravity.getAds(
{ body, headers: Object.fromEntries(request.headers) },
body.messages,
[{ placement: 'below_response', placement_id: 'main' }],
);
return Response.json({ ads });
}const [adResult, llmResult] = await Promise.allSettled([
gravity.getAds(req, messages, placements),
yourLLMCall(messages),
]);Ad latency never blocks the chat response.
For Python backends (FastAPI, Django, Flask), use the gravity-py package:
pip install gravity-sdk+ 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": "chat", "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"
+ 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")Configure once at startup. All options are optional.
const gravity = new Gravity({
production: true,
relevancy: 0.3,
excludedTopics: ['gambling'],
});| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
process.env.GRAVITY_API_KEY |
Gravity API key |
production |
boolean |
false |
true = real ads. false = test ads (no billing). |
relevancy |
number |
0.2 |
Minimum relevancy threshold (0-1). Lower = more ads. |
timeoutMs |
number |
3000 |
Request timeout in ms |
excludedTopics |
string[] |
— | Topics to exclude from ad matching |
gravityApi |
string |
production URL | Custom API endpoint |
Fetch ads. Never throws. Returns { ads: [] } on any failure.
| Parameter | Type | Required | Description |
|---|---|---|---|
req |
object |
Yes | Server request object. gravity_context is read from req.body. |
messages |
MessageObject[] |
Yes | Conversation [{ role, content }] — last 2 turns sent |
placements |
PlacementObject[] |
Yes | Ad slots, e.g. [{ placement: "below_response", placement_id: "main" }] |
overrides |
GravityAdsOptions |
No | Per-call overrides merged on top of constructor config |
Returns: { ads: Ad[], status: number, elapsed: string, requestBody: object, error?: string }
Client-side. Collects device + user context. Returns a plain object to include in your request body.
| Parameter | Type | Required | Description |
|---|---|---|---|
overrides.sessionId |
string |
Yes | Your chat/conversation session ID |
overrides.user.userId |
string |
Yes | Your authenticated user's ID |
overrides.device |
object |
No | Device field overrides |
Returns: { sessionId, user: { id, ... }, device: { ua, timezone, locale, ... } }
Auto-detected fields vary by runtime:
| Field | Browser | Node/Bun/Deno |
|---|---|---|
ua |
navigator.userAgent |
gravity-js/x.x.x (OS arch; Runtime/ver) |
browser |
"web" |
"cli" |
device_type |
"mobile" or "desktop" |
"server" (CI) or "desktop" |
timezone |
Intl.DateTimeFormat |
Intl.DateTimeFormat |
screen_width/height |
screen.width/height |
stdout.columns/rows |
viewport_width/height |
innerWidth/Height |
stdout.columns/rows |
Standalone function — same as gravity.getAds() but takes all config per-call. Useful when you don't want to instantiate a class.
import { gravityAds } from '@gravity-ai/api';
const { ads } = await gravityAds(req, messages, placements, { production: true });┌──────────────────┐ gravity_context in body ┌──────────────────┐ POST /api/v1/ad ┌─────────┐
│ Client code │ ────────────────────────────▶ │ Your server │ ──────────────────▶ │ Gravity │
│ │ │ │ │ API │
│ gravityContext() │ { sessionId, │ gravity.getAds( │ { messages, │ │
│ auto-detects │ user: {...}, │ req, messages, │ sessionId, │ returns │
│ user + device │ device: {...} } │ placements) │ user: {...}, │ Ad[] │
│ │ │ reads ctx from │ device: {ip,...} } │ │
└──────────────────┘ │ req.body + IP │ └─────────┘
└──────────────────┘
For OpenTUI terminal apps, createGravityAdCard() gives you a ready-to-use ad card with impression tracking, click-to-open-browser, and hover affordance:
import { createGravityAdCard } from '@gravity-ai/api/opentui';
const adCard = createGravityAdCard({ renderer, input });
root.add(adCard.panel);
// In your SSE stream consumer:
if (event.type === 'ad') {
adCard.showAd(event.ads[0]); // populates content, fires impression, wires click
}Every visual property is overridable — pass panel, text, cta, hoverPanel style overrides, or mutate adCard.elements directly. For fully custom layouts, use the low-level gravityAdTracking() primitive instead.
Requires @opentui/core as a peer dependency (bun add @opentui/core). See OPENTUI.md for the full API reference, custom layout guide, and theme matching.
npm install @gravity-ai/reactimport { GravityAd } from '@gravity-ai/react';
<GravityAd ad={ads[0]} variant="card" />See @gravity-ai/react for component docs, variants, and styling.
Ads are plain JSON objects. Render them however you want:
const ad = ads[0];
if (ad) {
element.innerHTML = `<a href="${ad.clickUrl}">${ad.adText}</a>`;
if (ad.impUrl) new Image().src = ad.impUrl;
}MIT