> ## 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.

# JavaScript SDK

> Official JavaScript/TypeScript SDK for integrating Gravity ads into your applications.

## Installation

```bash theme={null}
npm install @gravity-ai/api
```

For React components:

```bash theme={null}
npm install @gravity-ai/react
```

Set `GRAVITY_API_KEY` in your server environment.

## How the pieces fit together

<Steps>
  <Step title="Client — prepare context">
    `gravityContext()` runs in your chat component. It captures `sessionId`, `userId`, and device signals (User-Agent, screen, timezone, locale) and returns a plain object your server can read.
  </Step>

  <Step title="Server — fetch ads">
    `gravity.getAds(req, messages, placements)` reads the context from the request body, adds the client IP, and POSTs to `https://server.trygravity.ai/api/v1/ad`. Runs in parallel with your LLM call.
  </Step>

  <Step title="Gravity — match and return">
    The engine matches the conversation against active campaigns, runs an auction, and returns an ad object per requested placement.
  </Step>

  <Step title="Client — render">
    `<GravityAd />` renders the ad, fires the impression pixel on visibility, and routes clicks through the tracked URL automatically.
  </Step>
</Steps>

## Client — prepare context

In your chat component, prepare user and device signals:

```ts theme={null}
import { gravityContext } from '@gravity-ai/api';

const gravity_context = gravityContext({
  sessionId: chatSession.id,
  user: { userId: currentUser.id },
});

// Send it alongside your messages
fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ messages, gravity_context }),
});
```

`gravityContext()` auto-detects device signals — User-Agent, screen size, timezone, language — on the client and returns them under a `device` object. Required parameters:

| Parameter     | Type     | Required | Description                                                                                                                                                                    |
| ------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sessionId`   | `string` | Yes      | Unique session identifier                                                                                                                                                      |
| `user.userId` | `string` | **Yes**  | Stable per-user identifier. Thrown at runtime if missing — pass `"anonymous"` (or any constant) yourself if you genuinely don't have one. Gets normalized to `id` on the wire. |

<Warning>
  Forward the whole `gravity_context` (including its `device` object) to your ad request. `device.ip` and `device.ua` are **required** — if your backend drops them, the ad request is rejected with HTTP `400` and no ad is served. See [Device signals](/ai-platforms/request-ads#device-signals).
</Warning>

## Server — fetch ads

```ts theme={null}
import { Gravity } from '@gravity-ai/api';

const gravity = new Gravity({ production: true });

app.post('/api/chat', async (req, res) => {
  const { messages } = req.body;

  // Fire ad request in parallel with your LLM call
  const adPromise = gravity.getAds(req, messages, [
    { placement: 'below_response', placement_id: 'main' },
  ]);

  // Stream your LLM response...
  for await (const token of streamYourLLM(messages)) {
    res.write(`data: ${JSON.stringify({ type: 'chunk', content: token })}\n\n`);
  }

  // Append ads at the end
  const { ads } = await adPromise;
  res.write(`data: ${JSON.stringify({ type: 'done', ads })}\n\n`);
  res.end();
});
```

The `Gravity` constructor accepts:

| Parameter        | Type       | Default                   | Description                                                                           |
| ---------------- | ---------- | ------------------------- | ------------------------------------------------------------------------------------- |
| `apiKey`         | `string`   | `GRAVITY_API_KEY` env var | Your Gravity API key                                                                  |
| `gravityApi`     | `string`   | Production URL            | Gravity ad endpoint URL                                                               |
| `timeoutMs`      | `number`   | `3000`                    | Abort after this many ms                                                              |
| `relevancy`      | `number`   | `0.2`                     | Minimum relevancy threshold, 0.0–1.0. Lower = more ads with weaker contextual matches |
| `production`     | `boolean`  | `false`                   | `false` returns test ads (no billing)                                                 |
| `excludedTopics` | `string[]` | `undefined`               | Topics to exclude from ad matching                                                    |

<Warning>
  `gravity.getAds()` never throws. On any failure, it returns `{ ads: [] }` silently. Safe to fire-and-forget.
</Warning>

## React — render ads

```tsx theme={null}
import { GravityAd } from '@gravity-ai/react';

function ChatResponse({ response, ads }) {
  return (
    <div>
      <p>{response}</p>
      {ads[0] && <GravityAd ad={ads[0]} variant="card" />}
    </div>
  );
}
```

The `GravityAd` component handles impression tracking automatically via `IntersectionObserver`.

| Prop      | Type                                                                                                                                                                                                                                                                                                                                | Description                                                                                                                                                                                                                                                                       |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ad`      | `Ad`                                                                                                                                                                                                                                                                                                                                | The ad object from `gravity.getAds()`                                                                                                                                                                                                                                             |
| `variant` | `"card" \| "inline" \| "minimal" \| "bubble" \| "contextual" \| "native" \| "footnote" \| "quote" \| "suggestion" \| "accent" \| "side-panel" \| "labeled" \| "spotlight" \| "embed" \| "split-action" \| "pill" \| "banner" \| "divider" \| "toolbar" \| "tooltip" \| "notification" \| "hyperlink" \| "text-link" \| "lead_form"` | Rendering style. When an [experiment](/ai-platforms/experiments) is active and `variant` is omitted, the component uses the engine-assigned renderer. `lead_form` renders a [lead-form ad unit](/ai-platforms/lead-forms) (also auto-selected whenever `ad.leadForm` is present). |

## Lead-form ads

When an advertiser runs a lead-form campaign, the served ad carries a `leadForm`
envelope and `GravityAd` renders an inline form instead of a click-through. This
needs two extra props — `sessionId` (must match the ad request) and `apiKey`:

```tsx theme={null}
import { GravityAd } from '@gravity-ai/react';

<GravityAd
  ad={ads[0]}
  sessionId={sessionId}
  apiKey={process.env.GRAVITY_API_KEY}
  userEmail={user?.email}   // optional — enables identity pre-fill
/>
```

The same component still renders normal ads when `ad.leadForm` is absent, so one
integration covers every campaign type. For display modes, pre-fill, the
submission endpoint, and styling, see the full [Lead-form ads
guide](/ai-platforms/lead-forms). The lower-level `GravityLeadFormAd` component
is also exported for direct use.

## Impression tracking

If you render ads with a custom component instead of `GravityAd`, fire the impression pixel when the ad becomes visible:

```ts theme={null}
new Image().src = ad.impUrl;
```

Click tracking is handled automatically through the `clickUrl` link.

## What's next

<Card title="Experiments" icon="flask" href="/ai-platforms/experiments">
  Run A/B tests on ad creative and rendering. The engine assigns arms; the SDK renders them.
</Card>

## FAQ

<AccordionGroup>
  <Accordion title="Does the ad request block my LLM response?">
    No. Fire `gravity.getAds()` in parallel with your LLM call using `Promise.allSettled()`. It never throws, so failures are silent.
  </Accordion>

  <Accordion title="Where does the API key go?">
    Set `GRAVITY_API_KEY` as an environment variable on your server. The `Gravity` class reads it automatically. You can also pass it via `new Gravity({ apiKey: '...' })`.
  </Accordion>

  <Accordion title="Do I need @gravity-ai/react?">
    Only if you want pre-built React components. For custom rendering, just use the ad objects from `@gravity-ai/api` directly.
  </Accordion>
</AccordionGroup>
