@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 +390 -281
- package/dist/index.d.mts +188 -193
- package/dist/index.d.ts +188 -193
- package/dist/index.js +43 -129
- package/dist/index.mjs +43 -129
- package/package.json +1 -1
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
|
-
|
|
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',
|
|
27
|
-
userId: 'user-456',
|
|
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
|
-
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Migrating from v0
|
|
62
|
+
|
|
63
|
+
If you're upgrading from a previous version, there are key changes:
|
|
37
64
|
|
|
38
|
-
|
|
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
|
-
|
|
72
|
+
// Before (v0)
|
|
73
|
+
const ad = await client.getAd({ messages });
|
|
42
74
|
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
108
|
+
| `relevancy` | `number` | `null` | Minimum relevancy score (0.1-1) |
|
|
67
109
|
|
|
68
110
|
---
|
|
69
111
|
|
|
70
|
-
##
|
|
112
|
+
## Integration Types
|
|
71
113
|
|
|
72
|
-
|
|
114
|
+
Choose the integration type that matches your application. All types share a **common schema**.
|
|
73
115
|
|
|
74
|
-
###
|
|
116
|
+
### Common Fields
|
|
75
117
|
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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.
|
|
99
|
-
//
|
|
212
|
+
const response = await client.getAd({
|
|
213
|
+
// Common fields
|
|
100
214
|
messages: [
|
|
101
|
-
{ role: 'user', content: '
|
|
102
|
-
{ role: 'assistant', content: 'What is your budget?' }
|
|
215
|
+
{ role: 'user', content: 'What are the best practices for React performance?' }
|
|
103
216
|
],
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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: '
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
247
|
+
---
|
|
139
248
|
|
|
140
|
-
|
|
249
|
+
### IDE / CLI
|
|
141
250
|
|
|
142
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
264
|
+
</details>
|
|
156
265
|
|
|
157
|
-
|
|
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.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### Mobile
|
|
308
|
+
|
|
309
|
+
**For:** iOS apps, Android apps, React Native, Flutter
|
|
310
|
+
|
|
311
|
+
#### Mobile-specific Fields
|
|
175
312
|
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
325
|
+
const response = await client.getAd({
|
|
326
|
+
// Common fields
|
|
181
327
|
messages: [
|
|
182
|
-
{ role: '
|
|
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-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
370
|
+
### General
|
|
211
371
|
|
|
212
|
-
|
|
372
|
+
**For:** Any integration not covered above.
|
|
213
373
|
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
##
|
|
403
|
+
## Response Types
|
|
238
404
|
|
|
239
|
-
###
|
|
240
|
-
|
|
241
|
-
Returned by `contextualAd()`, `summaryAd()`, `nonContextualAd()`, and `render()`.
|
|
405
|
+
### AdResponse
|
|
242
406
|
|
|
243
407
|
```typescript
|
|
244
|
-
interface
|
|
245
|
-
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
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
|
459
|
+
The client returns `null` on failure. Errors are logged to console.
|
|
371
460
|
|
|
372
461
|
```typescript
|
|
373
|
-
const response = await client.
|
|
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
|
-
// -
|
|
378
|
-
// -
|
|
379
|
-
// -
|
|
380
|
-
// -
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
535
|
+
5. **Handle null responses gracefully** — Don't break the UX when no ad is available.
|
|
453
536
|
|
|
454
|
-
|
|
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
|
|