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

# AI platform quickstart

> From zero to your first live ad in under five minutes.

## What you're going to do

1. Create an AI platform account and grab your API key.
2. Install the SDK.
3. Fire ad requests from your server in parallel with your LLM call.
4. Render the ad in your chat UI.
5. Drop the web pixel on your site.
6. Flip to production when it looks right.

Copy the commands, change a couple of IDs, ship it.

<Note>
  Throughout this guide we use the SDK (`@gravity-ai/api` + `@gravity-ai/react`). If you'd rather hit the HTTP API directly, see the [API Reference](/engine/introduction). Same engine, you just own the rendering and tracking.
</Note>

## 1. Create your account

1. Sign up at [app.trygravity.ai/publisher/signup](https://app.trygravity.ai/publisher/signup).
2. Create an AI platform account for your app.
3. From your dashboard, copy the **API key** and the **Pixel ID**.

Save the API key as `GRAVITY_API_KEY` in your server environment.

## 2. Install the SDK

<CodeGroup>
  ```bash Node / JS / TS theme={null}
  npm install @gravity-ai/api @gravity-ai/react
  ```

  ```bash Python theme={null}
  pip install gravity-sdk
  ```
</CodeGroup>

## 3. Prepare context on the client

Call `gravityContext()` in your chat component so the server gets the session ID and **device signals** (the end user's User-Agent, IP, etc.) alongside the messages.

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

const gravity_context = gravityContext({
  sessionId: chatSession.id,      // your conversation ID
  user: { userId: currentUser.id } // your user ID
});
// gravity_context includes a `device` object: { ua, timezone, locale, ... }
// The server-side SDK adds the end-user IP from the request headers automatically.

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

<Warning>
  **Don't skip this — and forward `gravity_context` all the way to the ad request.** The `device` object's `ua` and `ip` are **required** — they power fraud/bot detection plus the **Device** and **Geography** breakdowns in your dashboard, and requests missing them are rejected with HTTP `400`. If your backend calls Gravity without forwarding the client's `device`, the ad request carries your *server's* UA/IP instead of the end user's. The SDK's `getAds(req, …)` reads `gravity_context` from the request body for you — just make sure your client actually posts it. See [Device signals](/ai-platforms/request-ads#device-signals).
</Warning>

## 4. Fetch ads on the server

Fire the ad request **in parallel** with your LLM call. It never throws — failures return an empty array.

<CodeGroup>
  ```ts Node / Express theme={null}
  import { Gravity } from '@gravity-ai/api';

  const gravity = new Gravity();  // reads GRAVITY_API_KEY from env

  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 in parallel...
    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();
  });
  ```

  ```python Python / FastAPI theme={null}
  import asyncio
  from gravity_sdk import Gravity

  gravity = Gravity()  # reads GRAVITY_API_KEY from env

  @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"
          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")
  ```
</CodeGroup>

## 5. Render the ad

```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>
  );
}
```

`GravityAd` handles impression tracking automatically via `IntersectionObserver`, wires the click URL for attribution, and supports 20+ visual variants (`card`, `minimal`, `inline`, `spotlight`, `pill`, `banner`, `hyperlink`, etc.). Pick whichever fits your UI.

## 6. Install the pixel (required)

The Gravity pixel is **required** for attribution and payouts. Without it, Gravity can't measure visitor journeys or tie ad performance back to real outcomes. Drop this on every page:

```html theme={null}
<script>
  !function(w,d,t,u,n,a,m){w['GravityPixelObject']=n;w[n]=w[n]||function(){
  (w[n].q=w[n].q||[]).push(arguments)},w[n].l=1*new Date();a=d.createElement(t),
  m=d.getElementsByTagName(t)[0];a.async=1;a.src=u;m.parentNode.insertBefore(a,m)
  }(window,document,'script','https://code.trygravity.ai/gr-pix.js','gravity');
  gravity('init', 'YOUR_PIXEL_ID');
</script>
```

See the [Pixel page](/ai-platforms/pixel) for verification steps and the [in-app browser install](/ai-platforms/pixel#in-app-browser-webview) if your native app opens ad links in a WebView.

## 7. Verify, then go live

By default, `new Gravity()` returns **test ads** — no billing, safe to iterate. You'll see the same test creative every time.

When you're ready for real traffic:

<CodeGroup>
  ```ts Node / JS / TS theme={null}
  const gravity = new Gravity({ production: true });
  ```

  ```python Python theme={null}
  gravity = Gravity(production=True)
  ```
</CodeGroup>

That's it. You're now serving real ads and earning revenue.

<CardGroup cols={2}>
  <Card title="Full integration guide" icon="book" href="/ai-platforms/integration-guide">
    The long version: lifecycle, placements, error handling, framework-specific examples.
  </Card>

  <Card title="Going live checklist" icon="rocket" href="/ai-platforms/going-live">
    Everything to verify before flipping `production: true`.
  </Card>
</CardGroup>

## What you didn't have to think about

* **Retries** — the SDK handles transient failures silently.
* **Timeouts** — 3 seconds by default. Configurable.
* **Impression / click tracking** — fired by `GravityAd`. Attribution is automatic.
* **Experiments** — the engine can test different ad variants for your traffic without you shipping code. See [Experiments](/ai-platforms/experiments).
* **Ad quality** — Gravity handles the creative; you never hand-pick advertisers.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Getting empty ad arrays in production">
    Expected when no campaign matches the conversation — the engine returns `204 No Content` and the SDK resolves to `{ ads: [] }`. Gracefully hide the ad slot. Lower the [relevancy threshold](/ai-platforms/request-ads#relevancy-tradeoff) to fill more aggressively if you want looser matches.
  </Accordion>

  <Accordion title="I'm seeing the same ad every request">
    You're still on test mode. Pass `production: true` to the constructor or set `production=True` in Python.
  </Accordion>

  <Accordion title="Ads appear but impressions aren't counting">
    If you're using `GravityAd`, it fires on visibility via IntersectionObserver — make sure the ad is actually rendering in the viewport. If you render ads with a custom component, you have to fire the impression pixel yourself: `new Image().src = ad.impUrl` when the ad becomes visible.
  </Accordion>
</AccordionGroup>
