@gravity-ai/api 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  The official Node.js/TypeScript SDK for the Gravity AI advertising API. Fetch contextually relevant ads based on conversation content.
4
4
 
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Migrating from v0](#migrating-from-v0)
10
+ - [Client Configuration](#client-configuration)
11
+ - [Integration Types](#integration-types)
12
+ - [Web](#web)
13
+ - [IDE / CLI](#ide--cli)
14
+ - [Mobile](#mobile)
15
+ - [General](#general)
16
+ - [Response Types](#response-types)
17
+ - [Error Handling](#error-handling)
18
+ - [Using with React](#using-with-react)
19
+ - [Best Practices](#best-practices)
20
+
21
+ ---
22
+
5
23
  ## Installation
6
24
 
7
25
  ```bash
@@ -10,6 +28,8 @@ npm install @gravity-ai/api
10
28
 
11
29
  > **Note:** Requires Node.js 18+
12
30
 
31
+ ---
32
+
13
33
  ## Quick Start
14
34
 
15
35
  ```typescript
@@ -17,14 +37,17 @@ import { Client } from '@gravity-ai/api';
17
37
 
18
38
  const client = new Client('your-api-key');
19
39
 
20
- // Contextual ads (v1 API)
21
- const response = await client.contextualAd({
40
+ const response = await client.getAd({
22
41
  messages: [
23
42
  { role: 'user', content: 'What are some good hiking trails?' },
24
- { role: 'assistant', content: 'Here are some popular trails...' }
25
43
  ],
26
- sessionId: 'session-123', // Recommended
27
- userId: 'user-456', // Recommended
44
+ sessionId: 'session-123',
45
+ userId: 'user-456',
46
+ numAds: 1,
47
+ render_context: {
48
+ placements: [{ placement: 'below_response' }]
49
+ },
50
+ testAd: true,
28
51
  });
29
52
 
30
53
  if (response) {
@@ -33,221 +56,366 @@ if (response) {
33
56
  }
34
57
  ```
35
58
 
36
- ## Client Configuration
59
+ ---
60
+
61
+ ## Migrating from v0
62
+
63
+ If you're upgrading from a previous version, there are key changes:
37
64
 
38
- ### Basic Initialization
65
+ **1. `sessionId` is now required**
66
+
67
+ **2. `render_context` object with `placements` array is now required**
68
+
69
+ **3. Response format changed to `ads` array**
39
70
 
40
71
  ```typescript
41
- import { Client } from '@gravity-ai/api';
72
+ // Before (v0)
73
+ const ad = await client.getAd({ messages });
42
74
 
43
- const client = new Client('your-api-key');
75
+ // After (v1)
76
+ const response = await client.getAd({
77
+ messages,
78
+ sessionId: 'session-123',
79
+ numAds: 1,
80
+ render_context: {
81
+ placements: [{ placement: 'below_response' }]
82
+ }
83
+ });
84
+ const ad = response?.ads[0];
44
85
  ```
45
86
 
46
- ### Advanced Configuration
87
+ ---
88
+
89
+ ## Client Configuration
47
90
 
48
91
  ```typescript
49
92
  import { Client } from '@gravity-ai/api';
50
93
 
94
+ // Basic
95
+ const client = new Client('your-api-key');
96
+
97
+ // Advanced
51
98
  const client = new Client('your-api-key', {
52
- // Topics to exclude globally (optional)
53
- excludedTopics: ['politics', 'religion'],
54
-
55
- // Minimum relevancy threshold 0-1 (optional)
56
- relevancy: 0.8,
99
+ excludedTopics: ['politics', 'religion'], // Global exclusions
100
+ relevancy: 0.6, // Min relevancy threshold (0.1-1)
57
101
  });
58
102
  ```
59
103
 
60
- #### Configuration Options
61
-
62
104
  | Option | Type | Default | Description |
63
105
  |--------|------|---------|-------------|
64
106
  | `endpoint` | `string` | `'https://server.trygravity.ai'` | API endpoint URL |
65
107
  | `excludedTopics` | `string[]` | `[]` | Topics to exclude from ad matching |
66
- | `relevancy` | `number \| null` | `null` | Minimum relevancy score (0-1) |
108
+ | `relevancy` | `number` | `null` | Minimum relevancy score (0.1-1) |
67
109
 
68
110
  ---
69
111
 
70
- ## v1 API Methods
112
+ ## Integration Types
71
113
 
72
- The v1 API provides explicit endpoints for different ad types with multi-ad support.
114
+ Choose the integration type that matches your application. All types share a **common schema**.
73
115
 
74
- ### `contextualAd()` — Contextual Ads
116
+ ### Common Fields
75
117
 
76
- Fetch ads based on conversation context. Requires `messages` array.
118
+ | Field | Type | Description |
119
+ |-------|------|-------------|
120
+ | `messages` | `MessageObject[]` | Conversation context. Array of `{role, content}` objects. **Required.** |
121
+ | `sessionId` | `string` | Session identifier for frequency capping. **Required.** |
122
+ | `render_context` | `RenderContextObject` | Describes how the ad will be rendered in your app. **Required.** |
123
+ | `userId` | `string` | Unique user identifier. |
124
+ | `testAd` | `boolean` | Returns real ad without tracking. Use for testing. |
125
+ | `numAds` | `number` | Number of ads to return (1-3). Must match `placements` length. |
77
126
 
78
- ```typescript
79
- const response = await client.contextualAd({
80
- messages: [
81
- { role: 'user', content: 'I need help finding a new laptop.' },
82
- { role: 'assistant', content: 'What is your budget?' }
83
- ],
84
- sessionId: 'session-123', // Recommended
85
- userId: 'user-456', // Recommended
86
- numAds: 1, // 1-3, default 1
87
- });
127
+ #### What is `render_context`?
88
128
 
89
- if (response) {
90
- const ad = response.ads[0];
91
- console.log(ad.adText);
92
- }
93
- ```
129
+ The `render_context` object describes how the ad will be rendered in your app, so Gravity can generate a more contextually relevant ad.
130
+
131
+ For example:
132
+ - **`placements`** — Where you plan to show the ad (below the response, in a sidebar, etc.)
133
+ - **`max_ad_length`** — Character limit you can display, so we don't send copy that gets truncated
134
+ - **`supports_markdown`** — Whether you can render formatted text
135
+ - **`supports_images`** — Whether you can display brand logos or product images
136
+
137
+ The more context you provide, the better we can optimize the ad copy, format, and creative for your specific integration.
138
+
139
+ <details>
140
+ <summary><strong>RenderContextObject</strong></summary>
141
+
142
+ | Field | Type | Description |
143
+ |-------|------|-------------|
144
+ | `placements` | `PlacementObject[]` | Array of placement objects (1-3). Length must match `numAds`. **Required.** |
145
+ | `max_ad_length` | `number` | Max characters for ad text. |
146
+ | `supports_markdown` | `boolean` | Whether markdown rendering is supported. |
147
+ | `supports_links` | `boolean` | Whether clickable links are supported. |
148
+ | `supports_images` | `boolean` | Whether images can be displayed. |
149
+ | `supports_cta_button` | `boolean` | Whether CTA buttons are supported. |
150
+
151
+ </details>
152
+
153
+ <details>
154
+ <summary><strong>PlacementObject</strong></summary>
155
+
156
+ | Field | Type | Description |
157
+ |-------|------|-------------|
158
+ | `placement` | `string` | **Required.** One of: `"above_response"`, `"below_response"`, `"inline_response"`, `"left_response"`, `"right_response"` |
159
+ | `placement_id` | `string` | Optional tracking ID for this ad slot. |
160
+
161
+ </details>
162
+
163
+ <details>
164
+ <summary><strong>DeviceObject</strong></summary>
165
+
166
+ | Field | Type | Description |
167
+ |-------|------|-------------|
168
+ | `ip` | `string` | IP address for geo-targeting. **Required.** |
169
+ | `ua` | `string` | User agent string. Enables browser/device detection. |
170
+ | `os` | `string` | Operating system: `"macos"`, `"windows"`, `"linux"`, `"iOS"`, `"Android"`. |
171
+ | `os_version` | `string` | OS version (e.g., `"17.2"`). |
172
+ | `browser` | `string` | Browser name: `"chrome"`, `"safari"`, `"firefox"`. |
173
+ | `device_type` | `string` | `"desktop"`, `"mobile"`, `"tablet"`. |
174
+ | `device_model` | `string` | Device model (e.g., `"iPhone 15 Pro"`). |
175
+ | `ifa` | `string` | IDFA/GAID for cross-app attribution. **Higher CPMs.** |
176
+ | `timezone` | `string` | IANA timezone (e.g., `"America/New_York"`). |
177
+ | `locale` | `string` | Locale (e.g., `"en-US"`). |
178
+ | `connection_type` | `string` | `"wifi"` or `"cellular"`. |
179
+
180
+ </details>
181
+
182
+ <details>
183
+ <summary><strong>UserObject</strong></summary>
184
+
185
+ | Field | Type | Description |
186
+ |-------|------|-------------|
187
+ | `email` | `string` | User's email for identity matching. **Higher CPMs.** |
188
+ | `gender` | `string` | `"male"`, `"female"`, `"other"`. |
189
+ | `age` | `string` | Age range: `"18-24"`, `"25-34"`, `"35-44"`, etc. |
190
+ | `keywords` | `string` | Comma-separated interest keywords. |
191
+ | `subscription_tier` | `string` | `"free"`, `"pro"`, `"premium"`, `"enterprise"`. |
192
+ | `user_created_at` | `string` | Account creation date (ISO 8601). |
193
+ | `user_interests` | `string[]` | Array of user interests for targeting. |
94
194
 
95
- #### Full Request with All Options
195
+ </details>
196
+
197
+ ---
198
+
199
+ ### Web
200
+
201
+ **For:** Chat interfaces, AI assistants, web apps with conversational UI
202
+
203
+ #### Web-specific Fields
204
+
205
+ | Field | Type | Description |
206
+ |-------|------|-------------|
207
+ | `web.referrer` | `string` | Referring URL. Enables traffic source targeting. |
208
+
209
+ #### Example
96
210
 
97
211
  ```typescript
98
- const response = await client.contextualAd({
99
- // Required: conversation messages
212
+ const response = await client.getAd({
213
+ // Common fields
100
214
  messages: [
101
- { role: 'user', content: 'I need help finding a new laptop.' },
102
- { role: 'assistant', content: 'What is your budget?' }
215
+ { role: 'user', content: 'What are the best practices for React performance?' }
103
216
  ],
104
-
105
- // Recommended: session/user tracking (improves ad relevance & revenue)
106
- sessionId: 'session-123',
107
- userId: 'user-456',
108
-
109
- // Optional: user information for targeting
217
+ sessionId: 'session-web-001',
218
+ userId: 'user-web-789',
219
+ numAds: 1,
220
+ testAd: true,
221
+ render_context: {
222
+ placements: [
223
+ { placement: 'right_response', placement_id: 'sidebar-1' }
224
+ ],
225
+ max_ad_length: 200
226
+ },
227
+
228
+ // Web-specific fields
110
229
  user: {
111
- uid: 'user-123', // Unique user identifier
112
- gender: 'male', // 'male' | 'female' | 'other'
113
- age: '25-34', // Age range string
114
- keywords: 'tech,gadgets', // User interest keywords
230
+ email: 'webuser@example.com',
231
+ subscription_tier: 'pro'
115
232
  },
116
-
117
- // Optional: device information
118
233
  device: {
119
- ip: '1.2.3.4', // User IP address
120
- country: 'US', // ISO country code
121
- ua: 'Mozilla/5.0...', // User agent string
122
- os: 'macOS', // Operating system
123
- ifa: 'device-ad-id', // Advertising identifier
234
+ ip: '203.0.113.50',
235
+ ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
236
+ browser: 'chrome',
237
+ device_type: 'desktop',
238
+ timezone: 'Europe/London',
239
+ locale: 'en-GB'
124
240
  },
125
-
126
- // Optional: ad request settings
127
- excludedTopics: ['politics'], // Topics to exclude
128
- relevancy: 0.8, // Min relevancy threshold (0-1)
129
- numAds: 1, // Number of ads (1-3)
130
- testAd: false, // Return test ad when true
131
-
132
- // Optional: custom fields (open-ended, passed to matching)
133
- interests: ['coding', 'apple', 'software development'],
134
- summary: 'User wants a laptop for software development',
241
+ web: {
242
+ referrer: 'https://google.com'
243
+ }
135
244
  });
136
245
  ```
137
246
 
138
- ### `summaryAd()` — Summary-Based Ads
247
+ ---
139
248
 
140
- Fetch ads based on a search query or summary string. Requires `queryString`.
249
+ ### IDE / CLI
141
250
 
142
- ```typescript
143
- const response = await client.summaryAd({
144
- queryString: 'User wants a laptop for software development',
145
- sessionId: 'session-123',
146
- userId: 'user-456',
147
- });
251
+ **For:** Dev tools like Cursor, Claude Code, GitHub Copilot, Windsurf and VS Code extensions
148
252
 
149
- if (response) {
150
- const ad = response.ads[0];
151
- console.log(ad.adText);
152
- }
153
- ```
253
+ #### IDE-specific Fields
254
+
255
+ <details>
256
+ <summary><strong>IDEObject</strong></summary>
257
+
258
+ | Field | Type | Description |
259
+ |-------|------|-------------|
260
+ | `name` | `string` | IDE name: `"cursor"`, `"vscode"`, `"intellij"`. |
261
+ | `session_duration_ms` | `number` | Time since IDE opened. Helps with engagement-based targeting. |
262
+ | `active_file_language` | `string` | Current file language (e.g., `"typescript"`). |
154
263
 
155
- ### `nonContextualAd()` — Non-Contextual Ads
264
+ </details>
156
265
 
157
- Fetch ads without context matching. Ideal for:
158
- - **Integration testing** — Test your ad implementation on a subset of users before rolling out contextual ads
159
- - **Brand awareness** — Show ads without requiring conversation context
160
- - **Fallback placements** — Ad slots where context isn't available
266
+ #### Example
161
267
 
162
268
  ```typescript
163
- const response = await client.nonContextualAd({
164
- sessionId: 'session-123',
165
- userId: 'user-456',
166
- numAds: 2, // Request multiple ads
167
- });
269
+ const response = await client.getAd({
270
+ // Common fields
271
+ messages: [
272
+ { role: 'user', content: 'How do I set up authentication in my Express app?' }
273
+ ],
274
+ sessionId: 'session-ide-001',
275
+ userId: 'user-dev-123',
276
+ numAds: 1,
277
+ testAd: true,
278
+ render_context: {
279
+ placements: [
280
+ { placement: 'below_response' }
281
+ ],
282
+ max_ad_length: 280,
283
+ supports_markdown: true,
284
+ supports_links: true
285
+ },
168
286
 
169
- if (response) {
170
- response.ads.forEach(ad => console.log(ad.adText));
171
- }
287
+ // IDE-specific fields
288
+ user: {
289
+ email: 'developer@example.com'
290
+ },
291
+ device: {
292
+ ip: '192.168.1.100',
293
+ os: 'macos',
294
+ timezone: 'America/New_York',
295
+ locale: 'en-US'
296
+ },
297
+ ide: {
298
+ name: 'cursor',
299
+ session_duration_ms: 3600000,
300
+ active_file_language: 'typescript'
301
+ }
302
+ });
172
303
  ```
173
304
 
174
- ### `bid()` + `render()` — Two-Phase Flow
305
+ ---
306
+
307
+ ### Mobile
308
+
309
+ **For:** iOS apps, Android apps, React Native, Flutter
310
+
311
+ #### Mobile-specific Fields
175
312
 
176
- For publishers who need to know the clearing price before rendering the ad.
313
+ <details>
314
+ <summary><strong>AppObject</strong></summary>
315
+
316
+ | Field | Type | Description |
317
+ |-------|------|-------------|
318
+ | `version` | `string` | Your app version. |
319
+
320
+ </details>
321
+
322
+ #### Example
177
323
 
178
324
  ```typescript
179
- // Phase 1: Get bid price
180
- const bidResult = await client.bid({
325
+ const response = await client.getAd({
326
+ // Common fields
181
327
  messages: [
182
- { role: 'user', content: 'I need help with my code' }
328
+ { role: 'assistant', content: 'I found several Italian restaurants nearby.' },
329
+ { role: 'user', content: 'Which one has the best pasta?' }
183
330
  ],
184
- sessionId: 'session-123',
185
- });
331
+ sessionId: 'session-mobile-001',
332
+ userId: 'user-app-456',
333
+ numAds: 1,
334
+ testAd: true,
335
+ render_context: {
336
+ placements: [
337
+ { placement: 'inline_response' }
338
+ ],
339
+ max_ad_length: 150,
340
+ supports_images: true,
341
+ supports_cta_button: true
342
+ },
186
343
 
187
- if (bidResult) {
188
- console.log(`Clearing price: $${bidResult.bid} CPM`);
189
- console.log(`Bid ID: ${bidResult.bidId}`);
190
-
191
- // Decide whether to show ad based on price...
192
-
193
- // Phase 2: Render the ad
194
- const response = await client.render({
195
- bidId: bidResult.bidId,
196
- realizedPrice: bidResult.bid,
197
- });
198
-
199
- if (response) {
200
- const ad = response.ads[0];
201
- console.log(ad.adText);
344
+ // Mobile-specific fields
345
+ user: {
346
+ email: 'user@example.com',
347
+ subscription_tier: 'premium',
348
+ user_created_at: '2023-06-15T00:00:00Z',
349
+ user_interests: ['food', 'dining', 'travel']
350
+ },
351
+ device: {
352
+ ip: '198.51.100.23',
353
+ ua: 'MyApp/2.1.0 (iPhone; iOS 17.2)',
354
+ os: 'iOS',
355
+ os_version: '17.2',
356
+ device_model: 'iPhone 15 Pro',
357
+ ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC',
358
+ timezone: 'America/Los_Angeles',
359
+ locale: 'en-US',
360
+ connection_type: 'wifi'
361
+ },
362
+ app: {
363
+ version: '2.1.0'
202
364
  }
203
- }
365
+ });
204
366
  ```
205
367
 
206
- > **Note:** Bids expire after 60 seconds. Call `render()` promptly after `bid()`.
207
-
208
368
  ---
209
369
 
210
- ## v1 Request Parameters
370
+ ### General
211
371
 
212
- ### Base Parameters (All v1 Methods)
372
+ **For:** Any integration not covered above.
213
373
 
214
- | Parameter | Type | Required | Description |
215
- |-----------|------|----------|-------------|
216
- | `sessionId` | `string` | Recommended | Session identifier for tracking |
217
- | `userId` | `string` | Recommended | User identifier for frequency capping |
218
- | `device` | `DeviceObject` | - | Device/location info |
219
- | `user` | `UserObject` | - | User targeting info |
220
- | `excludedTopics` | `string[]` | - | Topics to exclude |
221
- | `relevancy` | `number` | - | Min relevancy (0-1) |
222
- | `numAds` | `number` | - | Number of ads (1-3, default 1) |
223
- | `testAd` | `boolean` | - | Return test ad when true |
374
+ #### Example
224
375
 
225
- ### Method-Specific Parameters
376
+ ```typescript
377
+ const response = await client.getAd({
378
+ // Common fields
379
+ messages: [
380
+ { role: 'user', content: 'Where can I buy marathon gear?' }
381
+ ],
382
+ sessionId: 'session-general-001',
383
+ userId: 'user-general-123',
384
+ numAds: 1,
385
+ testAd: true,
386
+ render_context: {
387
+ placements: [
388
+ { placement: 'below_response' }
389
+ ]
390
+ },
226
391
 
227
- | Method | Required Parameter | Description |
228
- |--------|-------------------|-------------|
229
- | `contextualAd()` | `messages: MessageObject[]` | Conversation history |
230
- | `summaryAd()` | `queryString: string` | Search/summary query |
231
- | `nonContextualAd()` | None | No context required |
232
- | `bid()` | `messages: MessageObject[]` | Conversation for bid |
233
- | `render()` | `bidId: string`, `realizedPrice: number` | From bid response |
392
+ // Optional fields
393
+ device: {
394
+ ip: '192.168.1.1',
395
+ timezone: 'America/New_York',
396
+ locale: 'en-US'
397
+ }
398
+ });
399
+ ```
234
400
 
235
401
  ---
236
402
 
237
- ## v1 Response Types
403
+ ## Response Types
238
404
 
239
- ### AdResponseV1
240
-
241
- Returned by `contextualAd()`, `summaryAd()`, `nonContextualAd()`, and `render()`.
405
+ ### AdResponse
242
406
 
243
407
  ```typescript
244
- interface AdResponseV1 {
245
- ads: AdV1[]; // Array of ads
408
+ interface AdResponse {
409
+ ads: Ad[]; // Array of ads (one per placement)
246
410
  numAds: number; // Number of ads returned
247
411
  totalPayout?: number; // Total payout across all ads
248
412
  }
413
+ ```
249
414
 
250
- interface AdV1 {
415
+ ### Ad
416
+
417
+ ```typescript
418
+ interface Ad {
251
419
  adText: string; // Ad copy text
252
420
  adId: string; // Unique ad identifier
253
421
  title?: string; // Ad title
@@ -255,164 +423,74 @@ interface AdV1 {
255
423
  brandImage?: string; // Brand logo URL
256
424
  url?: string; // Landing page URL
257
425
  favicon?: string; // Favicon URL
258
- impUrl?: string; // Impression tracking URL
259
- clickUrl?: string; // Click-through URL
260
- payout?: number; // Payout amount
261
- }
262
- ```
263
-
264
- ### BidResponse
265
-
266
- Returned by `bid()`.
267
-
268
- ```typescript
269
- interface BidResponse {
270
- bid: number; // Clearing price (CPM)
271
- bidId: string; // Use this in render()
426
+ impUrl?: string; // Impression tracking URL (null for testAd)
427
+ clickUrl?: string; // Click-through URL (null for testAd)
428
+ payout?: number; // Payout amount (null for testAd)
272
429
  }
273
430
  ```
274
431
 
275
- ---
276
-
277
- ## Common Types
278
-
279
- ### Message Object
432
+ ### Request Types
280
433
 
281
434
  ```typescript
282
435
  interface MessageObject {
283
436
  role: 'user' | 'assistant';
284
437
  content: string;
285
438
  }
286
- ```
287
439
 
288
- ### User Object
289
-
290
- ```typescript
291
- interface UserObject {
292
- uid?: string; // Unique user ID
293
- gender?: 'male' | 'female' | 'other'; // User gender
294
- age?: string; // Age range (e.g., '25-34')
295
- keywords?: string; // Interest keywords
440
+ interface RenderContextObject {
441
+ placements: PlacementObject[]; // Required, 1-3 items
442
+ max_ad_length?: number;
443
+ supports_markdown?: boolean;
444
+ supports_links?: boolean;
445
+ supports_images?: boolean;
446
+ supports_cta_button?: boolean;
296
447
  }
297
- ```
298
448
 
299
- ### Device Object
300
-
301
- ```typescript
302
- interface DeviceObject {
303
- ip: string; // IP address
304
- country: string; // ISO country code
305
- ua: string; // User agent
306
- os?: string; // Operating system
307
- ifa?: string; // Advertising ID
449
+ interface PlacementObject {
450
+ placement: 'above_response' | 'below_response' | 'inline_response' | 'left_response' | 'right_response';
451
+ placement_id?: string;
308
452
  }
309
453
  ```
310
454
 
311
455
  ---
312
456
 
313
- ## Legacy API (v0)
314
-
315
- The original `getAd()` method is still available for backward compatibility.
316
-
317
- ### Basic Request
318
-
319
- ```typescript
320
- const ad = await client.getAd({
321
- messages: [
322
- { role: 'user', content: 'I need help finding a new laptop.' },
323
- { role: 'assistant', content: 'What is your budget?' }
324
- ]
325
- });
326
- ```
327
-
328
- ### Full Request with All Options
329
-
330
- ```typescript
331
- const ad = await client.getAd({
332
- messages: [...],
333
- user: { uid: 'user-123', gender: 'male', age: '25-34' },
334
- device: { ip: '1.2.3.4', country: 'US', ua: 'Mozilla/5.0...' },
335
- excludedTopics: ['politics'],
336
- relevancy: 0.8,
337
- });
338
- ```
339
-
340
- ### Legacy Response
341
-
342
- ```typescript
343
- interface AdResponse {
344
- adText: string; // The ad copy to display
345
- impUrl?: string; // Impression tracking URL
346
- clickUrl?: string; // Click-through URL
347
- payout?: number; // Payout amount
348
- }
349
- ```
350
-
351
- ### Handling the Legacy Response
352
-
353
- ```typescript
354
- const ad = await client.getAd({ messages });
355
-
356
- if (ad) {
357
- console.log('Ad:', ad.adText);
358
-
359
- // Track impression
360
- if (ad.impUrl) {
361
- fetch(ad.impUrl);
362
- }
363
- } else {
364
- console.log('No ad available');
365
- }
366
- ```
367
-
368
457
  ## Error Handling
369
458
 
370
- The client handles errors gracefully and returns `null` on failure. Errors are logged to the console.
459
+ The client returns `null` on failure. Errors are logged to console.
371
460
 
372
461
  ```typescript
373
- const response = await client.contextualAd({ messages, sessionId: '...' });
462
+ const response = await client.getAd({
463
+ messages,
464
+ sessionId: 'session-123',
465
+ numAds: 1,
466
+ render_context: { placements: [{ placement: 'below_response' }] }
467
+ });
374
468
 
375
469
  // Returns null on:
376
470
  // - Network errors
377
- // - API errors (4xx, 5xx)
378
- // - No relevant ad (204)
379
- // - Invalid response data
380
- // - Expired bid (404 for render())
471
+ // - 401: Invalid API key
472
+ // - 422: Validation error (missing sessionId, render_context, or numAds/placements mismatch)
473
+ // - 204: No relevant ad found
474
+ // - 429: Rate limit exceeded
381
475
 
382
476
  if (!response) {
383
477
  // Handle gracefully - don't break the user experience
384
478
  }
385
479
  ```
386
480
 
387
- ## TypeScript
388
-
389
- Full TypeScript support with exported types:
481
+ | Status | Meaning |
482
+ |--------|---------|
483
+ | `200` | Ad(s) matched and returned successfully |
484
+ | `204` | No matching ads found (null response) |
485
+ | `401` | Invalid or missing API key |
486
+ | `422` | Validation error (e.g., missing `sessionId`, `render_context`, or `numAds`/`placements` mismatch) |
487
+ | `429` | Rate limit exceeded |
390
488
 
391
- ```typescript
392
- import { Client, ClientParams } from '@gravity-ai/api';
393
- import type {
394
- // v1 types
395
- ContextualAdParams,
396
- SummaryAdParams,
397
- NonContextualAdParams,
398
- BidParams,
399
- RenderParams,
400
- AdV1,
401
- AdResponseV1,
402
- BidResponse,
403
- // Common types
404
- MessageObject,
405
- DeviceObject,
406
- UserObject,
407
- // Legacy types
408
- AdParams,
409
- AdResponse,
410
- } from '@gravity-ai/api';
411
- ```
489
+ ---
412
490
 
413
491
  ## Using with React
414
492
 
415
- For React applications, consider using the companion package `@gravity-ai/react` which provides ready-to-use components with automatic tracking:
493
+ For React applications, use the companion package `@gravity-ai/react`:
416
494
 
417
495
  ```bash
418
496
  npm install @gravity-ai/api @gravity-ai/react
@@ -426,12 +504,15 @@ const client = new Client('your-api-key');
426
504
 
427
505
  function ChatApp() {
428
506
  const [ad, setAd] = useState(null);
429
-
507
+
430
508
  useEffect(() => {
431
- client.contextualAd({
509
+ client.getAd({
432
510
  messages,
433
511
  sessionId: 'session-123',
434
512
  userId: 'user-456',
513
+ numAds: 1,
514
+ render_context: { placements: [{ placement: 'below_response' }] },
515
+ testAd: true,
435
516
  }).then(res => setAd(res?.ads[0] || null));
436
517
  }, [messages]);
437
518
 
@@ -439,19 +520,47 @@ function ChatApp() {
439
520
  }
440
521
  ```
441
522
 
523
+ ---
524
+
442
525
  ## Best Practices
443
526
 
444
- 1. **Always include `sessionId` and `userId`** These directly impact publisher revenue through better frequency capping and ad relevance.
527
+ 1. **`sessionId` and `render_context` are required** Enable frequency capping and ad placement tracking.
528
+
529
+ 2. **`numAds` must match `placements` length** — Request 2 ads? Provide 2 placement objects.
530
+
531
+ 3. **Include `userId` when available** — Improves targeting and increases CPMs.
445
532
 
446
- 2. **Choose the right method**:
447
- - Chat/conversation → `contextualAd()`
448
- - Chat summary → `summaryAd()`
449
- - Brand awareness → `nonContextualAd()`
450
- - Custom auction → `bid()` + `render()`
533
+ 4. **Use `testAd: true` during development** — Prevents generating real impressions or clicks.
451
534
 
452
- 3. **Handle null responses gracefully** — Don't break the user experience when no ad is available.
535
+ 5. **Handle null responses gracefully** — Don't break the UX when no ad is available.
453
536
 
454
- 4. **Fire impression url** — Use the `impUrl` when the ad becomes visible to properly track impressions.
537
+ 6. **Fire `impUrl` when the ad is visible** Required for proper impression tracking.
538
+
539
+ 7. **Include `device.ip` for geo-targeting** — Enables location-based ads and higher CPMs.
540
+
541
+ 8. **Include `user.email` when available** — Enables identity matching for significantly higher CPMs.
542
+
543
+ ---
544
+
545
+ ## TypeScript
546
+
547
+ Full TypeScript support with exported types:
548
+
549
+ ```typescript
550
+ import { Client } from '@gravity-ai/api';
551
+ import type {
552
+ AdParams,
553
+ Ad,
554
+ AdResponse,
555
+ MessageObject,
556
+ RenderContextObject,
557
+ PlacementObject,
558
+ DeviceObject,
559
+ UserObject,
560
+ } from '@gravity-ai/api';
561
+ ```
562
+
563
+ ---
455
564
 
456
565
  ## License
457
566