# Publisher SDK: Integration Guide (v1)

The Adgeist Publisher SDK serves ads into slots on your pages, measures their
views and clicks, sends page events to the Adgeist CDP, and manages user
consent. You add one script tag and one `<ins>` element per ad slot. The SDK
fills the slots on its own, and you call its global functions to identify users
and send custom events.

|              |                       |
| ------------ | --------------------- |
| Global       | `window.adsbyadgeist` |
| Bundle size  | ~31 kB gzipped        |
| Dependencies | nothing to install    |

**Steps**

1. [Integrate the SDK](#step-1-integrate-the-sdk)
2. [Access the SDK](#step-2-access-the-sdk)
3. [Global functions](#step-3-global-functions): `setUserDetails`,
   `trackCustomEvent`, `showConsentBanner`
4. [Version](#step-4-version)

**Reference:** [API reference](#api-reference) ·
[How it works](#appendix-how-it-works) · [Troubleshooting](#troubleshooting)

---

## Step 1: Integrate the SDK

### 1.1 Add the script tag

**Where:** once, in your site's root file: the shared layout or template that
every page uses (`index.html`, `app/layout.tsx`, a base template). That single
tag loads the SDK on every page; you don't add it per page or per component.
`<head>` is the usual spot; the end of `<body>` works too.

```html
<head>
  <script
    src="https://cdn.adgeist.ai/publisher/v1/publisher.min.js"
    data-publisher-id="YOUR_PUBLISHER_ID"
    async
  ></script>
</head>
```

**No `init()` call.** The SDK starts on its own when the script loads.

| Attribute                          | Required | Default | Purpose                                                          |
| ---------------------------------- | -------- | ------- | ---------------------------------------------------------------- |
| `data-publisher-id`                | **yes**  |         | Your Adgeist publisher ID. Without it no ads are shown.          |
| `data-should-ingest-events-to-cdp` | no       | `true`  | Set to `"false"` to stop sending page events to the Adgeist CDP. |

Put these attributes on the SDK's own `<script>` tag, not on a wrapper or
loader script.

#### Channels

| Channel  | URL                                                                  | Use for                                    |
| -------- | -------------------------------------------------------------------- | ------------------------------------------ |
| Floating | `cdn.adgeist.ai/publisher/v1/publisher.min.js`                       | production; receives every `1.x.y` release |
| Pinned   | `cdn.adgeist.ai/publisher/<version>/publisher.min.js` (e.g. `1.4.3`) | a fixed build; required for SRI            |
| Beta     | `cdn-beta.adgeist.ai/publisher/v1-beta/publisher.min.js`             | pre-release testing                        |

`v1` is a **floating channel**: you automatically receive fixes and
backward-compatible features. Breaking changes ship to a new channel (`v2`) that
you opt into explicitly.

### 1.2 Place an ad slot

**Where:** at the exact spot in the page where the ad should appear. Add one
`<ins>` element per ad space:

```html
<ins
  class="adsbyadgeist"
  data-ad-slot="YOUR_AD_SPACE_ID"
  style="display:inline-block;width:728px;height:90px"
></ins>
```

**`data-ad-slot` is the only attribute you need to set.** Position and size the
slot with your own CSS. A **responsive** ad space fills the slot's size; a
fixed ad space renders at the size configured in the dashboard.

Responsive slots work the same way:

```html
<ins
  class="adsbyadgeist"
  data-ad-slot="YOUR_AD_SPACE_ID"
  style="display:inline-block;width:100%;height:250px"
></ins>
```

If you don't set a height, the slot is sized from the ad space settings. The ad
only shows once the slot is at least the minimum size for its type:

| Type        | Min (w × h) | Max (w × h) |
| ----------- | ----------- | ----------- |
| `banner`    | 120 × 50    | 1200 × 900  |
| `display`   | 300 × 60    | 1200 × 900  |
| `companion` | 320 × 305   | 560 × 470   |

**Rules worth knowing**

- **One slot per ad space per page.** If two slots on a page use the same
  `data-ad-slot`, only the first is filled.
- **Slots added later are filled too.** Slots rendered after page load (by your
  framework, an infinite scroll, a modal) are filled automatically, with no
  extra call.
- **`.ad-container`:** if your page has an element with the class
  `ad-container`, place your slots inside it. Slots outside it are not filled.

### 1.3 Framework setup

**Next.js (App Router).** Add the script to the root layout so it loads once
and stays loaded across client-side navigation:

```tsx
// app/layout.tsx
import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          async
          src="https://cdn.adgeist.ai/publisher/v1/publisher.min.js"
          data-publisher-id="YOUR_PUBLISHER_ID"
        />
      </body>
    </html>
  );
}
```

**React (Vite, Create React App) or any SPA.** Put the script tag from 1.1 in
`index.html`. Do not inject it from a component that mounts and unmounts.

**Ad slot component (React / Next.js).** Render the `<ins>` element; the SDK
fills it on its own:

```tsx
export function AdSlot({ slotId }: { slotId: string }) {
  return (
    <ins
      className="adsbyadgeist"
      data-ad-slot={slotId}
      style={{ display: "inline-block", width: "100%", height: 250 }}
    />
  );
}
```

Keep the slot mounted. Every time a new `<ins>` mounts, it counts as a new ad
request and a new view. Route changes in single-page apps are handled for you;
see [Single-page apps](#single-page-apps).

---

## Step 2: Access the SDK

The SDK exposes one global object, `window.adsbyadgeist`. Every function in
[Step 3](#step-3-global-functions) is called on it.

### 2.1 When the global is available

The script loads `async`, so `window.adsbyadgeist` is `undefined` until the
script has loaded. Check for it before calling:

```js
if (window.adsbyadgeist) {
  window.adsbyadgeist.trackCustomEvent("add_to_cart", { sku: "ABC-123" });
}
```

### 2.2 TypeScript

Declare the global once, for example in `adgeist.d.ts`:

```ts
export {};

declare global {
  interface Window {
    adsbyadgeist?: {
      setUserDetails(details: {
        userId: string;
        email?: string;
        phone?: string;
        name?: string;
        companyName?: string;
      }): Promise<void>;
      trackCustomEvent(
        eventType: string,
        eventData: Record<string, unknown>,
      ): Promise<void>;
      showConsentBanner(): void;
      version: string;
    };
  }
}
```

### 2.3 Error handling

SDK calls never throw errors into your page, so you don't need `try`/`catch`
around them. If something goes wrong inside the SDK, your page keeps working.

---

## Step 3: Global functions

Each one is a method on `window.adsbyadgeist`. Ads need none of them: slots fill
on their own once [Step 1](#step-1-integrate-the-sdk) is done.

| Function                                     | Purpose                                | Returns         |
| -------------------------------------------- | -------------------------------------- | --------------- |
| [`setUserDetails`](#31-setuserdetails)       | Link this browser to your user         | `Promise<void>` |
| [`trackCustomEvent`](#32-trackcustomevent)   | Send a custom event to the Adgeist CDP | `Promise<void>` |
| [`showConsentBanner`](#33-showconsentbanner) | Open the built-in consent banner       | `void`          |

### 3.1 `setUserDetails`

**Purpose.** Tells Adgeist who the user is, so activity in this browser is
linked to your user ID and to the user's Adgeist CDP profile.

**When to call.** Once, when you know who the user is, for example right after
login or signup. You don't need to call it again on other pages.

**Where to call.** In your login/signup success handler, or wherever your app
first has the signed-in user.

**Signature**

```ts
setUserDetails(details: {
  userId: string;
  email?: string;
  phone?: string;
  name?: string;
  companyName?: string;
}): Promise<void>
```

**Parameters**

| Field         | Type     | Required | Notes                                                                                                    |
| ------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `userId`      | `string` | yes      | Your stable ID for the user.                                                                             |
| `email`       | `string` | no       | As the user typed it. Links the user to their Adgeist CDP profile.                                       |
| `phone`       | `string` | no       | **Must include the country code** (`+91…`); spaces and dashes are fine. Links the user to their profile. |
| `name`        | `string` | no       | The user's display name.                                                                                 |
| `companyName` | `string` | no       | Your site or brand name; defaults to your domain.                                                        |

Other fields are ignored.

**Returns.** `Promise<void>`. Never rejects.

**Behavior**

- **Updates, doesn't replace.** Each call adds to the details from earlier
  calls, so a field you leave out keeps its previous value.
- **Works regardless of consent.**
- **Invalid values are skipped, not fatal.** An email or phone Adgeist can't use
  (e.g. a phone without a country code) is ignored and a warning is logged to
  the console.

Email, phone, name and user ID are personal data sent to Adgeist. Only pass
them when your privacy policy covers it.

**Example**

```js
async function onLoginSuccess(user) {
  if (!window.adsbyadgeist) return;
  await window.adsbyadgeist.setUserDetails({
    userId: user.id, // 'u_10293'
    email: user.email, // 'user@example.com'
    phone: user.phone, // '+91 98765 43210'
    name: user.name, // 'Priya R'
    companyName: "MDTV",
  });
}
```

### 3.2 `trackCustomEvent`

**Purpose.** Sends an event you define (add to cart, video play, article read)
to the Adgeist CDP, together with the current page URL and the user details
from [`setUserDetails`](#31-setuserdetails).

> **Status:** custom events are not live yet. Calls are accepted, but no event
> is recorded until the feature launches.

**When to call.** When the user completes the action you want to record.

**Where to call.** In the handler for that action.

**Signature**

```ts
trackCustomEvent(
  eventType: string,
  eventData: Record<string, unknown>,
): Promise<void>
```

**Parameters**

| Parameter   | Type     | Required | Notes                                                    |
| ----------- | -------- | -------- | -------------------------------------------------------- |
| `eventType` | `string` | yes      | Any name you choose, e.g. `"add_to_cart"`.               |
| `eventData` | `object` | yes      | Any data about the event. The page URL is added for you. |

**Returns.** `Promise<void>`. Never rejects.

**Behavior**

- **Works regardless of consent.**
- **Does nothing when CDP events are turned off**
  (`data-should-ingest-events-to-cdp="false"`).

**Example**

```js
function onAddToCart(item) {
  if (!window.adsbyadgeist) return;
  window.adsbyadgeist.trackCustomEvent("add_to_cart", {
    sku: item.sku, // 'ABC-123'
    price: item.price, // 1499
  });
}
```

### 3.3 `showConsentBanner`

**Purpose.** Opens the built-in consent banner. Consent is shared with the
Advertiser SDK, so one decision covers both.

Consent has two categories: `necessary` (always on) and `personalization`, which
the user can turn on or off. `personalization` controls whether device details
and page data are collected for the Adgeist CDP. Ads are served, and their views
and clicks measured, either way.

**When to call.** When the user asks to review their choice, for example from a
"Cookie settings" or "Privacy choices" link. To show the banner automatically
on first visit, set `autoShow: true` in the consent config instead (below).

**Where to call.** In the click handler of that link.

**Signature**

```ts
showConsentBanner(): void
```

**Parameters.** None.

**Returns.** `void`.

**Behavior**

- **Does nothing when `ui: "none"`.** In that mode you supply your own UI (see
  below).

**Example**

```html
<a href="#" id="privacy-choices">Privacy choices</a>
<script>
  document.getElementById("privacy-choices").addEventListener("click", (e) => {
    e.preventDefault();
    if (window.adsbyadgeist) window.adsbyadgeist.showConsentBanner();
  });
</script>
```

#### Configure consent

Set the config **before** the SDK loads, in an inline `<script>` placed
**above** the publisher script tag:

```html
<script>
  window.adgeistConsentConfig = {
    mode: "opt-in",
    autoShow: true,
    policyUrl: "https://example.com/privacy",
  };
</script>
<!-- then the publisher <script> tag -->
```

or queue it with `window.adgeistConsent = [['setConsentConfig', config]]`.

| Field        | Type                    | Default             | Notes                                                                                                  |
| ------------ | ----------------------- | ------------------- | ------------------------------------------------------------------------------------------------------ |
| `mode`       | `'opt-in' \| 'opt-out'` | `'opt-out'`         | `opt-out`: collection runs until the user rejects. `opt-in`: collection is off until the user accepts. |
| `autoShow`   | `boolean`               | `false`             | Show the banner automatically when the user hasn't made a choice yet.                                  |
| `ui`         | `'default' \| 'none'`   | `'default'`         | `'none'` to bring your own UI.                                                                         |
| `policyUrl`  | `string`                |                     | Link shown in the banner.                                                                              |
| `cookieName` | `string`                | `'adgeist_consent'` | Cookie that stores the user's choice.                                                                  |
| `persist`    | `boolean`               | `true`              | `false` stops the config from being remembered on later page loads.                                    |

To supply your own UI, set `ui: "none"` and use `window.adgeistConsent`
(`acceptAll()`, `rejectAll()`, `setConsent()`, `onConsentChange()`, …).

---

## Step 4: Version

**Purpose.** Tells you which SDK version is running on the page.

```ts
window.adsbyadgeist.version: string // e.g. '1.4.3'
```

It is a property, not a function. Use it when debugging or reporting an issue:

```js
console.log(window.adsbyadgeist && window.adsbyadgeist.version);
```

**Which version you get** depends on the script URL
([channels](#channels)):

- `v1` (floating): the latest `1.x.y`. Fixes and backward-compatible features
  arrive automatically. Breaking changes ship as `v2`, which you opt into.
- `<version>` (pinned): exactly that version, never changes. Required for SRI.
- `v1-beta`: pre-release versions.

Beta and production share version numbers; only the script URL
(`cdn-beta.adgeist.ai/.../v1-beta` vs `cdn.adgeist.ai/.../v1`) tells them
apart.

---

## API reference

```ts
window.adsbyadgeist.setUserDetails(details: {
  userId: string;
  email?: string;
  phone?: string;
  name?: string;
  companyName?: string;
}): Promise<void>

window.adsbyadgeist.trackCustomEvent(
  eventType: string,
  eventData: Record<string, unknown>,
): Promise<void>

window.adsbyadgeist.showConsentBanner(): void

window.adsbyadgeist.version: string
```

---

## Appendix: How it works

### Single-page apps

Client-side route changes are handled automatically. New slots are filled, and
slots that are already showing an ad keep it. React Router, Next.js and Vue
Router need no extra setup.

### Ad formats

The ad's layout comes from the type of the ad space:

| Type        | Shows                                                               |
| ----------- | ------------------------------------------------------------------- |
| `banner`    | One image or video filling the slot                                 |
| `display`   | Image or video with headline and text, laid out to fit the slot     |
| `companion` | Video with a mute toggle, a banner below it, and advertiser details |

Ads render in an isolated frame, so your page's CSS doesn't affect the ad and
the ad's CSS doesn't affect your page.

### Views and clicks

A view is counted when at least 50% of the ad is on screen for 1 second
(2 seconds for video). Clicks are counted when the user clicks the ad.

---

## Troubleshooting

### Slot stays empty

1. **Check `data-ad-slot`** is present and matches your ad space ID.
2. **Check the slot meets the minimum size** for its type (see the table in
   [1.2](#12-place-an-ad-slot)).
3. **Check `data-publisher-id`** is on the SDK's own `<script>` tag.
4. **Check the ad space isn't used twice** on the page. Only the first slot is
   filled.
5. **Check the `.ad-container` rule.** If the page has an `ad-container`
   element, the slot must be inside it.
6. **There may be no ad to show.** When no campaign matches, the slot stays
   hidden.

### Views look inflated or missing

If your app re-mounts the `<ins>` element on every route change, each mount
counts as a new ad request and a new view. Keep the element mounted; slots that
are already showing an ad keep it across route changes.
