perspectapi-ts-sdk 6.5.9 → 7.0.1
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 +46 -1011
- package/dist/chunk-MZ22HQBX.mjs +1451 -0
- package/dist/index-BL9-AZpq.d.mts +2227 -0
- package/dist/index-BL9-AZpq.d.ts +2227 -0
- package/dist/index.d.mts +130 -2221
- package/dist/index.d.ts +130 -2221
- package/dist/index.js +71 -7
- package/dist/index.mjs +13 -1364
- package/dist/v2/index.d.mts +1 -0
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/index.js +1477 -0
- package/dist/v2/index.mjs +40 -0
- package/docs/README.md +15 -0
- package/docs/v1-deprecated/README.md +9 -0
- package/docs/v1-deprecated/examples/README.md +324 -0
- package/docs/v1-deprecated/examples/basic-usage.ts +258 -0
- package/docs/v1-deprecated/examples/cloudflare-worker.ts +274 -0
- package/docs/v1-deprecated/examples/content-query-with-slug-prefix.ts +237 -0
- package/docs/v1-deprecated/examples/image-transforms.ts +200 -0
- package/docs/v1-deprecated/examples/site-user-checkout.ts +186 -0
- package/docs/v1-deprecated/examples/slug-prefix-examples.ts +491 -0
- package/docs/v1-deprecated/legacy-docs/caching.md +667 -0
- package/docs/v1-deprecated/legacy-docs/contact.md +1396 -0
- package/docs/v1-deprecated/legacy-docs/csrf-protection.md +664 -0
- package/docs/v1-deprecated/legacy-docs/image-transforms.md +523 -0
- package/docs/v1-deprecated/legacy-docs/loaders.md +304 -0
- package/docs/v1-deprecated/legacy-docs/newsletter.md +811 -0
- package/docs/v1-deprecated/legacy-docs/site-users.md +817 -0
- package/docs/v1-deprecated/legacy-notes/CHANGELOG-CHECKOUT.md +143 -0
- package/docs/v1-deprecated/legacy-notes/CSRF-CHECKOUT.md +271 -0
- package/docs/v1-deprecated/legacy-notes/IMAGE_TRANSFORMS_PORT.md +298 -0
- package/docs/v1-deprecated/sdk-readme.md +1076 -0
- package/examples/README.md +19 -0
- package/examples/basic-v2.ts +37 -0
- package/llms.txt +25 -0
- package/package.json +18 -7
- package/src/client/api-keys-client.ts +4 -0
- package/src/client/auth-client.ts +4 -0
- package/src/client/base-client.ts +7 -0
- package/src/client/bundles-client.ts +4 -0
- package/src/client/categories-client.ts +4 -0
- package/src/client/checkout-client.ts +4 -0
- package/src/client/contact-client.ts +4 -0
- package/src/client/content-client.ts +4 -0
- package/src/client/newsletter-client.ts +4 -0
- package/src/client/newsletter-management-client.ts +4 -0
- package/src/client/organizations-client.ts +4 -0
- package/src/client/products-client.ts +4 -0
- package/src/client/site-users-client.ts +10 -1
- package/src/client/sites-client.ts +4 -0
- package/src/client/webhooks-client.ts +4 -0
- package/src/deprecation.ts +2 -1
- package/src/index.ts +2 -1
- package/src/loaders.ts +59 -0
- package/src/perspect-api-client.ts +2 -2
- package/src/v2/client/orders-client.ts +89 -6
- package/src/v2/types.ts +3 -0
package/README.md
CHANGED
|
@@ -1,40 +1,14 @@
|
|
|
1
1
|
# PerspectAPI TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the Perspect public API. The current API and SDK surface is **v2**.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
> New integrations **must** use the v2 client (`createPerspectApiV2Client`).
|
|
7
|
-
> The v1 surface — `PerspectApiClient`, `createPerspectApiClient`, the
|
|
8
|
-
> `src/client/*` classes, and everything under `/api/v1` — will stop working
|
|
9
|
-
> after the sunset date. Coding agents: do not generate code against v1.
|
|
5
|
+
## v1 Is Deprecated
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
**Do not use `/api/v1`, `PerspectApiClient`, `createPerspectApiClient`, or `src/client/*` for new code.**
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
import { createPerspectApiV2Client } from 'perspectapi-ts-sdk';
|
|
15
|
-
|
|
16
|
-
const client = createPerspectApiV2Client({
|
|
17
|
-
baseUrl: 'https://your-perspectapi-instance.com',
|
|
18
|
-
apiKey: 'your-api-key',
|
|
19
|
-
});
|
|
9
|
+
The v1 API and SDK surface sunsets on **2026-06-01**. New integrations must use `/api/v2` through `PerspectApiV2Client` or `createPerspectApiV2Client`.
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The v1 Quick Start below is kept only for callers migrating away from v1.
|
|
25
|
-
|
|
26
|
-
## Features
|
|
27
|
-
|
|
28
|
-
- 🚀 **Cloudflare Workers Compatible** - Uses native fetch API, no Node.js dependencies
|
|
29
|
-
- 🔒 **Type Safe** - Full TypeScript support with comprehensive type definitions
|
|
30
|
-
- 🔄 **Automatic Retries** - Built-in retry logic with exponential backoff
|
|
31
|
-
- 🛡️ **Error Handling** - Structured error responses with detailed information
|
|
32
|
-
- 📦 **Modular Design** - Use individual clients or the complete SDK
|
|
33
|
-
- 🔑 **Multiple Auth Methods** - Support for JWT tokens and API keys
|
|
34
|
-
- 📊 **Comprehensive Coverage** - All PerspectAPI endpoints supported
|
|
35
|
-
- 🧩 **High-Level Loaders** - Drop-in helpers for products, content, and checkout flows with fallbacks
|
|
36
|
-
- 📧 **Newsletter Management** - Complete newsletter subscription system with double opt-in, preferences, and lists
|
|
37
|
-
- 👥 **Site Users** - OTP-based customer accounts with metadata, profiles, orders, and subscriptions
|
|
11
|
+
Historical v1 examples have been moved to [docs/v1-deprecated/sdk-readme.md](docs/v1-deprecated/sdk-readme.md) for migration reference only.
|
|
38
12
|
|
|
39
13
|
## Installation
|
|
40
14
|
|
|
@@ -44,1023 +18,84 @@ npm install perspectapi-ts-sdk
|
|
|
44
18
|
|
|
45
19
|
## Quick Start
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
import { createPerspectApiClient } from 'perspectapi-ts-sdk';
|
|
49
|
-
|
|
50
|
-
// Initialize with API key
|
|
51
|
-
const client = createPerspectApiClient({
|
|
52
|
-
baseUrl: 'https://your-perspectapi-instance.com',
|
|
53
|
-
apiKey: 'your-api-key'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Or initialize with JWT token
|
|
57
|
-
const client = createPerspectApiClient({
|
|
58
|
-
baseUrl: 'https://your-perspectapi-instance.com',
|
|
59
|
-
jwt: 'your-jwt-token'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Use the client
|
|
63
|
-
async function example() {
|
|
64
|
-
const siteName = 'your-site-name';
|
|
65
|
-
|
|
66
|
-
// Get all content
|
|
67
|
-
const content = await client.content.getContent(siteName);
|
|
68
|
-
console.log(content.data);
|
|
69
|
-
|
|
70
|
-
// Create new content
|
|
71
|
-
const newContent = await client.content.createContent({
|
|
72
|
-
page_title: 'Hello World',
|
|
73
|
-
page_content: 'This is my first post!',
|
|
74
|
-
page_status: 'publish',
|
|
75
|
-
page_type: 'post'
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
console.log(newContent.data);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
### High-Level Loaders (Quick Example)
|
|
21
|
+
Use the v2 subpath when you want the import itself to make the version explicit:
|
|
82
22
|
|
|
83
23
|
```typescript
|
|
84
|
-
import {
|
|
85
|
-
loadProducts,
|
|
86
|
-
loadPosts,
|
|
87
|
-
createCheckoutSession
|
|
88
|
-
} from 'perspectapi-ts-sdk';
|
|
89
|
-
|
|
90
|
-
const loaders = {
|
|
91
|
-
client: createPerspectApiClient({ baseUrl, apiKey }),
|
|
92
|
-
siteName: 'my-museum-site'
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const products = await loadProducts(loaders);
|
|
96
|
-
const posts = await loadPosts(loaders);
|
|
97
|
-
|
|
98
|
-
const checkout = await createCheckoutSession({
|
|
99
|
-
...loaders,
|
|
100
|
-
items: [{ productId: products[0].id, quantity: 1 }],
|
|
101
|
-
successUrl: 'https://example.com/success',
|
|
102
|
-
cancelUrl: 'https://example.com/cancel'
|
|
103
|
-
});
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
> 📚 See [docs/loaders.md](docs/loaders.md) for full walkthroughs, including fallback data, custom logging, and Stripe price resolution.
|
|
107
|
-
|
|
108
|
-
## Configuring Caching
|
|
109
|
-
|
|
110
|
-
Pass a `cache` block when you create the client to enable caching. The SDK falls back to an in-memory store in development; in production you can supply any adapter that satisfies the `CacheAdapter` interface.
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
import { PerspectApiClient } from 'perspectapi-ts-sdk';
|
|
114
|
-
import { myCacheAdapter } from './cache-adapter';
|
|
115
|
-
|
|
116
|
-
const perspect = new PerspectApiClient({
|
|
117
|
-
baseUrl: 'https://api.perspect.comm',
|
|
118
|
-
apiKey: env.PERSPECT_API_KEY,
|
|
119
|
-
cache: {
|
|
120
|
-
adapter: myCacheAdapter,
|
|
121
|
-
defaultTtlSeconds: 300,
|
|
122
|
-
keyPrefix: 'storefront', // optional namespace
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Cloudflare Workers KV example
|
|
128
|
-
|
|
129
|
-
KV namespaces make a great globally distributed cache. Bind a namespace (e.g. `PERSPECT_CACHE`) in your Worker and wrap it with a small adapter:
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
// worker.ts
|
|
133
|
-
import { PerspectApiClient, type CacheAdapter } from 'perspectapi-ts-sdk';
|
|
134
|
-
|
|
135
|
-
const kvAdapter = (kv: KVNamespace): CacheAdapter => ({
|
|
136
|
-
async get(key) {
|
|
137
|
-
const value = await kv.get(key);
|
|
138
|
-
return value ?? undefined;
|
|
139
|
-
},
|
|
140
|
-
async set(key, value, options) {
|
|
141
|
-
const ttl = options?.ttlSeconds;
|
|
142
|
-
await kv.put(key, value, ttl ? { expirationTtl: ttl } : undefined);
|
|
143
|
-
},
|
|
144
|
-
async delete(key) {
|
|
145
|
-
await kv.delete(key);
|
|
146
|
-
},
|
|
147
|
-
async deleteMany(keys) {
|
|
148
|
-
await Promise.all(keys.map(key => kv.delete(key)));
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
export default {
|
|
153
|
-
async fetch(request: Request, env: Env) {
|
|
154
|
-
const perspect = new PerspectApiClient({
|
|
155
|
-
baseUrl: env.PERSPECT_API_URL,
|
|
156
|
-
apiKey: env.PERSPECT_API_KEY,
|
|
157
|
-
cache: {
|
|
158
|
-
adapter: kvAdapter(env.PERSPECT_CACHE),
|
|
159
|
-
defaultTtlSeconds: 300,
|
|
160
|
-
keyPrefix: 'storefront',
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const products = await perspect.products.getProducts('museum-indian-art');
|
|
165
|
-
return new Response(JSON.stringify(products.data), {
|
|
166
|
-
headers: { 'Content-Type': 'application/json' },
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
When PerspectAPI sends a webhook—or when your Worker mutates data directly—call `perspect.cache.invalidate({ tags: [...] })` using the tags emitted by the SDK (`products:site:<site>`, `content:slug:<site>:<slug>`, `newsletter:campaigns:slug:<site>:<slug>`, etc.) so stale entries are purged immediately.
|
|
173
|
-
|
|
174
|
-
### Webhook-driven cache invalidation
|
|
175
|
-
|
|
176
|
-
Most deployments rely on PerspectAPI webhooks to bust cache entries whenever content changes. Your app needs an HTTP endpoint that:
|
|
177
|
-
|
|
178
|
-
1. Verifies the webhook signature (or shared secret).
|
|
179
|
-
2. Maps the webhook payload to the cache tags that were used when reading data.
|
|
180
|
-
3. Calls `client.cache.invalidate({ tags })`.
|
|
181
|
-
|
|
182
|
-
Below is a minimal Cloudflare Worker handler; the same flow works in Express, Fastify, etc.—just swap the request parsing.
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
// worker.ts
|
|
186
|
-
import { PerspectApiClient } from 'perspectapi-ts-sdk';
|
|
187
|
-
|
|
188
|
-
const kvAdapter = (kv: KVNamespace) => ({
|
|
189
|
-
get: (key: string) => kv.get(key),
|
|
190
|
-
set: (key: string, value: string, options?: { ttlSeconds?: number }) =>
|
|
191
|
-
options?.ttlSeconds
|
|
192
|
-
? kv.put(key, value, { expirationTtl: options.ttlSeconds })
|
|
193
|
-
: kv.put(key, value),
|
|
194
|
-
delete: (key: string) => kv.delete(key),
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const perspect = new PerspectApiClient({
|
|
198
|
-
baseUrl: env.PERSPECT_API_URL,
|
|
199
|
-
apiKey: env.PERSPECT_API_KEY,
|
|
200
|
-
cache: { adapter: kvAdapter(env.PERSPECT_CACHE), defaultTtlSeconds: 300 },
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
type WebhookEvent =
|
|
204
|
-
| { type: 'product.updated'; site: string; slug?: string; id?: number }
|
|
205
|
-
| { type: 'content.published'; site: string; slug: string; id: number }
|
|
206
|
-
| { type: 'category.updated'; site: string; slug: string };
|
|
207
|
-
|
|
208
|
-
const tagMap: Record<string, (e: WebhookEvent) => string[]> = {
|
|
209
|
-
'product.updated': event => [
|
|
210
|
-
`products`,
|
|
211
|
-
`products:site:${event.site}`,
|
|
212
|
-
event.slug ? `products:slug:${event.site}:${event.slug}` : '',
|
|
213
|
-
event.id ? `products:id:${event.id}` : '',
|
|
214
|
-
],
|
|
215
|
-
'content.published': event => [
|
|
216
|
-
`content`,
|
|
217
|
-
`content:site:${event.site}`,
|
|
218
|
-
`content:slug:${event.site}:${event.slug}`,
|
|
219
|
-
`content:id:${event.id}`,
|
|
220
|
-
],
|
|
221
|
-
'category.updated': event => [
|
|
222
|
-
`categories`,
|
|
223
|
-
`categories:site:${event.site}`,
|
|
224
|
-
`categories:product:${event.site}:${event.slug}`,
|
|
225
|
-
`products:category:${event.site}:${event.slug}`,
|
|
226
|
-
`content:category:${event.site}:${event.slug}`,
|
|
227
|
-
],
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export default {
|
|
231
|
-
async fetch(request: Request, env: Env) {
|
|
232
|
-
const url = new URL(request.url);
|
|
233
|
-
|
|
234
|
-
if (url.pathname === '/webhooks/perspect' && request.method === 'POST') {
|
|
235
|
-
const event = (await request.json()) as WebhookEvent;
|
|
236
|
-
|
|
237
|
-
// TODO: validate shared secret / signature before proceeding
|
|
238
|
-
|
|
239
|
-
const buildTags = tagMap[event.type];
|
|
240
|
-
if (buildTags) {
|
|
241
|
-
const tags = buildTags(event).filter(Boolean);
|
|
242
|
-
if (tags.length) {
|
|
243
|
-
await perspect.cache.invalidate({ tags });
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return new Response(null, { status: 202 });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ...rest of your app
|
|
251
|
-
return new Response('ok');
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
> 🔁 Adjust the `WebhookEvent` union and `tagMap` to match the actual payloads you receive. PerspectAPI webhooks also carry version IDs and environment metadata that you can use for more granular targeting if needed.
|
|
257
|
-
|
|
258
|
-
### Newsletter Publish Invalidation
|
|
259
|
-
|
|
260
|
-
Newsletter list and campaign reads are cache-aware:
|
|
261
|
-
|
|
262
|
-
```ts
|
|
263
|
-
const campaigns = await perspect.newsletter.getPublishedCampaigns('museum-indian-art', {
|
|
264
|
-
page: 1,
|
|
265
|
-
limit: 20,
|
|
266
|
-
});
|
|
267
|
-
const campaign = await perspect.newsletter.getPublishedCampaignBySlug(
|
|
268
|
-
'museum-indian-art',
|
|
269
|
-
'spring-launch',
|
|
270
|
-
{ slugPrefix: 'updates' }
|
|
271
|
-
);
|
|
272
|
-
```
|
|
24
|
+
import { createPerspectApiV2Client } from 'perspectapi-ts-sdk/v2';
|
|
273
25
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const result = await perspect.newsletter.invalidatePublishedCampaignCacheFromWebhook(
|
|
278
|
-
webhookPayload,
|
|
279
|
-
'museum-indian-art'
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
if (result.invalidated) {
|
|
283
|
-
console.log(result.tags);
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Image Transformations
|
|
288
|
-
|
|
289
|
-
The SDK includes utilities for transforming images using Cloudflare's Image Resizing service:
|
|
290
|
-
|
|
291
|
-
```typescript
|
|
292
|
-
import { transformMediaItem, buildImageUrl } from 'perspectapi-ts-sdk';
|
|
293
|
-
|
|
294
|
-
// Get a product with media
|
|
295
|
-
const product = await client.products.getProduct('mysite', 123);
|
|
296
|
-
const media = product.data.media?.[0];
|
|
297
|
-
|
|
298
|
-
if (media) {
|
|
299
|
-
// Generate all responsive sizes automatically
|
|
300
|
-
const urls = transformMediaItem('https://api.perspect.comm', media);
|
|
301
|
-
|
|
302
|
-
console.log(urls.thumbnail); // 150x150 cover crop
|
|
303
|
-
console.log(urls.small); // 400px wide
|
|
304
|
-
console.log(urls.medium); // 800px wide
|
|
305
|
-
console.log(urls.large); // 1200px wide
|
|
306
|
-
console.log(urls.original); // Original with auto format
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Or build custom transformations
|
|
310
|
-
const customUrl = buildImageUrl(
|
|
311
|
-
'https://api.perspect.comm',
|
|
312
|
-
'media/mysite/photo.jpg',
|
|
313
|
-
{
|
|
314
|
-
width: 400,
|
|
315
|
-
height: 300,
|
|
316
|
-
fit: 'cover',
|
|
317
|
-
format: 'webp',
|
|
318
|
-
quality: 85
|
|
319
|
-
}
|
|
320
|
-
);
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**Features:**
|
|
324
|
-
- ✅ On-the-fly image resizing (no pre-generated thumbnails)
|
|
325
|
-
- ✅ Automatic format optimization (WebP/AVIF)
|
|
326
|
-
- ✅ Responsive image support with srcset
|
|
327
|
-
- ✅ CDN caching at the edge
|
|
328
|
-
- ✅ Bandwidth savings
|
|
329
|
-
|
|
330
|
-
> 📚 See [docs/image-transforms.md](docs/image-transforms.md) for complete documentation, examples, and best practices.
|
|
331
|
-
|
|
332
|
-
## High-Level Data Loaders
|
|
333
|
-
|
|
334
|
-
The SDK now ships with convenience loaders that wrap the lower-level REST clients. They help you:
|
|
335
|
-
|
|
336
|
-
- normalise products (including nested media arrays and Stripe IDs)
|
|
337
|
-
- search products server-side with limit/offset support and filter by category names or IDs (no hard-coded IDs)
|
|
338
|
-
- fetch published posts/pages with sensible defaults
|
|
339
|
-
- fall back to sample data when PerspectAPI is not configured (useful for local previews)
|
|
340
|
-
- create Stripe checkout sessions by automatically resolving price IDs
|
|
341
|
-
|
|
342
|
-
All loaders accept a shared `LoaderOptions` object:
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
import {
|
|
346
|
-
loadProducts,
|
|
347
|
-
loadProductBySlug,
|
|
348
|
-
loadPosts,
|
|
349
|
-
createCheckoutSession,
|
|
350
|
-
type LoaderOptions
|
|
351
|
-
} from 'perspectapi-ts-sdk';
|
|
352
|
-
|
|
353
|
-
const options: LoaderOptions = {
|
|
354
|
-
client: createPerspectApiClient({ baseUrl, apiKey }),
|
|
355
|
-
siteName: 'museum-indian-art',
|
|
356
|
-
logger: console, // optional
|
|
357
|
-
fallbackProducts: [], // optional
|
|
358
|
-
fallbackPosts: [] // optional
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const products = await loadProducts(options);
|
|
362
|
-
const single = await loadProductBySlug({ ...options, slug: 'sunset-painting' });
|
|
363
|
-
const posts = await loadPosts(options);
|
|
364
|
-
|
|
365
|
-
await createCheckoutSession({
|
|
366
|
-
...options,
|
|
367
|
-
items: [{ productId: products[0].id, quantity: 1 }],
|
|
368
|
-
successUrl: 'https://example.com/success',
|
|
369
|
-
cancelUrl: 'https://example.com/cancel'
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// Filter products by multiple category names/IDs while combining search
|
|
373
|
-
const decorPillows = await loadProducts({
|
|
374
|
-
...options,
|
|
375
|
-
category: ['Home Decor', 'Seasonal Sale'],
|
|
376
|
-
categoryIds: [12],
|
|
377
|
-
search: 'pillow',
|
|
378
|
-
offset: 24,
|
|
379
|
-
limit: 12
|
|
26
|
+
const client = createPerspectApiV2Client({
|
|
27
|
+
baseUrl: 'https://api.perspect.com',
|
|
28
|
+
apiKey: process.env.PERSPECTAPI_API_KEY!,
|
|
380
29
|
});
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
> More real-world scenarios—including Cloudflare Workers, Remix/Next.js loaders, custom logger implementations, and checkout price resolution—are documented in [docs/loaders.md](docs/loaders.md).
|
|
384
|
-
|
|
385
|
-
## Authentication
|
|
386
|
-
|
|
387
|
-
### API Key Authentication
|
|
388
30
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
baseUrl: 'https://api.example.com',
|
|
392
|
-
apiKey: 'pk_your_api_key_here'
|
|
393
|
-
});
|
|
31
|
+
const site = await client.sites.get('my-site');
|
|
32
|
+
console.log(site.id, site.name);
|
|
394
33
|
```
|
|
395
34
|
|
|
396
|
-
|
|
35
|
+
The top-level package also exports v2:
|
|
397
36
|
|
|
398
37
|
```typescript
|
|
399
|
-
|
|
400
|
-
baseUrl: 'https://api.example.com',
|
|
401
|
-
jwt: 'your.jwt.token'
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Or authenticate after initialization
|
|
405
|
-
await client.auth.signIn({
|
|
406
|
-
email: 'user@example.com',
|
|
407
|
-
password: 'password'
|
|
408
|
-
});
|
|
38
|
+
import { createPerspectApiV2Client } from 'perspectapi-ts-sdk';
|
|
409
39
|
```
|
|
410
40
|
|
|
411
|
-
|
|
41
|
+
## Common Resources
|
|
412
42
|
|
|
413
43
|
```typescript
|
|
414
|
-
const
|
|
415
|
-
|
|
44
|
+
const content = await client.content.list('my-site', {
|
|
45
|
+
type: 'post',
|
|
46
|
+
status: 'publish',
|
|
47
|
+
limit: 10,
|
|
416
48
|
});
|
|
417
49
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// or
|
|
421
|
-
client.setAuth('your.jwt.token');
|
|
422
|
-
|
|
423
|
-
// Clear authentication
|
|
424
|
-
client.clearAuth();
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
## API Reference
|
|
428
|
-
|
|
429
|
-
### Content Management
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
// Get all content with pagination
|
|
433
|
-
const content = await client.content.getContent('your-site-name', {
|
|
434
|
-
page: 1,
|
|
50
|
+
const products = await client.products.list('my-site', {
|
|
51
|
+
published: true,
|
|
435
52
|
limit: 20,
|
|
436
|
-
page_status: 'publish',
|
|
437
|
-
page_type: 'post',
|
|
438
|
-
slug_prefix: 'blog' // Optional: filter by slug prefix
|
|
439
53
|
});
|
|
440
54
|
|
|
441
|
-
|
|
442
|
-
const post = await client.content.getContentById(123);
|
|
443
|
-
|
|
444
|
-
// Get content by slug
|
|
445
|
-
const page = await client.content.getContentBySlug('your-site-name', 'about-us');
|
|
446
|
-
|
|
447
|
-
// Get content by category slug
|
|
448
|
-
const categoryContent = await client.content.getContentByCategorySlug(
|
|
449
|
-
'your-site-name',
|
|
450
|
-
'news',
|
|
451
|
-
{
|
|
452
|
-
page: 1,
|
|
453
|
-
limit: 20,
|
|
454
|
-
page_type: 'post'
|
|
455
|
-
}
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
// Create new content
|
|
459
|
-
const newPost = await client.content.createContent({
|
|
460
|
-
page_title: 'My New Post',
|
|
461
|
-
page_content: '<p>Content goes here</p>',
|
|
462
|
-
content_markdown: '# My New Post\n\nContent goes here',
|
|
463
|
-
page_status: 'draft',
|
|
464
|
-
page_type: 'post',
|
|
465
|
-
slug: 'my-new-post'
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// Update content
|
|
469
|
-
const updated = await client.content.updateContent(123, {
|
|
470
|
-
page_title: 'Updated Title',
|
|
471
|
-
page_status: 'publish'
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Delete content
|
|
475
|
-
await client.content.deleteContent(123);
|
|
476
|
-
|
|
477
|
-
// Publish/unpublish content
|
|
478
|
-
await client.content.publishContent(123);
|
|
479
|
-
await client.content.unpublishContent(123);
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Products & E-commerce
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
// Get all products
|
|
486
|
-
const products = await client.products.getProducts('my-site', {
|
|
487
|
-
page: 1,
|
|
55
|
+
const contacts = await client.contacts.list('my-site', {
|
|
488
56
|
limit: 20,
|
|
489
|
-
isActive: true,
|
|
490
|
-
slug_prefix: 'shop' // Optional: filter by slug prefix
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// Create new product
|
|
494
|
-
const product = await client.products.createProduct({
|
|
495
|
-
name: 'Amazing Product',
|
|
496
|
-
description: 'Product description',
|
|
497
|
-
price: 29.99,
|
|
498
|
-
currency: 'USD',
|
|
499
|
-
sku: 'PROD-001'
|
|
500
57
|
});
|
|
501
|
-
|
|
502
|
-
// Update product inventory
|
|
503
|
-
await client.products.updateProductInventory(123, {
|
|
504
|
-
quantity: 10,
|
|
505
|
-
operation: 'add',
|
|
506
|
-
reason: 'Restocked'
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
// Get product by slug and site name (NEW!)
|
|
510
|
-
const productBySlug = await client.products.getProductBySlug('my-site', 'awesome-laptop');
|
|
511
|
-
console.log('Product:', productBySlug.data?.name);
|
|
512
|
-
console.log('Variants:', productBySlug.data?.variants);
|
|
513
|
-
|
|
514
|
-
// Get products by category slug (NEW!)
|
|
515
|
-
const categoryProducts = await client.products.getProductsByCategorySlug(
|
|
516
|
-
'my-site',
|
|
517
|
-
'electronics',
|
|
518
|
-
{
|
|
519
|
-
page: 1,
|
|
520
|
-
limit: 20,
|
|
521
|
-
published: true,
|
|
522
|
-
search: 'phone'
|
|
523
|
-
}
|
|
524
|
-
);
|
|
525
|
-
console.log('Products:', categoryProducts.data?.data);
|
|
526
|
-
console.log('Category info:', categoryProducts.data?.category);
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### Organizations & Sites
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
// Get organizations
|
|
533
|
-
const orgs = await client.organizations.getOrganizations();
|
|
534
|
-
|
|
535
|
-
// Create new organization
|
|
536
|
-
const org = await client.organizations.createOrganization({
|
|
537
|
-
name: 'My Company',
|
|
538
|
-
description: 'Company description'
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Get sites
|
|
542
|
-
const sites = await client.sites.getSites({
|
|
543
|
-
organizationId: 1
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
// Create new site
|
|
547
|
-
const site = await client.sites.createSite({
|
|
548
|
-
name: 'My Website',
|
|
549
|
-
domain: 'example.com',
|
|
550
|
-
organizationId: 1
|
|
551
|
-
});
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### API Key Management
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
// Get all API keys
|
|
558
|
-
const apiKeys = await client.apiKeys.getApiKeys();
|
|
559
|
-
|
|
560
|
-
// Create new API key
|
|
561
|
-
const newKey = await client.apiKeys.createApiKey({
|
|
562
|
-
name: 'Frontend App Key',
|
|
563
|
-
description: 'API key for frontend application',
|
|
564
|
-
permissions: ['content:read', 'products:read'],
|
|
565
|
-
expiresAt: '2024-12-31T23:59:59Z'
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
console.log('New API key:', newKey.data.key);
|
|
569
|
-
|
|
570
|
-
// Test API key validity
|
|
571
|
-
const testResult = await client.apiKeys.testApiKey('pk_test_key');
|
|
572
|
-
console.log('Key valid:', testResult.data.valid);
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
### Webhooks
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
// Get all webhooks
|
|
579
|
-
const webhooks = await client.webhooks.getWebhooks();
|
|
580
|
-
|
|
581
|
-
// Create new webhook
|
|
582
|
-
const webhook = await client.webhooks.createWebhook({
|
|
583
|
-
name: 'Order Notifications',
|
|
584
|
-
url: 'https://myapp.com/webhooks/orders',
|
|
585
|
-
provider: 'stripe',
|
|
586
|
-
events: ['payment_intent.succeeded', 'payment_intent.payment_failed'],
|
|
587
|
-
isActive: true
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// Test webhook
|
|
591
|
-
const testResult = await client.webhooks.testWebhook(webhook.data.id);
|
|
592
|
-
console.log('Webhook test:', testResult.data);
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
### Checkout & Payments
|
|
596
|
-
|
|
597
|
-
```typescript
|
|
598
|
-
// Create Stripe checkout session
|
|
599
|
-
const session = await client.checkout.createCheckoutSession({
|
|
600
|
-
priceId: 'price_1234567890',
|
|
601
|
-
successUrl: 'https://myapp.com/success',
|
|
602
|
-
cancelUrl: 'https://myapp.com/cancel',
|
|
603
|
-
customerEmail: 'customer@example.com',
|
|
604
|
-
currency: 'usd',
|
|
605
|
-
shipping_amount: 500,
|
|
606
|
-
shipping_address: {
|
|
607
|
-
country: 'US',
|
|
608
|
-
state: 'CA',
|
|
609
|
-
postal_code: '94110'
|
|
610
|
-
},
|
|
611
|
-
billing_address: {
|
|
612
|
-
country: 'US',
|
|
613
|
-
state: 'CA',
|
|
614
|
-
postal_code: '94110'
|
|
615
|
-
},
|
|
616
|
-
tax: {
|
|
617
|
-
strategy: 'manual_rates',
|
|
618
|
-
customer_identifier: 'acct-123',
|
|
619
|
-
customer_display_name: 'Acme Corp',
|
|
620
|
-
customer_exemption: {
|
|
621
|
-
status: 'exempt',
|
|
622
|
-
tax_id: '99-1234567',
|
|
623
|
-
tax_id_type: 'ein'
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
// Redirect user to checkout
|
|
629
|
-
window.location.href = session.data.url;
|
|
630
|
-
|
|
631
|
-
// Get checkout session status
|
|
632
|
-
const sessionStatus = await client.checkout.getCheckoutSession('cs_test_123');
|
|
633
|
-
console.log(sessionStatus.data.tax?.amount); // Display assessed tax (if calculated)
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
### Contact Forms
|
|
637
|
-
|
|
638
|
-
```typescript
|
|
639
|
-
const siteName = 'your-site-name';
|
|
640
|
-
|
|
641
|
-
// Submit contact form
|
|
642
|
-
const submission = await client.contact.submitContact(siteName, {
|
|
643
|
-
name: 'John Doe',
|
|
644
|
-
email: 'john@example.com',
|
|
645
|
-
subject: 'Question about pricing',
|
|
646
|
-
message: 'I have a question about your pricing plans.',
|
|
647
|
-
turnstileToken: 'turnstile-response-token'
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// Get contact submissions (admin only)
|
|
651
|
-
const submissions = await client.contact.getContactSubmissions(siteName, {
|
|
652
|
-
status: 'unread',
|
|
653
|
-
page: 1,
|
|
654
|
-
limit: 50
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// Update submission status
|
|
658
|
-
await client.contact.updateContactStatus(siteName, submission.data.id, 'read');
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### Newsletter Subscriptions
|
|
662
|
-
|
|
663
|
-
```typescript
|
|
664
|
-
const siteName = 'your-site-name';
|
|
665
|
-
|
|
666
|
-
// Subscribe to newsletter (with double opt-in)
|
|
667
|
-
const subscription = await client.newsletter.subscribe(siteName, {
|
|
668
|
-
email: 'subscriber@example.com',
|
|
669
|
-
name: 'Jane Doe',
|
|
670
|
-
list_ids: ['list_default'], // Optional: subscribe to specific lists
|
|
671
|
-
frequency: 'weekly', // instant, daily, weekly, monthly
|
|
672
|
-
topics: ['news', 'updates'], // Optional: topic preferences
|
|
673
|
-
double_opt_in: true, // Default: true for GDPR compliance
|
|
674
|
-
turnstile_token: 'token' // Optional: Turnstile verification
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
// Confirm subscription via email token
|
|
678
|
-
const confirmed = await client.newsletter.confirmSubscription(
|
|
679
|
-
siteName,
|
|
680
|
-
'confirmation-token-from-email'
|
|
681
|
-
);
|
|
682
|
-
|
|
683
|
-
// Unsubscribe
|
|
684
|
-
const unsubscribed = await client.newsletter.unsubscribe(siteName, {
|
|
685
|
-
email: 'subscriber@example.com',
|
|
686
|
-
reason: 'Too many emails' // Optional feedback
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
// One-click unsubscribe (from email link)
|
|
690
|
-
await client.newsletter.unsubscribeByToken(siteName, 'unsubscribe-token');
|
|
691
|
-
|
|
692
|
-
// Update preferences
|
|
693
|
-
await client.newsletter.updatePreferences(siteName, 'subscriber@example.com', {
|
|
694
|
-
frequency: 'monthly',
|
|
695
|
-
topics: ['product-updates'],
|
|
696
|
-
email_format: 'html', // html, text, or both
|
|
697
|
-
timezone: 'America/New_York',
|
|
698
|
-
track_opens: false,
|
|
699
|
-
track_clicks: false
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
// Check subscription status
|
|
703
|
-
const status = await client.newsletter.getStatus(siteName, 'subscriber@example.com');
|
|
704
|
-
console.log('Subscribed:', status.data.subscribed);
|
|
705
|
-
console.log('Status:', status.data.status); // pending, confirmed, unsubscribed
|
|
706
|
-
|
|
707
|
-
// Get available newsletter lists
|
|
708
|
-
const lists = await client.newsletter.getLists(siteName);
|
|
709
|
-
console.log('Available lists:', lists.data.lists);
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
#### Newsletter Management APIs
|
|
713
|
-
|
|
714
|
-
`client.newsletter` is now public-only (subscribe/confirm/unsubscribe/preferences/public lists/published campaigns).
|
|
715
|
-
For management operations, use `client.newsletterManagement`.
|
|
716
|
-
|
|
717
|
-
```typescript
|
|
718
|
-
// Sync subscriber by email (canonical create/update path)
|
|
719
|
-
const sync = await client.newsletterManagement.syncSubscription(siteName, {
|
|
720
|
-
email: 'user@example.com',
|
|
721
|
-
name: 'User Name',
|
|
722
|
-
list_ids: ['list_default'],
|
|
723
|
-
resubscribe_override: false
|
|
724
|
-
});
|
|
725
|
-
console.log(sync.data.code); // CREATED, UPDATED, RESUBSCRIBED, ALREADY_UNSUBSCRIBED
|
|
726
|
-
|
|
727
|
-
// List subscribers
|
|
728
|
-
const subscriptions = await client.newsletterManagement.listSubscriptions(siteName, {
|
|
729
|
-
page: 1,
|
|
730
|
-
limit: 100,
|
|
731
|
-
status: 'confirmed'
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
// Import with override controls (row-level override wins)
|
|
735
|
-
await client.newsletterManagement.importSubscriptions(siteName, {
|
|
736
|
-
resubscribe_override: false,
|
|
737
|
-
rows: [
|
|
738
|
-
{ email: 'user1@example.com', list_ids: ['list_default'] },
|
|
739
|
-
{ email: 'user2@example.com', resubscribe_override: true }
|
|
740
|
-
]
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
// List + campaign management
|
|
744
|
-
await client.newsletterManagement.createList(siteName, {
|
|
745
|
-
list_name: 'VIP Customers',
|
|
746
|
-
slug: 'vip-customers',
|
|
747
|
-
is_public: false
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
await client.newsletterManagement.createCampaign(siteName, {
|
|
751
|
-
campaign_name: 'Spring Update',
|
|
752
|
-
subject: 'Spring Update',
|
|
753
|
-
markdown_content: '# Hello',
|
|
754
|
-
status: 'draft'
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
// Exports (csv/json only; xlsx returns UNSUPPORTED_FORMAT)
|
|
758
|
-
const exportData = await client.newsletterManagement.createExport(siteName, {
|
|
759
|
-
format: 'csv'
|
|
760
|
-
});
|
|
761
|
-
console.log(exportData.data.downloadUrl);
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
> 📚 **For complete newsletter documentation**, see [docs/newsletter.md](docs/newsletter.md)
|
|
765
|
-
|
|
766
|
-
### Site Users (Customer Accounts)
|
|
767
|
-
|
|
768
|
-
Site users are per-site customer accounts with OTP-based authentication, separate from admin users.
|
|
769
|
-
|
|
770
|
-
```typescript
|
|
771
|
-
const siteName = 'your-site-name';
|
|
772
|
-
|
|
773
|
-
// Request OTP for login/signup (with optional metadata)
|
|
774
|
-
await client.siteUsers.requestOtp(
|
|
775
|
-
siteName,
|
|
776
|
-
{
|
|
777
|
-
email: 'user@example.com',
|
|
778
|
-
waitlist: true, // Optional: mark as waitlist signup
|
|
779
|
-
metadata: { // Optional: set metadata at signup time
|
|
780
|
-
signupSource: 'landing-page',
|
|
781
|
-
referralCode: 'FRIEND123',
|
|
782
|
-
interests: ['product-updates']
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
csrfToken
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
// Verify OTP and get JWT token
|
|
789
|
-
const response = await client.siteUsers.verifyOtp(
|
|
790
|
-
siteName,
|
|
791
|
-
{ email: 'user@example.com', code: '123456' },
|
|
792
|
-
csrfToken
|
|
793
|
-
);
|
|
794
|
-
|
|
795
|
-
const { token, user } = response.data;
|
|
796
|
-
client.setAuth(token); // Set auth for subsequent requests
|
|
797
|
-
|
|
798
|
-
// Get current user profile
|
|
799
|
-
const { data } = await client.siteUsers.getMe(siteName);
|
|
800
|
-
console.log(data.user); // Basic user fields + metadata
|
|
801
|
-
console.log(data.profile); // Key-value profile data
|
|
802
|
-
|
|
803
|
-
// Update user profile and metadata
|
|
804
|
-
await client.siteUsers.updateMe(
|
|
805
|
-
siteName,
|
|
806
|
-
{
|
|
807
|
-
first_name: 'John',
|
|
808
|
-
last_name: 'Doe',
|
|
809
|
-
metadata: {
|
|
810
|
-
preferences: { theme: 'dark' },
|
|
811
|
-
tags: ['premium'],
|
|
812
|
-
customField: 'value'
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
csrfToken
|
|
816
|
-
);
|
|
817
|
-
|
|
818
|
-
// Set profile key-values (phone, addresses, etc.)
|
|
819
|
-
await client.siteUsers.setProfileValue(
|
|
820
|
-
siteName,
|
|
821
|
-
'phone',
|
|
822
|
-
'+1-555-0123',
|
|
823
|
-
csrfToken
|
|
824
|
-
);
|
|
825
|
-
|
|
826
|
-
await client.siteUsers.setProfileValue(
|
|
827
|
-
siteName,
|
|
828
|
-
'address_shipping',
|
|
829
|
-
JSON.stringify({
|
|
830
|
-
line1: '123 Main St',
|
|
831
|
-
city: 'San Francisco',
|
|
832
|
-
state: 'CA',
|
|
833
|
-
postal_code: '94102',
|
|
834
|
-
country: 'US'
|
|
835
|
-
}),
|
|
836
|
-
csrfToken
|
|
837
|
-
);
|
|
838
|
-
|
|
839
|
-
// Get order history
|
|
840
|
-
const orders = await client.siteUsers.getOrders(siteName, {
|
|
841
|
-
limit: 50,
|
|
842
|
-
offset: 0
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
// Get subscriptions
|
|
846
|
-
const subscriptions = await client.siteUsers.getSubscriptions(siteName);
|
|
847
|
-
|
|
848
|
-
// Cancel at end of billing period (default)
|
|
849
|
-
await client.siteUsers.cancelSubscription(siteName, 'sub_123', csrfToken);
|
|
850
|
-
|
|
851
|
-
// Cancel immediately
|
|
852
|
-
await client.siteUsers.cancelSubscription(
|
|
853
|
-
siteName,
|
|
854
|
-
'sub_123',
|
|
855
|
-
csrfToken,
|
|
856
|
-
{ mode: 'immediate' }
|
|
857
|
-
);
|
|
858
|
-
|
|
859
|
-
// Cancel at a scheduled time (absolute)
|
|
860
|
-
await client.siteUsers.cancelSubscription(
|
|
861
|
-
siteName,
|
|
862
|
-
'sub_123',
|
|
863
|
-
csrfToken,
|
|
864
|
-
{ mode: 'scheduled', cancel_at: '2026-05-01T15:30:00Z' }
|
|
865
|
-
);
|
|
866
|
-
|
|
867
|
-
// Cancel after a configured delay (days/hours/minutes)
|
|
868
|
-
await client.siteUsers.cancelSubscription(
|
|
869
|
-
siteName,
|
|
870
|
-
'sub_123',
|
|
871
|
-
csrfToken,
|
|
872
|
-
{ mode: 'scheduled', delay_days: 7, delay_hours: 2 }
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
// Logout
|
|
876
|
-
await client.siteUsers.logout(siteName);
|
|
877
|
-
client.setAuth(null);
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
> 📚 **For complete site users documentation** including waitlist management, metadata patterns, cross-domain authentication, and complete examples, see [docs/site-users.md](docs/site-users.md)
|
|
881
|
-
|
|
882
|
-
## Configuration Options
|
|
883
|
-
|
|
884
|
-
```typescript
|
|
885
|
-
interface PerspectApiConfig {
|
|
886
|
-
baseUrl: string; // Required: API base URL
|
|
887
|
-
apiKey?: string; // Optional: API key for authentication
|
|
888
|
-
jwt?: string; // Optional: JWT token for authentication
|
|
889
|
-
timeout?: number; // Optional: Request timeout in ms (default: 30000)
|
|
890
|
-
retries?: number; // Optional: Number of retries (default: 3)
|
|
891
|
-
headers?: Record<string, string>; // Optional: Additional headers
|
|
892
|
-
}
|
|
893
58
|
```
|
|
894
59
|
|
|
895
60
|
## Error Handling
|
|
896
61
|
|
|
897
|
-
The SDK provides structured error handling:
|
|
898
|
-
|
|
899
62
|
```typescript
|
|
900
|
-
import {
|
|
63
|
+
import { PerspectV2Error } from 'perspectapi-ts-sdk/v2';
|
|
901
64
|
|
|
902
65
|
try {
|
|
903
|
-
|
|
66
|
+
await client.products.get('my-site', 'prod_missing');
|
|
904
67
|
} catch (error) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
console.log('Error message:', apiError.message);
|
|
908
|
-
console.log('Status code:', apiError.status);
|
|
909
|
-
console.log('Error code:', apiError.code);
|
|
910
|
-
console.log('Details:', apiError.details);
|
|
911
|
-
}
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
## Cloudflare Workers Usage
|
|
915
|
-
|
|
916
|
-
The SDK is fully compatible with Cloudflare Workers:
|
|
917
|
-
|
|
918
|
-
```typescript
|
|
919
|
-
// worker.ts
|
|
920
|
-
import { createPerspectApiClient } from 'perspectapi-ts-sdk';
|
|
921
|
-
|
|
922
|
-
export default {
|
|
923
|
-
async fetch(request: Request, env: any): Promise<Response> {
|
|
924
|
-
const client = createPerspectApiClient({
|
|
925
|
-
baseUrl: env.PERSPECT_API_URL,
|
|
926
|
-
apiKey: env.PERSPECT_API_KEY
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
try {
|
|
930
|
-
const content = await client.content.getContent(env.PERSPECT_SITE_NAME, { limit: 10 });
|
|
931
|
-
|
|
932
|
-
return new Response(JSON.stringify(content.data), {
|
|
933
|
-
headers: { 'Content-Type': 'application/json' }
|
|
934
|
-
});
|
|
935
|
-
} catch (error) {
|
|
936
|
-
return new Response('Error fetching content', { status: 500 });
|
|
937
|
-
}
|
|
68
|
+
if (error instanceof PerspectV2Error) {
|
|
69
|
+
console.error(error.type, error.code, error.message);
|
|
938
70
|
}
|
|
939
|
-
}
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
## Advanced Usage
|
|
943
|
-
|
|
944
|
-
### Using Individual Clients
|
|
945
|
-
|
|
946
|
-
```typescript
|
|
947
|
-
import { HttpClient, ContentClient } from 'perspectapi-ts-sdk';
|
|
948
|
-
|
|
949
|
-
// Create HTTP client
|
|
950
|
-
const http = new HttpClient({
|
|
951
|
-
baseUrl: 'https://api.example.com',
|
|
952
|
-
apiKey: 'your-api-key'
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
// Use individual client
|
|
956
|
-
const contentClient = new ContentClient(http);
|
|
957
|
-
const content = await contentClient.getContent('your-site-name');
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
### Custom Headers
|
|
961
|
-
|
|
962
|
-
```typescript
|
|
963
|
-
const client = createPerspectApiClient({
|
|
964
|
-
baseUrl: 'https://api.example.com',
|
|
965
|
-
apiKey: 'your-api-key',
|
|
966
|
-
headers: {
|
|
967
|
-
'X-Custom-Header': 'custom-value',
|
|
968
|
-
'User-Agent': 'MyApp/1.0.0'
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
### Timeout and Retries
|
|
974
|
-
|
|
975
|
-
```typescript
|
|
976
|
-
const client = createPerspectApiClient({
|
|
977
|
-
baseUrl: 'https://api.example.com',
|
|
978
|
-
apiKey: 'your-api-key',
|
|
979
|
-
timeout: 60000, // 60 seconds
|
|
980
|
-
retries: 5 // Retry up to 5 times
|
|
981
|
-
});
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
## Slug Prefix Filtering
|
|
985
|
-
|
|
986
|
-
The SDK supports filtering content and products by `slug_prefix` to organize your content by URL structure:
|
|
987
|
-
|
|
988
|
-
```typescript
|
|
989
|
-
// Filter blog posts
|
|
990
|
-
const blogPosts = await client.content.getContent('mysite', {
|
|
991
|
-
slug_prefix: 'blog', // /blog/post-1, /blog/post-2
|
|
992
|
-
page_status: 'publish'
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
// Filter news articles
|
|
996
|
-
const news = await client.content.getContent('mysite', {
|
|
997
|
-
slug_prefix: 'news', // /news/article-1, /news/article-2
|
|
998
|
-
page_status: 'publish'
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
// Filter shop products
|
|
1002
|
-
const shopProducts = await client.products.getProducts('mysite', {
|
|
1003
|
-
slug_prefix: 'shop', // /shop/product-1, /shop/product-2
|
|
1004
|
-
isActive: true
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
// Filter artwork products
|
|
1008
|
-
const artwork = await client.products.getProducts('mysite', {
|
|
1009
|
-
slug_prefix: 'artwork', // /artwork/painting-1, /artwork/sculpture-1
|
|
1010
|
-
isActive: true
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
// Combine with other filters
|
|
1014
|
-
const results = await client.content.getContent('mysite', {
|
|
1015
|
-
slug_prefix: 'blog',
|
|
1016
|
-
page_status: 'publish',
|
|
1017
|
-
page_type: 'post',
|
|
1018
|
-
search: 'javascript', // Search within blog posts only
|
|
1019
|
-
page: 1,
|
|
1020
|
-
limit: 20
|
|
1021
|
-
});
|
|
71
|
+
}
|
|
1022
72
|
```
|
|
1023
73
|
|
|
1024
|
-
|
|
1025
|
-
- Organize blog posts, news, and documentation separately
|
|
1026
|
-
- Separate shop products from artwork or digital downloads
|
|
1027
|
-
- Create multi-section websites with distinct URL structures
|
|
1028
|
-
- Filter content/products by section for navigation
|
|
1029
|
-
|
|
1030
|
-
**See `examples/slug-prefix-examples.ts` for 20+ real-world examples!**
|
|
1031
|
-
|
|
1032
|
-
## TypeScript Support
|
|
1033
|
-
|
|
1034
|
-
The SDK is written in TypeScript and provides comprehensive type definitions:
|
|
74
|
+
## Caching
|
|
1035
75
|
|
|
1036
76
|
```typescript
|
|
1037
|
-
import
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
ApiResponse,
|
|
1041
|
-
PaginatedResponse
|
|
77
|
+
import {
|
|
78
|
+
createPerspectApiV2Client,
|
|
79
|
+
InMemoryCacheAdapter,
|
|
1042
80
|
} from 'perspectapi-ts-sdk';
|
|
1043
81
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
82
|
+
const client = createPerspectApiV2Client({
|
|
83
|
+
baseUrl: process.env.PERSPECTAPI_BASE_URL!,
|
|
84
|
+
apiKey: process.env.PERSPECTAPI_API_KEY!,
|
|
85
|
+
cache: {
|
|
86
|
+
adapter: new InMemoryCacheAdapter(),
|
|
87
|
+
defaultTtlSeconds: 300,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
1047
90
|
```
|
|
1048
91
|
|
|
1049
|
-
##
|
|
1050
|
-
|
|
1051
|
-
1. Fork the repository
|
|
1052
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1053
|
-
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
1054
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1055
|
-
5. Open a Pull Request
|
|
1056
|
-
|
|
1057
|
-
## License
|
|
92
|
+
## API Reference
|
|
1058
93
|
|
|
1059
|
-
|
|
94
|
+
- Current SDK entry point: `perspectapi-ts-sdk/v2`
|
|
95
|
+
- Current REST base path: `/api/v2`
|
|
96
|
+
- Public docs: `https://perspect.com/docs/sdk/typescript/v2-client`
|
|
97
|
+
- LLM docs: `https://perspect.com/llms.txt`
|
|
1060
98
|
|
|
1061
|
-
##
|
|
99
|
+
## Migration
|
|
1062
100
|
|
|
1063
|
-
-
|
|
1064
|
-
- 🐛 [Issue Tracker](https://github.com/perspectapi/perspectapi-ts-sdk/issues)
|
|
1065
|
-
- 💬 [Discussions](https://github.com/perspectapi/perspectapi-ts-sdk/discussions)
|
|
1066
|
-
- 📧 [Email Support](mailto:support@perspectapi.com)
|
|
101
|
+
If you are maintaining existing v1 code, migrate it before **2026-06-01**. v1 runtime responses include `Deprecation`, `Sunset`, `Warning`, `Link`, and `X-Perspect-Deprecation-Notice` headers, and v1 SDK constructors emit a warning.
|