@kili-ai/api
v1.0.7
Published
Kili JS SDK for context collection and ad fetching
Readme
@kili-ai/api
Publisher SDK for Kili ads.
Talks to the public api.kili service (not the internal dashboard server.kili). Default origin is https://api-dev.trykili.ai.
KiliContext.collect() gathers device signals. Kili.getAds() reads kiliContext from your server request and POSTs to Kili.
Install
pnpm add @kili-ai/apinpm install @kili-ai/apiClient — chat UI
import { KiliContext } from "@kili-ai/api";
const kiliContext = new KiliContext().collect({
sessionId: chatSession.id,
user: { userId: currentUser.id },
});
fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages, kiliContext }),
});sessionId (UUID) and user.userId are required. Device fields (ua, timezone, locale) are collected automatically in the browser. You can pass them explicitly to override.
Server — fetch ads
import { Kili, KiliError } from "@kili-ai/api";
const kili = new Kili({ apiKey: process.env.KILI_API_KEY! });
app.post("/api/chat", async (req, res) => {
const { messages } = req.body;
const adPromise = kili
.getAds(req, messages, [
{ placement: "below_response", placementId: "main" },
])
.catch((error) => {
if (error instanceof KiliError) {
return { ads: [] };
}
throw error;
});
// stream your LLM response...
const { ads } = await adPromise;
res.write(`data: ${JSON.stringify({ type: "done", ads })}\n\n`);
res.end();
});getAds throws KiliError with statusCode. Wrap with try/catch or Promise.allSettled so chat is not blocked.
| Case | statusCode |
|------|--------------|
| Missing apiKey | 401 |
| Missing kiliContext | 400 |
| Kili API 400 / 401 / 5xx | body statusCode or HTTP status |
| Timeout | 408 |
| Network | 503 |
Next.js route handler
import { Kili } from "@kili-ai/api";
const kili = new Kili({ apiKey: process.env.KILI_API_KEY! });
export async function POST(request: Request) {
const body = await request.json();
try {
const { ads } = await kili.getAds(
{ body, headers: Object.fromEntries(request.headers) },
body.messages,
[{ placement: "below_response", placementId: "main" }],
);
return Response.json({ ads });
} catch {
return Response.json({ ads: [] });
}
}new Kili(opts)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required for getAds | Kili API key (kil_...) |
| baseUrl | string | https://api-dev.trykili.ai | Public api.kili origin |
| timeoutMs | number | 3000 | Request timeout |
| relevancy | number | 0.2 | Min relevancy (0–1) |
| excludedTopics | string[] | [] | Topics to skip |
Point baseUrl at a local api.kili with { baseUrl: "http://localhost:3010" }. Dashboard / auth traffic belongs on server.kili (http://localhost:3011) and is outside this SDK.
kili.getAds(req, messages, placements, overrides?)
Reads kiliContext from req.body, forwards the end-user IP as x-forwarded-for (x-forwarded-for → x-real-ip → socket), and POSTs camelCase JSON to {baseUrl}/ads with x-kili-api-key.
overrides is a partial TKiliOptions (apiKey, baseUrl, timeoutMs, relevancy, excludedTopics) applied for that call only.
Placements: above_response | below_response | loader_text | loader_div | left_response | right_response.
Empty fill is { ads: [] } (HTTP 200). That is success, not an error.
Tracking
Each ad in the response may include absolute tracking URLs minted by api.kili (API_APP_BASE_URL, e.g. https://api-dev.trykili.ai):
impUrl— impression beacon (GET /ack?p=…)clickUrl— click redirect (GET /track?p=…). Navigate through this URL (not rawurl): the redirect records the click and 302s to the landing page with?pxclid=(PIXEL_CLICK_ID_QUERY_PARAM). The advertiser pixel maps that to wire/CAPIuserData.klclid(ATTRIBUTION_CLICK_ID_KEY).
Pass these through to your client unchanged. Billing is handled client-side via GET beacons (typically by @kili-ai/react); this SDK does not fire them.
Attribution → CAPI: on the advertiser site, getCAPIData() returns camelCase { userData, eventSourceUrl, clientContext } with klclid. The advertiser backend merges that with order PII and POSTs to GATEWAY_EVENTS_PATH (/gateway/events) on api.kili using an advertiser API key (?api_key= or Authorization: Bearer).
kiliContext.sessionId must be a UUID — the backend validates this on POST /ads.
Data flow
┌──────────────────┐ kiliContext in body ┌──────────────────┐ POST /ads ┌────────────────────────────┐
│ Client code │ ─────────────────────────▶ │ Your server │ ───────────▶ │ api.kili │
│ KiliContext │ │ kili.getAds() │ │ (api-dev.trykili.ai / :3010) │
└──────────────────┘ └──────────────────┘ └────────────────────────────┘Development
Run from this repo (pkg.api.kili):
pnpm install
pnpm check
pnpm test
pnpm buildReleasing
Changesets live in this repo only. Until 1.0.0: patch = fix, minor = feature or breaking, major = 1.0.0.
pnpm changeset # add a changeset on a feature branch
pnpm changeset:status # preview the bumpOn merge to main, CI opens a chore: update version PR (pnpm changeset:version). Merging that PR publishes to npm (pnpm changeset:publish), tags vX.Y.Z, and creates a GitHub Release. Set the GitHub Actions secret NPM_TOKEN.
