@whereamimentioned/api-client 0.0.0-stage → 0.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/dist/index.d.ts +1374 -0
- package/dist/index.js +1105 -0
- package/package.json +50 -3
- package/README.md +0 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
// src/contract.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/enums.ts
|
|
5
|
+
var ORG_TYPES = ["agency", "brand"];
|
|
6
|
+
var ORG_ROLES = ["owner", "admin", "member", "viewer"];
|
|
7
|
+
var TIERS = ["free", "single", "pro", "agency", "enterprise"];
|
|
8
|
+
var INTENT_STAGES = ["awareness", "research", "comparison", "purchase", "branded"];
|
|
9
|
+
var PROMPT_TYPES = [
|
|
10
|
+
"category",
|
|
11
|
+
"competitor_comparison",
|
|
12
|
+
"use_case",
|
|
13
|
+
"persona_need",
|
|
14
|
+
"branded"
|
|
15
|
+
];
|
|
16
|
+
var PROMPT_SOURCES = ["generated", "template", "custom"];
|
|
17
|
+
var SENTIMENTS = ["positive", "neutral", "negative", "not_present"];
|
|
18
|
+
var SCAN_STATUSES = ["queued", "running", "done", "failed"];
|
|
19
|
+
var SCAN_MODES = ["grounded", "raw", "both"];
|
|
20
|
+
var CITATION_BUCKETS = ["owned", "competitor", "third_party"];
|
|
21
|
+
var RECOMMENDATION_STATUSES = ["open", "done", "dismissed"];
|
|
22
|
+
var RECOMMENDATION_TYPES = [
|
|
23
|
+
"losing_prompt",
|
|
24
|
+
"power_source",
|
|
25
|
+
"low_prominence",
|
|
26
|
+
"reputation_risk",
|
|
27
|
+
"citation_gap",
|
|
28
|
+
"ai_readiness"
|
|
29
|
+
];
|
|
30
|
+
var REPORT_FORMATS = ["pdf", "csv", "json"];
|
|
31
|
+
var API_SCOPES = [
|
|
32
|
+
"workspaces:read",
|
|
33
|
+
"workspaces:write",
|
|
34
|
+
"scans:read",
|
|
35
|
+
"scans:write",
|
|
36
|
+
"reports:read",
|
|
37
|
+
"prompts:read",
|
|
38
|
+
"prompts:write",
|
|
39
|
+
"competitors:read",
|
|
40
|
+
"competitors:write",
|
|
41
|
+
"orgs:write"
|
|
42
|
+
];
|
|
43
|
+
var READ_ONLY_SCOPES = [
|
|
44
|
+
"workspaces:read",
|
|
45
|
+
"scans:read",
|
|
46
|
+
"reports:read",
|
|
47
|
+
"prompts:read",
|
|
48
|
+
"competitors:read"
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// src/contract.ts
|
|
52
|
+
var API_KEY_KINDS = ["org", "user"];
|
|
53
|
+
var API_KEY_ENVS = ["live", "test"];
|
|
54
|
+
var API_VERSION = 1;
|
|
55
|
+
var API_VERSION_HEADER = "X-Wam-Api-Version";
|
|
56
|
+
var IDEMPOTENT_REPLAY_HEADER = "X-Idempotent-Replay";
|
|
57
|
+
var API_ERROR_CODES = [
|
|
58
|
+
"bad_request",
|
|
59
|
+
// 400 (body not parseable as JSON at all)
|
|
60
|
+
"unauthorized",
|
|
61
|
+
// 401
|
|
62
|
+
"insufficient_role",
|
|
63
|
+
// 403
|
|
64
|
+
"insufficient_scope",
|
|
65
|
+
// 403
|
|
66
|
+
"feature_not_available",
|
|
67
|
+
// 403 (tier gate)
|
|
68
|
+
"not_found",
|
|
69
|
+
// 404
|
|
70
|
+
"conflict",
|
|
71
|
+
// 409
|
|
72
|
+
"scan_already_running",
|
|
73
|
+
// 409
|
|
74
|
+
"validation_failed",
|
|
75
|
+
// 422
|
|
76
|
+
"insufficient_credits",
|
|
77
|
+
// 402
|
|
78
|
+
"rate_limited",
|
|
79
|
+
// 429
|
|
80
|
+
"internal_error"
|
|
81
|
+
// 500
|
|
82
|
+
];
|
|
83
|
+
var requestId = z.string().regex(/^req_[0-9A-HJKMNP-TV-Z]{26}$/);
|
|
84
|
+
var apiErrorBody = z.object({
|
|
85
|
+
error: z.object({
|
|
86
|
+
code: z.enum(API_ERROR_CODES),
|
|
87
|
+
message: z.string(),
|
|
88
|
+
/** Correlates with Sentry and with the server log line. Always present. */
|
|
89
|
+
requestId: z.string(),
|
|
90
|
+
/** Code-specific structured payload. Never free-form prose. */
|
|
91
|
+
details: z.record(z.string(), z.unknown()).optional()
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
var insufficientCreditsDetails = z.object({
|
|
95
|
+
required: z.number().int(),
|
|
96
|
+
available: z.number().int(),
|
|
97
|
+
billingUrl: z.string().url()
|
|
98
|
+
});
|
|
99
|
+
var rateLimitedDetails = z.object({
|
|
100
|
+
retryAfterSeconds: z.number().int().nonnegative()
|
|
101
|
+
});
|
|
102
|
+
var scanAlreadyRunningDetails = z.object({
|
|
103
|
+
scanId: z.string(),
|
|
104
|
+
pollUrl: z.string()
|
|
105
|
+
});
|
|
106
|
+
var conflictDetails = z.object({
|
|
107
|
+
originalScanId: z.string().optional(),
|
|
108
|
+
originalOrgId: z.string().optional(),
|
|
109
|
+
limit: z.number().int().optional(),
|
|
110
|
+
current: z.number().int().optional(),
|
|
111
|
+
tier: z.enum(TIERS).optional(),
|
|
112
|
+
billingUrl: z.string().url().optional(),
|
|
113
|
+
fields: z.array(z.string()).optional()
|
|
114
|
+
});
|
|
115
|
+
var featureNotAvailableDetails = z.object({
|
|
116
|
+
billingUrl: z.string().url().optional(),
|
|
117
|
+
limit: z.number().int().optional(),
|
|
118
|
+
current: z.number().int().optional(),
|
|
119
|
+
allowedFormats: z.array(z.enum(REPORT_FORMATS)).optional()
|
|
120
|
+
});
|
|
121
|
+
var validationFailedDetails = z.object({
|
|
122
|
+
hint: z.string().optional(),
|
|
123
|
+
issues: z.array(z.object({ path: z.array(z.union([z.string(), z.number()])), message: z.string() })).optional()
|
|
124
|
+
});
|
|
125
|
+
var reportNotFoundDetails = z.object({
|
|
126
|
+
status: z.enum(SCAN_STATUSES)
|
|
127
|
+
});
|
|
128
|
+
var API_ERROR_DETAILS = {
|
|
129
|
+
insufficient_credits: insufficientCreditsDetails,
|
|
130
|
+
rate_limited: rateLimitedDetails,
|
|
131
|
+
scan_already_running: scanAlreadyRunningDetails,
|
|
132
|
+
conflict: conflictDetails,
|
|
133
|
+
feature_not_available: featureNotAvailableDetails,
|
|
134
|
+
validation_failed: validationFailedDetails
|
|
135
|
+
};
|
|
136
|
+
function parseErrorDetails(code, details) {
|
|
137
|
+
const parsed = API_ERROR_DETAILS[code].safeParse(details);
|
|
138
|
+
return parsed.success ? parsed.data : void 0;
|
|
139
|
+
}
|
|
140
|
+
var pagination = z.object({
|
|
141
|
+
/** Opaque. `null` on the last page, and that (never an empty `data`) is the end signal. */
|
|
142
|
+
nextCursor: z.string().nullable(),
|
|
143
|
+
hasMore: z.boolean(),
|
|
144
|
+
limit: z.number().int().min(1).max(100)
|
|
145
|
+
});
|
|
146
|
+
function page(item) {
|
|
147
|
+
return z.object({ data: z.array(item), pagination });
|
|
148
|
+
}
|
|
149
|
+
var pageQuery = z.object({
|
|
150
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
151
|
+
cursor: z.string().optional(),
|
|
152
|
+
/** 08 section 4.6: list endpoints only, intersected with the serializer allowlist. */
|
|
153
|
+
fields: z.array(z.string().min(1)).optional()
|
|
154
|
+
}).strict();
|
|
155
|
+
var scanListQuery = pageQuery.extend({
|
|
156
|
+
status: z.enum(SCAN_STATUSES).optional(),
|
|
157
|
+
/** ISO 8601 date or date-time, inclusive, against `created_at`. */
|
|
158
|
+
from: z.string().optional(),
|
|
159
|
+
to: z.string().optional()
|
|
160
|
+
}).strict();
|
|
161
|
+
var promptListQuery = pageQuery.extend({
|
|
162
|
+
enabled: z.boolean().optional(),
|
|
163
|
+
source: z.enum(PROMPT_SOURCES).optional(),
|
|
164
|
+
stage: z.enum(INTENT_STAGES).optional()
|
|
165
|
+
}).strict();
|
|
166
|
+
var answerListQuery = pageQuery.extend({
|
|
167
|
+
modelId: z.string().optional(),
|
|
168
|
+
mentioned: z.boolean().optional(),
|
|
169
|
+
lowConfidence: z.boolean().optional()
|
|
170
|
+
}).strict();
|
|
171
|
+
var recommendationListQuery = pageQuery.extend({
|
|
172
|
+
scanId: z.string().optional(),
|
|
173
|
+
status: z.enum(RECOMMENDATION_STATUSES).optional(),
|
|
174
|
+
type: z.enum(RECOMMENDATION_TYPES).optional()
|
|
175
|
+
}).strict();
|
|
176
|
+
var citationListQuery = pageQuery.extend({
|
|
177
|
+
scanId: z.string().optional(),
|
|
178
|
+
bucket: z.enum(CITATION_BUCKETS).optional()
|
|
179
|
+
}).strict();
|
|
180
|
+
var meResponse = z.object({
|
|
181
|
+
key: z.object({
|
|
182
|
+
id: z.string(),
|
|
183
|
+
name: z.string(),
|
|
184
|
+
/** Display only, e.g. "wam_live_7Ka3Qp2m". Never sufficient to authenticate. */
|
|
185
|
+
prefix: z.string(),
|
|
186
|
+
last4: z.string(),
|
|
187
|
+
kind: z.enum(API_KEY_KINDS),
|
|
188
|
+
env: z.enum(API_KEY_ENVS),
|
|
189
|
+
scopes: z.array(z.enum(API_SCOPES)),
|
|
190
|
+
roleCeiling: z.enum(ORG_ROLES),
|
|
191
|
+
expiresAt: z.string().nullable()
|
|
192
|
+
}),
|
|
193
|
+
org: z.object({
|
|
194
|
+
id: z.string(),
|
|
195
|
+
slug: z.string(),
|
|
196
|
+
name: z.string(),
|
|
197
|
+
type: z.enum(ORG_TYPES)
|
|
198
|
+
}),
|
|
199
|
+
actor: z.object({
|
|
200
|
+
userId: z.string(),
|
|
201
|
+
role: z.enum(ORG_ROLES),
|
|
202
|
+
effectiveRole: z.enum(ORG_ROLES)
|
|
203
|
+
}),
|
|
204
|
+
plan: z.object({
|
|
205
|
+
tier: z.enum(TIERS),
|
|
206
|
+
/** TIER_FEATURES[tier].label verbatim. */
|
|
207
|
+
label: z.string(),
|
|
208
|
+
mcp: z.boolean(),
|
|
209
|
+
/** File download formats only; format=json on the report route is never gated. */
|
|
210
|
+
exportFormats: z.array(z.enum(REPORT_FORMATS)),
|
|
211
|
+
promptsPerWorkspace: z.number().int(),
|
|
212
|
+
maxScanPrompts: z.number().int()
|
|
213
|
+
}),
|
|
214
|
+
credits: z.object({
|
|
215
|
+
balance: z.number().int(),
|
|
216
|
+
/** CREDIT_UNIT_DESCRIPTION served verbatim. Never paraphrase it in a client or a fixture. */
|
|
217
|
+
unit: z.string()
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
var organizationSummary = z.object({
|
|
221
|
+
id: z.string(),
|
|
222
|
+
slug: z.string(),
|
|
223
|
+
name: z.string(),
|
|
224
|
+
tier: z.enum(TIERS),
|
|
225
|
+
/** min(live role, roleCeiling): the power the caller actually gets, not the raw membership. */
|
|
226
|
+
effectiveRole: z.enum(ORG_ROLES),
|
|
227
|
+
workspaceCount: z.number().int()
|
|
228
|
+
});
|
|
229
|
+
var createOrganizationBody = z.object({
|
|
230
|
+
name: z.string().trim().min(1).max(120),
|
|
231
|
+
type: z.enum(ORG_TYPES)
|
|
232
|
+
}).strict();
|
|
233
|
+
var createOrganizationResult = z.object({
|
|
234
|
+
id: z.string(),
|
|
235
|
+
slug: z.string(),
|
|
236
|
+
name: z.string(),
|
|
237
|
+
type: z.enum(ORG_TYPES),
|
|
238
|
+
tier: z.enum(TIERS),
|
|
239
|
+
/** Always false on a fresh create; recomputed as tier !== "free" on an idempotent replay (08 section 7.3). */
|
|
240
|
+
usable: z.boolean(),
|
|
241
|
+
subscribeUrl: z.string().url(),
|
|
242
|
+
/** User-visible copy. Print it verbatim. Present only while `usable` is false (08 section 7.3). */
|
|
243
|
+
message: z.string().optional()
|
|
244
|
+
}).superRefine((v, ctx) => {
|
|
245
|
+
if (!v.usable && v.message === void 0) {
|
|
246
|
+
ctx.addIssue({ code: "custom", path: ["message"], message: "message is required while usable is false" });
|
|
247
|
+
}
|
|
248
|
+
if (v.usable && v.message !== void 0) {
|
|
249
|
+
ctx.addIssue({ code: "custom", path: ["message"], message: "message must be absent when usable is true" });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
var latestScanRef = z.object({
|
|
253
|
+
id: z.string(),
|
|
254
|
+
status: z.enum(SCAN_STATUSES),
|
|
255
|
+
finishedAt: z.string().nullable(),
|
|
256
|
+
score: z.number().nullable()
|
|
257
|
+
});
|
|
258
|
+
var workspaceSummary = z.object({
|
|
259
|
+
id: z.string(),
|
|
260
|
+
slug: z.string(),
|
|
261
|
+
name: z.string(),
|
|
262
|
+
brandName: z.string().nullable(),
|
|
263
|
+
defaultRegion: z.string(),
|
|
264
|
+
defaultLanguage: z.string(),
|
|
265
|
+
/** Boolean only. The key itself, encrypted or not, never leaves the server. */
|
|
266
|
+
usesOwnKey: z.boolean(),
|
|
267
|
+
promptCount: z.number().int(),
|
|
268
|
+
enabledPromptCount: z.number().int(),
|
|
269
|
+
/** null when the workspace has never completed a scan. */
|
|
270
|
+
latestScan: latestScanRef.nullable(),
|
|
271
|
+
/** null when nothing is queued or running. */
|
|
272
|
+
activeScanId: z.string().nullable(),
|
|
273
|
+
createdAt: z.string(),
|
|
274
|
+
updatedAt: z.string()
|
|
275
|
+
});
|
|
276
|
+
var workspaceDetail = workspaceSummary.extend({
|
|
277
|
+
competitorCount: z.number().int()
|
|
278
|
+
});
|
|
279
|
+
var createWorkspaceBody = z.object({
|
|
280
|
+
name: z.string().trim().min(1).max(120)
|
|
281
|
+
}).strict();
|
|
282
|
+
var competitor = z.object({
|
|
283
|
+
id: z.string(),
|
|
284
|
+
name: z.string(),
|
|
285
|
+
domain: z.string().nullable(),
|
|
286
|
+
aliases: z.array(z.string()),
|
|
287
|
+
createdAt: z.string()
|
|
288
|
+
});
|
|
289
|
+
var createCompetitorBody = z.object({
|
|
290
|
+
name: z.string().trim().min(1).max(120),
|
|
291
|
+
domain: z.string().trim().max(253).optional(),
|
|
292
|
+
aliases: z.array(z.string().trim().min(1).max(120)).max(20).default([])
|
|
293
|
+
}).strict();
|
|
294
|
+
var prompt = z.object({
|
|
295
|
+
id: z.string(),
|
|
296
|
+
text: z.string(),
|
|
297
|
+
intentStage: z.enum(INTENT_STAGES),
|
|
298
|
+
persona: z.string(),
|
|
299
|
+
type: z.enum(PROMPT_TYPES),
|
|
300
|
+
topic: z.string().nullable(),
|
|
301
|
+
isBranded: z.boolean(),
|
|
302
|
+
tags: z.array(z.string()),
|
|
303
|
+
source: z.enum(PROMPT_SOURCES),
|
|
304
|
+
enabled: z.boolean(),
|
|
305
|
+
createdAt: z.string()
|
|
306
|
+
});
|
|
307
|
+
var promptListResponse = page(prompt).extend({
|
|
308
|
+
promptsPerWorkspace: z.number().int(),
|
|
309
|
+
maxScanPrompts: z.number().int()
|
|
310
|
+
});
|
|
311
|
+
var createPromptBody = z.object({
|
|
312
|
+
text: z.string().trim().min(8).max(400),
|
|
313
|
+
intentStage: z.enum(INTENT_STAGES).default("research"),
|
|
314
|
+
persona: z.string().trim().max(80).optional(),
|
|
315
|
+
enabled: z.boolean().default(true)
|
|
316
|
+
}).strict();
|
|
317
|
+
var patchPromptBody = z.object({
|
|
318
|
+
enabled: z.boolean()
|
|
319
|
+
}).strict();
|
|
320
|
+
var deletePromptResult = z.object({
|
|
321
|
+
deleted: z.literal(true),
|
|
322
|
+
id: z.string()
|
|
323
|
+
});
|
|
324
|
+
var scanSummary = z.object({
|
|
325
|
+
id: z.string(),
|
|
326
|
+
status: z.enum(SCAN_STATUSES),
|
|
327
|
+
mode: z.enum(SCAN_MODES),
|
|
328
|
+
repetitions: z.number().int(),
|
|
329
|
+
/** modelIds.length as stored: the roster the scan actually ran with. */
|
|
330
|
+
modelCount: z.number().int(),
|
|
331
|
+
/** The stored prompt_count column, never recomputed. */
|
|
332
|
+
promptCount: z.number().int(),
|
|
333
|
+
creditsSpent: z.number().int().nullable(),
|
|
334
|
+
/** The overall ai_visibility_score, null until done. */
|
|
335
|
+
score: z.number().nullable(),
|
|
336
|
+
createdAt: z.string(),
|
|
337
|
+
startedAt: z.string().nullable(),
|
|
338
|
+
finishedAt: z.string().nullable(),
|
|
339
|
+
error: z.string().nullable()
|
|
340
|
+
});
|
|
341
|
+
var scanProgress = z.object({
|
|
342
|
+
done: z.number().int(),
|
|
343
|
+
/**
|
|
344
|
+
* Grows while the scan runs (adaptive re-runs are planned as it goes), so a naive done/total
|
|
345
|
+
* percentage moves backwards. Render a percentage only when `totalIsFinal` is true.
|
|
346
|
+
*/
|
|
347
|
+
total: z.number().int(),
|
|
348
|
+
/** status === "done" || status === "failed". */
|
|
349
|
+
totalIsFinal: z.boolean()
|
|
350
|
+
});
|
|
351
|
+
var scanStatus = z.object({
|
|
352
|
+
id: z.string(),
|
|
353
|
+
workspaceSlug: z.string(),
|
|
354
|
+
status: z.enum(SCAN_STATUSES),
|
|
355
|
+
mode: z.enum(SCAN_MODES),
|
|
356
|
+
repetitions: z.number().int(),
|
|
357
|
+
progress: scanProgress,
|
|
358
|
+
creditsReserved: z.number().int(),
|
|
359
|
+
creditsSpent: z.number().int().nullable(),
|
|
360
|
+
score: z.number().nullable(),
|
|
361
|
+
createdAt: z.string(),
|
|
362
|
+
startedAt: z.string().nullable(),
|
|
363
|
+
finishedAt: z.string().nullable(),
|
|
364
|
+
error: z.string().nullable()
|
|
365
|
+
});
|
|
366
|
+
var scanModelRef = z.object({
|
|
367
|
+
id: z.string(),
|
|
368
|
+
displayName: z.string()
|
|
369
|
+
});
|
|
370
|
+
var scanEstimate = z.object({
|
|
371
|
+
/** prompts x models.length x repetitions (x2 when mode is "both"). Tier-derived, never a constant. */
|
|
372
|
+
credits: z.number().int(),
|
|
373
|
+
prompts: z.number().int(),
|
|
374
|
+
models: z.array(scanModelRef),
|
|
375
|
+
repetitions: z.number().int(),
|
|
376
|
+
mode: z.enum(SCAN_MODES),
|
|
377
|
+
balance: z.number().int(),
|
|
378
|
+
sufficient: z.boolean(),
|
|
379
|
+
/** BYOK reserves zero credits and skips the ledger. */
|
|
380
|
+
usesOwnKey: z.boolean(),
|
|
381
|
+
quote: z.object({
|
|
382
|
+
token: z.string(),
|
|
383
|
+
expiresAt: z.string()
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
var estimateScanBody = z.object({}).strict();
|
|
387
|
+
var createScanBody = z.object({
|
|
388
|
+
/** Optional here, MANDATORY on the MCP run_scan tool (08 section 6.5). */
|
|
389
|
+
quote: z.string().optional()
|
|
390
|
+
}).strict();
|
|
391
|
+
var createScanResult = z.object({
|
|
392
|
+
id: z.string(),
|
|
393
|
+
status: z.enum(SCAN_STATUSES),
|
|
394
|
+
credits: z.number().int(),
|
|
395
|
+
prompts: z.number().int(),
|
|
396
|
+
models: z.number().int(),
|
|
397
|
+
/** The balance now on an idempotent replay, the balance at creation on a 201. */
|
|
398
|
+
balanceAfter: z.number().int(),
|
|
399
|
+
pollUrl: z.string()
|
|
400
|
+
});
|
|
401
|
+
var idempotencyKey = z.string().regex(/^[A-Za-z0-9_.:-]{1,128}$/);
|
|
402
|
+
var ANSWER_TEXT_MAX_CHARS = 1200;
|
|
403
|
+
var ANSWER_CITATIONS_MAX = 25;
|
|
404
|
+
var answerCitation = z.object({
|
|
405
|
+
url: z.string(),
|
|
406
|
+
domain: z.string()
|
|
407
|
+
});
|
|
408
|
+
var brandPresent = z.object({
|
|
409
|
+
name: z.string(),
|
|
410
|
+
is_target: z.boolean(),
|
|
411
|
+
rank: z.number().int().nullable()
|
|
412
|
+
});
|
|
413
|
+
var scanAnswer = z.object({
|
|
414
|
+
answerId: z.string(),
|
|
415
|
+
promptId: z.string(),
|
|
416
|
+
promptText: z.string(),
|
|
417
|
+
intentStage: z.enum(INTENT_STAGES),
|
|
418
|
+
modelId: z.string(),
|
|
419
|
+
modelName: z.string(),
|
|
420
|
+
grounded: z.boolean(),
|
|
421
|
+
brandMentioned: z.boolean(),
|
|
422
|
+
brandPosition: z.number().int().nullable(),
|
|
423
|
+
sentiment: z.enum(SENTIMENTS),
|
|
424
|
+
brandsPresent: z.array(brandPresent),
|
|
425
|
+
citations: z.array(answerCitation).max(ANSWER_CITATIONS_MAX),
|
|
426
|
+
confidence: z.number(),
|
|
427
|
+
lowConfidence: z.boolean(),
|
|
428
|
+
/** Truncated to 1200 characters. Full answer text is never returned by the API. */
|
|
429
|
+
answerText: z.string().max(ANSWER_TEXT_MAX_CHARS),
|
|
430
|
+
answerTextTruncated: z.boolean()
|
|
431
|
+
});
|
|
432
|
+
var recommendation = z.object({
|
|
433
|
+
id: z.string(),
|
|
434
|
+
type: z.enum(RECOMMENDATION_TYPES),
|
|
435
|
+
title: z.string(),
|
|
436
|
+
body: z.string(),
|
|
437
|
+
impact: z.number().int(),
|
|
438
|
+
effort: z.number().int(),
|
|
439
|
+
priority: z.number(),
|
|
440
|
+
status: z.enum(RECOMMENDATION_STATUSES),
|
|
441
|
+
scanId: z.string().nullable(),
|
|
442
|
+
createdAt: z.string()
|
|
443
|
+
});
|
|
444
|
+
var citation = z.object({
|
|
445
|
+
domain: z.string(),
|
|
446
|
+
count: z.number().int(),
|
|
447
|
+
share: z.number(),
|
|
448
|
+
bucket: z.enum(CITATION_BUCKETS)
|
|
449
|
+
});
|
|
450
|
+
var credits = z.object({
|
|
451
|
+
balance: z.number().int(),
|
|
452
|
+
tier: z.enum(TIERS),
|
|
453
|
+
monthlyCredits: z.number().int(),
|
|
454
|
+
/** CREDIT_UNIT_DESCRIPTION verbatim. */
|
|
455
|
+
unit: z.string(),
|
|
456
|
+
/** Scans remaining at the maximum scan size for your tier. Never "for workspace X". */
|
|
457
|
+
estimatedScansRemaining: z.number().int()
|
|
458
|
+
});
|
|
459
|
+
var revokeSelfQuery = z.object({ all: z.literal("cli").optional() }).strict();
|
|
460
|
+
var revokeSelfResult = z.object({
|
|
461
|
+
revoked: z.number().int(),
|
|
462
|
+
self: z.literal(true),
|
|
463
|
+
scope: z.literal("cli").optional()
|
|
464
|
+
});
|
|
465
|
+
var quotePayload = z.object({
|
|
466
|
+
/** Payload version. Bumped if any field below changes meaning. */
|
|
467
|
+
v: z.literal(1),
|
|
468
|
+
orgId: z.string(),
|
|
469
|
+
workspaceId: z.string(),
|
|
470
|
+
/** The key this quote was minted FOR. */
|
|
471
|
+
keyId: z.string(),
|
|
472
|
+
promptCount: z.number().int(),
|
|
473
|
+
/** The resolved roster, by id, not a count. */
|
|
474
|
+
modelIds: z.array(z.string()),
|
|
475
|
+
repetitions: z.number().int(),
|
|
476
|
+
mode: z.enum(SCAN_MODES),
|
|
477
|
+
creditCost: z.number().int(),
|
|
478
|
+
/** BYOK reserves zero credits, so a BYOK quote is not interchangeable with a metered one. */
|
|
479
|
+
byok: z.boolean(),
|
|
480
|
+
iat: z.number().int(),
|
|
481
|
+
exp: z.number().int()
|
|
482
|
+
});
|
|
483
|
+
var QUOTE_TTL_SECONDS = 300;
|
|
484
|
+
var reportData = z.object({
|
|
485
|
+
/** The scan the report describes (08 section 4.8): on the interface itself, not added by a client. */
|
|
486
|
+
scanId: z.string(),
|
|
487
|
+
brandName: z.string(),
|
|
488
|
+
workspaceName: z.string(),
|
|
489
|
+
scanDate: z.string(),
|
|
490
|
+
score: z.number(),
|
|
491
|
+
presenceRate: z.number(),
|
|
492
|
+
presenceCiLow: z.number(),
|
|
493
|
+
presenceCiHigh: z.number(),
|
|
494
|
+
sov: z.number(),
|
|
495
|
+
sentimentScore: z.number().nullable(),
|
|
496
|
+
brandCitationShare: z.number(),
|
|
497
|
+
perModel: z.array(z.object({ model: z.string(), score: z.number() })),
|
|
498
|
+
competitors: z.array(
|
|
499
|
+
z.object({
|
|
500
|
+
name: z.string(),
|
|
501
|
+
mentions: z.number(),
|
|
502
|
+
sov: z.number(),
|
|
503
|
+
isTarget: z.boolean()
|
|
504
|
+
})
|
|
505
|
+
),
|
|
506
|
+
citations: z.array(
|
|
507
|
+
z.object({
|
|
508
|
+
domain: z.string(),
|
|
509
|
+
count: z.number(),
|
|
510
|
+
share: z.number(),
|
|
511
|
+
/** Narrowed to CITATION_BUCKETS (08 section 4.8 change 2): one source for the filter and the OpenAPI enum. */
|
|
512
|
+
bucket: z.enum(CITATION_BUCKETS)
|
|
513
|
+
})
|
|
514
|
+
),
|
|
515
|
+
recommendations: z.array(
|
|
516
|
+
z.object({ title: z.string(), body: z.string(), priority: z.number() })
|
|
517
|
+
),
|
|
518
|
+
methodologyVersion: z.string()
|
|
519
|
+
});
|
|
520
|
+
var siteUrlInput = z.object({ url: z.string().url() }).strict();
|
|
521
|
+
var severityFinding = z.object({
|
|
522
|
+
severity: z.enum(["error", "warning"]),
|
|
523
|
+
message: z.string()
|
|
524
|
+
});
|
|
525
|
+
var crawlerAccessResult = z.object({
|
|
526
|
+
origin: z.string(),
|
|
527
|
+
robotsStatus: z.number().int(),
|
|
528
|
+
/** 4xx on robots.txt: everything is allowed. */
|
|
529
|
+
robotsMissing: z.boolean(),
|
|
530
|
+
/** 5xx: compliant crawlers treat this as full disallow. */
|
|
531
|
+
robotsServerError: z.boolean(),
|
|
532
|
+
truncated: z.boolean(),
|
|
533
|
+
sitemaps: z.array(z.string()),
|
|
534
|
+
bots: z.array(
|
|
535
|
+
z.object({
|
|
536
|
+
token: z.string(),
|
|
537
|
+
operator: z.string(),
|
|
538
|
+
category: z.string(),
|
|
539
|
+
/** Operator reputation, not a guarantee. robots.txt is voluntary. Never coerce to a boolean. */
|
|
540
|
+
respectsRobots: z.enum(["yes", "no", "unverified"]),
|
|
541
|
+
allowed: z.boolean(),
|
|
542
|
+
via: z.enum(["named", "wildcard", "none"]),
|
|
543
|
+
matchedLine: z.number().int().nullable(),
|
|
544
|
+
note: z.string().nullable(),
|
|
545
|
+
untrusted: z.object({
|
|
546
|
+
/** The verbatim robots.txt line that decided it. Third-party text, not instructions. */
|
|
547
|
+
matchedRule: z.string().nullable()
|
|
548
|
+
})
|
|
549
|
+
})
|
|
550
|
+
),
|
|
551
|
+
summary: z.object({ allowed: z.number().int(), blocked: z.number().int() })
|
|
552
|
+
});
|
|
553
|
+
var llmsTxtValidation = z.object({
|
|
554
|
+
origin: z.string(),
|
|
555
|
+
status: z.number().int(),
|
|
556
|
+
missing: z.boolean(),
|
|
557
|
+
score: z.number().int().min(0).max(100).nullable(),
|
|
558
|
+
findings: z.array(
|
|
559
|
+
z.object({
|
|
560
|
+
severity: z.enum(["error", "warning", "info"]),
|
|
561
|
+
message: z.string(),
|
|
562
|
+
line: z.number().int().optional()
|
|
563
|
+
})
|
|
564
|
+
),
|
|
565
|
+
stats: z.object({
|
|
566
|
+
lines: z.number().int(),
|
|
567
|
+
bytes: z.number().int(),
|
|
568
|
+
sections: z.number().int(),
|
|
569
|
+
entries: z.number().int(),
|
|
570
|
+
entriesWithDescription: z.number().int()
|
|
571
|
+
}).nullable(),
|
|
572
|
+
linksChecked: z.number().int(),
|
|
573
|
+
brokenLinks: z.array(z.object({ url: z.string(), status: z.number().int() })).max(30),
|
|
574
|
+
hasLlmsFullTxt: z.boolean()
|
|
575
|
+
});
|
|
576
|
+
var LLMS_TXT_DEPTHS = ["index", "full"];
|
|
577
|
+
var generateLlmsTxtInput = z.object({
|
|
578
|
+
url: z.string().url(),
|
|
579
|
+
depth: z.enum(LLMS_TXT_DEPTHS).default("index"),
|
|
580
|
+
siteName: z.string().max(120).optional(),
|
|
581
|
+
description: z.string().max(400).optional()
|
|
582
|
+
}).strict();
|
|
583
|
+
var llmsTxtGeneration = z.object({
|
|
584
|
+
untrusted: z.object({
|
|
585
|
+
/** The generated file. Untrusted: derived from third-party pages. */
|
|
586
|
+
content: z.string()
|
|
587
|
+
}),
|
|
588
|
+
depth: z.enum(LLMS_TXT_DEPTHS),
|
|
589
|
+
pageCount: z.number().int(),
|
|
590
|
+
skippedCount: z.number().int(),
|
|
591
|
+
discoveredVia: z.enum(["sitemap", "links", "sitemap+links"]),
|
|
592
|
+
timedOut: z.boolean(),
|
|
593
|
+
notices: z.array(z.string()),
|
|
594
|
+
totalWords: z.number().int().optional(),
|
|
595
|
+
estTokens: z.number().int().optional()
|
|
596
|
+
});
|
|
597
|
+
var sitemapGeneration = z.object({
|
|
598
|
+
untrusted: z.object({
|
|
599
|
+
/** The generated sitemap. Untrusted: URLs discovered on a third-party site. */
|
|
600
|
+
xml: z.string()
|
|
601
|
+
}),
|
|
602
|
+
urlCount: z.number().int(),
|
|
603
|
+
skipped: z.number().int(),
|
|
604
|
+
discoveredVia: z.enum(["sitemap", "links", "sitemap+links"]),
|
|
605
|
+
timedOut: z.boolean()
|
|
606
|
+
});
|
|
607
|
+
var chunkAnalysisResult = z.object({
|
|
608
|
+
url: z.string(),
|
|
609
|
+
mode: z.enum(["html", "text"]),
|
|
610
|
+
score: z.number().int().min(0).max(100),
|
|
611
|
+
errors: z.number().int(),
|
|
612
|
+
warnings: z.number().int(),
|
|
613
|
+
strengthCount: z.number().int(),
|
|
614
|
+
totalWords: z.number().int(),
|
|
615
|
+
totalTokens: z.number().int(),
|
|
616
|
+
topFixes: z.array(z.string()).max(5),
|
|
617
|
+
docFindings: z.array(severityFinding),
|
|
618
|
+
chunks: z.array(
|
|
619
|
+
z.object({
|
|
620
|
+
level: z.number().int().min(0).max(6),
|
|
621
|
+
untrusted: z.object({
|
|
622
|
+
/** Third-party page text, not an instruction. */
|
|
623
|
+
heading: z.string().nullable()
|
|
624
|
+
}),
|
|
625
|
+
words: z.number().int(),
|
|
626
|
+
tokens: z.number().int(),
|
|
627
|
+
paragraphs: z.number().int(),
|
|
628
|
+
longestParagraphWords: z.number().int(),
|
|
629
|
+
findings: z.array(severityFinding),
|
|
630
|
+
strengths: z.array(z.string())
|
|
631
|
+
})
|
|
632
|
+
).max(150)
|
|
633
|
+
});
|
|
634
|
+
var structuredDataValidation = z.object({
|
|
635
|
+
url: z.string(),
|
|
636
|
+
score: z.number().int().min(0).max(100),
|
|
637
|
+
entityCount: z.number().int(),
|
|
638
|
+
errors: z.number().int(),
|
|
639
|
+
warnings: z.number().int(),
|
|
640
|
+
passes: z.number().int(),
|
|
641
|
+
microdataCount: z.number().int(),
|
|
642
|
+
rdfaCount: z.number().int(),
|
|
643
|
+
blocks: z.array(
|
|
644
|
+
z.object({
|
|
645
|
+
index: z.number().int(),
|
|
646
|
+
syntaxError: z.string().optional(),
|
|
647
|
+
entities: z.array(
|
|
648
|
+
z.object({
|
|
649
|
+
type: z.string(),
|
|
650
|
+
untrusted: z.object({
|
|
651
|
+
/** Entity name as published on the page. Data, not instructions. */
|
|
652
|
+
name: z.string().optional()
|
|
653
|
+
}),
|
|
654
|
+
aiNote: z.string().optional(),
|
|
655
|
+
findings: z.array(
|
|
656
|
+
z.object({
|
|
657
|
+
severity: z.enum(["error", "warning", "pass", "info"]),
|
|
658
|
+
message: z.string()
|
|
659
|
+
})
|
|
660
|
+
)
|
|
661
|
+
})
|
|
662
|
+
)
|
|
663
|
+
})
|
|
664
|
+
).max(50)
|
|
665
|
+
});
|
|
666
|
+
var checkMetaInput = z.object({
|
|
667
|
+
url: z.string().url(),
|
|
668
|
+
/** Optional target keyword to check for presence and position. */
|
|
669
|
+
keyword: z.string().max(100).optional()
|
|
670
|
+
}).strict();
|
|
671
|
+
var metaCheck = z.object({
|
|
672
|
+
level: z.enum(["error", "warning", "suggestion", "pass"]),
|
|
673
|
+
message: z.string()
|
|
674
|
+
});
|
|
675
|
+
var metaAnalysisResult = z.object({
|
|
676
|
+
url: z.string(),
|
|
677
|
+
/** Third-party page text. DATA, never instructions. */
|
|
678
|
+
untrusted: z.object({
|
|
679
|
+
title: z.string().nullable(),
|
|
680
|
+
description: z.string().nullable(),
|
|
681
|
+
ogDescription: z.string().nullable(),
|
|
682
|
+
firstParagraph: z.string().nullable()
|
|
683
|
+
}),
|
|
684
|
+
score: z.number().int().min(0).max(100),
|
|
685
|
+
chars: z.number().int(),
|
|
686
|
+
pixels: z.number().int(),
|
|
687
|
+
desktopCutoff: z.number().int().nullable(),
|
|
688
|
+
mobileCutoff: z.number().int().nullable(),
|
|
689
|
+
checks: z.array(metaCheck),
|
|
690
|
+
openingParagraphChecks: z.array(metaCheck)
|
|
691
|
+
});
|
|
692
|
+
var SITE_ROUTES = {
|
|
693
|
+
checkCrawlers: "check-crawlers",
|
|
694
|
+
validateLlmsTxt: "validate-llms-txt",
|
|
695
|
+
generateLlmsTxt: "generate-llms-txt",
|
|
696
|
+
generateSitemap: "generate-sitemap",
|
|
697
|
+
validateStructuredData: "validate-structured-data",
|
|
698
|
+
checkMeta: "check-meta",
|
|
699
|
+
analyzeChunks: "analyze-chunks"
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// src/client.ts
|
|
703
|
+
var DEFAULT_BASE_URL = "https://whereamimentioned.com";
|
|
704
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
705
|
+
var SUPPORTED_API_VERSION = API_VERSION;
|
|
706
|
+
var MAX_GET_RETRIES = 2;
|
|
707
|
+
var RETRY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
708
|
+
var RETRY_BASE_MS = 500;
|
|
709
|
+
var RETRY_MAX_WAIT_MS = 3e4;
|
|
710
|
+
var ApiError = class extends Error {
|
|
711
|
+
status;
|
|
712
|
+
code;
|
|
713
|
+
requestId;
|
|
714
|
+
details;
|
|
715
|
+
retryAfterSeconds;
|
|
716
|
+
constructor(init) {
|
|
717
|
+
super(init.message);
|
|
718
|
+
this.name = "ApiError";
|
|
719
|
+
this.status = init.status;
|
|
720
|
+
this.code = init.code;
|
|
721
|
+
this.requestId = init.requestId;
|
|
722
|
+
this.details = init.details;
|
|
723
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var ApiTimeoutError = class extends Error {
|
|
727
|
+
timeoutMs;
|
|
728
|
+
method;
|
|
729
|
+
path;
|
|
730
|
+
constructor(method, path, timeoutMs) {
|
|
731
|
+
super(`${method} ${path} timed out after ${timeoutMs} ms`);
|
|
732
|
+
this.name = "ApiTimeoutError";
|
|
733
|
+
this.timeoutMs = timeoutMs;
|
|
734
|
+
this.method = method;
|
|
735
|
+
this.path = path;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
function parseRetryAfter(value) {
|
|
739
|
+
if (value === null) return null;
|
|
740
|
+
const s = value.trim();
|
|
741
|
+
if (/^\d+$/.test(s)) return Number(s);
|
|
742
|
+
const at = Date.parse(s);
|
|
743
|
+
if (Number.isNaN(at)) return null;
|
|
744
|
+
return Math.max(0, Math.ceil((at - Date.now()) / 1e3));
|
|
745
|
+
}
|
|
746
|
+
function parseApiVersion(value) {
|
|
747
|
+
if (value === null) return null;
|
|
748
|
+
const n = Number(value.trim());
|
|
749
|
+
return Number.isInteger(n) ? n : null;
|
|
750
|
+
}
|
|
751
|
+
function buildQuery(query) {
|
|
752
|
+
if (!query) return "";
|
|
753
|
+
const params = new URLSearchParams();
|
|
754
|
+
for (const [key, value] of Object.entries(query).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
755
|
+
if (value === void 0) continue;
|
|
756
|
+
if (Array.isArray(value)) {
|
|
757
|
+
if (value.length > 0) params.set(key, value.join(","));
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
params.set(key, String(value));
|
|
761
|
+
}
|
|
762
|
+
const s = params.toString();
|
|
763
|
+
return s ? `?${s}` : "";
|
|
764
|
+
}
|
|
765
|
+
function sleep(ms) {
|
|
766
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
767
|
+
}
|
|
768
|
+
function isAbortError(err) {
|
|
769
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
770
|
+
}
|
|
771
|
+
function seg(value) {
|
|
772
|
+
return encodeURIComponent(value);
|
|
773
|
+
}
|
|
774
|
+
function createWamClient(opts) {
|
|
775
|
+
const baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
776
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
777
|
+
const fetchImpl = opts.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
778
|
+
const state = {
|
|
779
|
+
apiVersion: null,
|
|
780
|
+
requestId: null,
|
|
781
|
+
lastResponse: null
|
|
782
|
+
};
|
|
783
|
+
function noteHeaders(res) {
|
|
784
|
+
const v = parseApiVersion(res.headers.get(API_VERSION_HEADER));
|
|
785
|
+
if (v !== null) state.apiVersion = v;
|
|
786
|
+
const rid = res.headers.get("X-Request-Id");
|
|
787
|
+
if (rid) state.requestId = rid;
|
|
788
|
+
state.lastResponse = {
|
|
789
|
+
status: res.status,
|
|
790
|
+
headers: res.headers,
|
|
791
|
+
replayed: res.headers.get(IDEMPOTENT_REPLAY_HEADER) === "true"
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function nonJsonError(res) {
|
|
795
|
+
return new ApiError({
|
|
796
|
+
status: res.status,
|
|
797
|
+
code: "unknown",
|
|
798
|
+
message: `Unexpected non-JSON response (HTTP ${res.status})`,
|
|
799
|
+
requestId: res.headers.get("X-Request-Id"),
|
|
800
|
+
details: { contentType: res.headers.get("content-type"), bodyPreview: res.text.slice(0, 200) },
|
|
801
|
+
retryAfterSeconds: parseRetryAfter(res.headers.get("Retry-After"))
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function toApiError(res) {
|
|
805
|
+
const retryAfterHeader = parseRetryAfter(res.headers.get("Retry-After"));
|
|
806
|
+
const headerRequestId = res.headers.get("X-Request-Id");
|
|
807
|
+
const text = res.text;
|
|
808
|
+
let parsed = void 0;
|
|
809
|
+
try {
|
|
810
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
811
|
+
} catch {
|
|
812
|
+
parsed = void 0;
|
|
813
|
+
}
|
|
814
|
+
const env = typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error : void 0;
|
|
815
|
+
if (typeof env === "object" && env !== null && typeof env.code === "string") {
|
|
816
|
+
const e = env;
|
|
817
|
+
const details = typeof e.details === "object" && e.details !== null && !Array.isArray(e.details) ? e.details : void 0;
|
|
818
|
+
const detailsRetry = details && typeof details.retryAfterSeconds === "number" ? details.retryAfterSeconds : null;
|
|
819
|
+
return new ApiError({
|
|
820
|
+
status: res.status,
|
|
821
|
+
code: e.code,
|
|
822
|
+
message: typeof e.message === "string" ? e.message : `HTTP ${res.status}`,
|
|
823
|
+
requestId: typeof e.requestId === "string" ? e.requestId : headerRequestId,
|
|
824
|
+
details,
|
|
825
|
+
retryAfterSeconds: retryAfterHeader ?? detailsRetry
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
return nonJsonError(res);
|
|
829
|
+
}
|
|
830
|
+
async function attempt(spec, url) {
|
|
831
|
+
const headers = {
|
|
832
|
+
Accept: spec.as === "bytes" ? "*/*" : "application/json",
|
|
833
|
+
"User-Agent": opts.userAgent
|
|
834
|
+
};
|
|
835
|
+
if (spec.auth !== false) {
|
|
836
|
+
headers[API_VERSION_HEADER] = String(API_VERSION);
|
|
837
|
+
if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
|
|
838
|
+
const org = spec.org === null ? void 0 : spec.org ?? opts.org;
|
|
839
|
+
if (org) headers["X-Wam-Org"] = org;
|
|
840
|
+
}
|
|
841
|
+
if (spec.idempotencyKey !== void 0) headers["Idempotency-Key"] = spec.idempotencyKey;
|
|
842
|
+
if (spec.body !== void 0) headers["Content-Type"] = "application/json";
|
|
843
|
+
const controller = new AbortController();
|
|
844
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
845
|
+
try {
|
|
846
|
+
const res = await fetchImpl(url, {
|
|
847
|
+
method: spec.method,
|
|
848
|
+
headers,
|
|
849
|
+
body: spec.body === void 0 ? void 0 : JSON.stringify(spec.body),
|
|
850
|
+
signal: controller.signal,
|
|
851
|
+
redirect: "manual"
|
|
852
|
+
});
|
|
853
|
+
noteHeaders(res);
|
|
854
|
+
if (res.ok && spec.as === "bytes") {
|
|
855
|
+
return { status: res.status, ok: true, headers: res.headers, text: "", bytes: await res.arrayBuffer() };
|
|
856
|
+
}
|
|
857
|
+
return { status: res.status, ok: res.ok, headers: res.headers, text: await res.text(), bytes: null };
|
|
858
|
+
} catch (err) {
|
|
859
|
+
if (isAbortError(err)) throw new ApiTimeoutError(spec.method, spec.path, timeoutMs);
|
|
860
|
+
throw err;
|
|
861
|
+
} finally {
|
|
862
|
+
clearTimeout(timer);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
async function request(spec) {
|
|
866
|
+
const url = `${baseUrl}${spec.path}${buildQuery(spec.query)}`;
|
|
867
|
+
const retryable = spec.method === "GET";
|
|
868
|
+
let attemptNo = 0;
|
|
869
|
+
state.lastResponse = null;
|
|
870
|
+
for (; ; ) {
|
|
871
|
+
let res;
|
|
872
|
+
try {
|
|
873
|
+
res = await attempt(spec, url);
|
|
874
|
+
} catch (err) {
|
|
875
|
+
if (retryable && attemptNo < MAX_GET_RETRIES && !(err instanceof ApiTimeoutError)) {
|
|
876
|
+
await sleep(Math.min(RETRY_BASE_MS * 2 ** attemptNo, RETRY_MAX_WAIT_MS));
|
|
877
|
+
attemptNo += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
throw err;
|
|
881
|
+
}
|
|
882
|
+
if (res.ok) {
|
|
883
|
+
if (spec.as === "bytes") return new Uint8Array(res.bytes ?? new ArrayBuffer(0));
|
|
884
|
+
if (!res.text) return void 0;
|
|
885
|
+
try {
|
|
886
|
+
return JSON.parse(res.text);
|
|
887
|
+
} catch {
|
|
888
|
+
throw nonJsonError(res);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (retryable && attemptNo < MAX_GET_RETRIES && RETRY_STATUSES.has(res.status)) {
|
|
892
|
+
const retryAfter = parseRetryAfter(res.headers.get("Retry-After"));
|
|
893
|
+
const backoff = RETRY_BASE_MS * 2 ** attemptNo;
|
|
894
|
+
const wait = retryAfter !== null ? Math.max(retryAfter * 1e3, backoff) : backoff;
|
|
895
|
+
await sleep(Math.min(wait, RETRY_MAX_WAIT_MS));
|
|
896
|
+
attemptNo += 1;
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
throw toApiError(res);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const v1 = "/api/v1";
|
|
903
|
+
const site = async (route, body) => request({ method: "POST", path: `/api/site/${route}`, body, auth: false, org: null });
|
|
904
|
+
return {
|
|
905
|
+
get lastApiVersion() {
|
|
906
|
+
return state.apiVersion;
|
|
907
|
+
},
|
|
908
|
+
get lastRequestId() {
|
|
909
|
+
return state.requestId;
|
|
910
|
+
},
|
|
911
|
+
get lastResponse() {
|
|
912
|
+
return state.lastResponse;
|
|
913
|
+
},
|
|
914
|
+
me: () => request({ method: "GET", path: `${v1}/me` }),
|
|
915
|
+
credits: () => request({ method: "GET", path: `${v1}/credits` }),
|
|
916
|
+
organizations: {
|
|
917
|
+
list: (q) => request({ method: "GET", path: `${v1}/organizations`, query: q, org: null }),
|
|
918
|
+
create: (body, idempotencyKey2) => request({
|
|
919
|
+
method: "POST",
|
|
920
|
+
path: `${v1}/organizations`,
|
|
921
|
+
body,
|
|
922
|
+
org: null,
|
|
923
|
+
idempotencyKey: idempotencyKey2
|
|
924
|
+
})
|
|
925
|
+
},
|
|
926
|
+
workspaces: {
|
|
927
|
+
list: (q) => request({ method: "GET", path: `${v1}/workspaces`, query: q }),
|
|
928
|
+
get: (slug) => request({ method: "GET", path: `${v1}/workspaces/${seg(slug)}` }),
|
|
929
|
+
// The org travels as X-Wam-Org, overriding opts.org, and is never put in the body.
|
|
930
|
+
create: async (orgSlug, body) => {
|
|
931
|
+
if (typeof orgSlug !== "string" || orgSlug.trim() === "") {
|
|
932
|
+
throw new TypeError("workspaces.create: orgSlug is required and must not be empty");
|
|
933
|
+
}
|
|
934
|
+
return request({ method: "POST", path: `${v1}/workspaces`, body, org: orgSlug });
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
scans: {
|
|
938
|
+
list: (ws, q) => request({ method: "GET", path: `${v1}/workspaces/${seg(ws)}/scans`, query: q }),
|
|
939
|
+
estimate: (ws) => request({ method: "POST", path: `${v1}/workspaces/${seg(ws)}/scans/estimate`, body: {} }),
|
|
940
|
+
create: (ws, body, idempotencyKey2) => request({
|
|
941
|
+
method: "POST",
|
|
942
|
+
path: `${v1}/workspaces/${seg(ws)}/scans`,
|
|
943
|
+
body,
|
|
944
|
+
idempotencyKey: idempotencyKey2
|
|
945
|
+
}),
|
|
946
|
+
get: (scanId) => request({ method: "GET", path: `${v1}/scans/${seg(scanId)}` }),
|
|
947
|
+
answers: (scanId, q) => request({ method: "GET", path: `${v1}/scans/${seg(scanId)}/answers`, query: q }),
|
|
948
|
+
report: (scanId, format) => format === "json" ? request({ method: "GET", path: `${v1}/scans/${seg(scanId)}/report`, query: { format } }) : request({
|
|
949
|
+
method: "GET",
|
|
950
|
+
path: `${v1}/scans/${seg(scanId)}/report`,
|
|
951
|
+
query: { format },
|
|
952
|
+
as: "bytes"
|
|
953
|
+
})
|
|
954
|
+
},
|
|
955
|
+
prompts: {
|
|
956
|
+
list: (ws, q) => request({ method: "GET", path: `${v1}/workspaces/${seg(ws)}/prompts`, query: q }),
|
|
957
|
+
create: (ws, body) => request({ method: "POST", path: `${v1}/workspaces/${seg(ws)}/prompts`, body }),
|
|
958
|
+
patch: (ws, promptId, body) => request({
|
|
959
|
+
method: "PATCH",
|
|
960
|
+
path: `${v1}/workspaces/${seg(ws)}/prompts/${seg(promptId)}`,
|
|
961
|
+
body
|
|
962
|
+
}),
|
|
963
|
+
remove: async (ws, promptId) => {
|
|
964
|
+
try {
|
|
965
|
+
await request({
|
|
966
|
+
method: "DELETE",
|
|
967
|
+
path: `${v1}/workspaces/${seg(ws)}/prompts/${seg(promptId)}`
|
|
968
|
+
});
|
|
969
|
+
} catch (err) {
|
|
970
|
+
if (err instanceof ApiError && err.status === 404) return;
|
|
971
|
+
throw err;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
competitors: {
|
|
976
|
+
list: (ws, q) => request({ method: "GET", path: `${v1}/workspaces/${seg(ws)}/competitors`, query: q }),
|
|
977
|
+
create: (ws, body) => request({ method: "POST", path: `${v1}/workspaces/${seg(ws)}/competitors`, body })
|
|
978
|
+
},
|
|
979
|
+
recommendations: {
|
|
980
|
+
list: (ws, q) => request({
|
|
981
|
+
method: "GET",
|
|
982
|
+
path: `${v1}/workspaces/${seg(ws)}/recommendations`,
|
|
983
|
+
query: q
|
|
984
|
+
})
|
|
985
|
+
},
|
|
986
|
+
citations: {
|
|
987
|
+
list: (ws, q) => request({ method: "GET", path: `${v1}/workspaces/${seg(ws)}/citations`, query: q })
|
|
988
|
+
},
|
|
989
|
+
site: {
|
|
990
|
+
checkCrawlers: (url) => site(SITE_ROUTES.checkCrawlers, { url }),
|
|
991
|
+
validateLlmsTxt: (url) => site(SITE_ROUTES.validateLlmsTxt, { url }),
|
|
992
|
+
generateLlmsTxt: (url, o) => site(SITE_ROUTES.generateLlmsTxt, { url, depth: o?.full ? "full" : "index" }),
|
|
993
|
+
generateSitemap: (url) => site(SITE_ROUTES.generateSitemap, { url }),
|
|
994
|
+
validateStructuredData: (url) => site(SITE_ROUTES.validateStructuredData, { url }),
|
|
995
|
+
checkMeta: (url, keyword) => site(SITE_ROUTES.checkMeta, keyword === void 0 ? { url } : { url, keyword }),
|
|
996
|
+
analyzeChunks: (url) => site(SITE_ROUTES.analyzeChunks, { url })
|
|
997
|
+
},
|
|
998
|
+
keys: {
|
|
999
|
+
revokeSelf: (all) => request({
|
|
1000
|
+
method: "DELETE",
|
|
1001
|
+
path: `${v1}/keys/self`,
|
|
1002
|
+
query: all === void 0 ? void 0 : { all },
|
|
1003
|
+
org: null
|
|
1004
|
+
})
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
export {
|
|
1009
|
+
ANSWER_CITATIONS_MAX,
|
|
1010
|
+
ANSWER_TEXT_MAX_CHARS,
|
|
1011
|
+
API_ERROR_CODES,
|
|
1012
|
+
API_ERROR_DETAILS,
|
|
1013
|
+
API_KEY_ENVS,
|
|
1014
|
+
API_KEY_KINDS,
|
|
1015
|
+
API_SCOPES,
|
|
1016
|
+
API_VERSION,
|
|
1017
|
+
API_VERSION_HEADER,
|
|
1018
|
+
ApiError,
|
|
1019
|
+
ApiTimeoutError,
|
|
1020
|
+
CITATION_BUCKETS,
|
|
1021
|
+
DEFAULT_BASE_URL,
|
|
1022
|
+
DEFAULT_TIMEOUT_MS,
|
|
1023
|
+
IDEMPOTENT_REPLAY_HEADER,
|
|
1024
|
+
INTENT_STAGES,
|
|
1025
|
+
LLMS_TXT_DEPTHS,
|
|
1026
|
+
MAX_GET_RETRIES,
|
|
1027
|
+
ORG_ROLES,
|
|
1028
|
+
ORG_TYPES,
|
|
1029
|
+
PROMPT_SOURCES,
|
|
1030
|
+
PROMPT_TYPES,
|
|
1031
|
+
QUOTE_TTL_SECONDS,
|
|
1032
|
+
READ_ONLY_SCOPES,
|
|
1033
|
+
RECOMMENDATION_STATUSES,
|
|
1034
|
+
RECOMMENDATION_TYPES,
|
|
1035
|
+
REPORT_FORMATS,
|
|
1036
|
+
SCAN_MODES,
|
|
1037
|
+
SCAN_STATUSES,
|
|
1038
|
+
SENTIMENTS,
|
|
1039
|
+
SITE_ROUTES,
|
|
1040
|
+
SUPPORTED_API_VERSION,
|
|
1041
|
+
TIERS,
|
|
1042
|
+
answerCitation,
|
|
1043
|
+
answerListQuery,
|
|
1044
|
+
apiErrorBody,
|
|
1045
|
+
brandPresent,
|
|
1046
|
+
checkMetaInput,
|
|
1047
|
+
chunkAnalysisResult,
|
|
1048
|
+
citation,
|
|
1049
|
+
citationListQuery,
|
|
1050
|
+
competitor,
|
|
1051
|
+
conflictDetails,
|
|
1052
|
+
crawlerAccessResult,
|
|
1053
|
+
createCompetitorBody,
|
|
1054
|
+
createOrganizationBody,
|
|
1055
|
+
createOrganizationResult,
|
|
1056
|
+
createPromptBody,
|
|
1057
|
+
createScanBody,
|
|
1058
|
+
createScanResult,
|
|
1059
|
+
createWamClient,
|
|
1060
|
+
createWorkspaceBody,
|
|
1061
|
+
credits,
|
|
1062
|
+
deletePromptResult,
|
|
1063
|
+
estimateScanBody,
|
|
1064
|
+
featureNotAvailableDetails,
|
|
1065
|
+
generateLlmsTxtInput,
|
|
1066
|
+
idempotencyKey,
|
|
1067
|
+
insufficientCreditsDetails,
|
|
1068
|
+
latestScanRef,
|
|
1069
|
+
llmsTxtGeneration,
|
|
1070
|
+
llmsTxtValidation,
|
|
1071
|
+
meResponse,
|
|
1072
|
+
metaAnalysisResult,
|
|
1073
|
+
organizationSummary,
|
|
1074
|
+
page,
|
|
1075
|
+
pageQuery,
|
|
1076
|
+
pagination,
|
|
1077
|
+
parseErrorDetails,
|
|
1078
|
+
patchPromptBody,
|
|
1079
|
+
prompt,
|
|
1080
|
+
promptListQuery,
|
|
1081
|
+
promptListResponse,
|
|
1082
|
+
quotePayload,
|
|
1083
|
+
rateLimitedDetails,
|
|
1084
|
+
recommendation,
|
|
1085
|
+
recommendationListQuery,
|
|
1086
|
+
reportData,
|
|
1087
|
+
reportNotFoundDetails,
|
|
1088
|
+
requestId,
|
|
1089
|
+
revokeSelfQuery,
|
|
1090
|
+
revokeSelfResult,
|
|
1091
|
+
scanAlreadyRunningDetails,
|
|
1092
|
+
scanAnswer,
|
|
1093
|
+
scanEstimate,
|
|
1094
|
+
scanListQuery,
|
|
1095
|
+
scanModelRef,
|
|
1096
|
+
scanProgress,
|
|
1097
|
+
scanStatus,
|
|
1098
|
+
scanSummary,
|
|
1099
|
+
siteUrlInput,
|
|
1100
|
+
sitemapGeneration,
|
|
1101
|
+
structuredDataValidation,
|
|
1102
|
+
validationFailedDetails,
|
|
1103
|
+
workspaceDetail,
|
|
1104
|
+
workspaceSummary
|
|
1105
|
+
};
|