livepasses 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/LICENSE +21 -0
- package/README.md +431 -0
- package/dist/index.d.mts +677 -0
- package/dist/index.d.ts +677 -0
- package/dist/index.js +590 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +576 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Livepasses
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Livepasses Node.js/TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the [Livepasses API](https://livepasses.com) - generate, manage, and redeem Apple Wallet and Google Wallet passes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install livepasses
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires **Node.js 18+** (uses native `fetch`). Zero runtime dependencies.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Livepasses } from 'livepasses';
|
|
17
|
+
|
|
18
|
+
const client = new Livepasses('lp_api_key_...');
|
|
19
|
+
|
|
20
|
+
const result = await client.passes.generate({
|
|
21
|
+
templateId: 'your-template-id',
|
|
22
|
+
passes: [{
|
|
23
|
+
customer: { firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com' },
|
|
24
|
+
businessData: { sectionInfo: 'A', rowInfo: '12', seatNumber: '5' },
|
|
25
|
+
}],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(result.passes[0].platforms.apple.addToWalletUrl);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Pass your API key when creating the client. Get your key at [dashboard.livepasses.com/api-keys](https://dashboard.livepasses.com/api-keys).
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const client = new Livepasses('lp_api_key_...', {
|
|
37
|
+
baseUrl: 'https://api.livepasses.com', // default
|
|
38
|
+
timeout: 30000, // 30s default
|
|
39
|
+
maxRetries: 3, // default
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Pass Generation
|
|
44
|
+
|
|
45
|
+
### Single pass (synchronous)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const result = await client.passes.generate({
|
|
49
|
+
templateId: 'template-id',
|
|
50
|
+
businessContext: {
|
|
51
|
+
event: {
|
|
52
|
+
eventName: 'Summer Concert 2026',
|
|
53
|
+
eventDate: '2026-07-15T18:00:00Z',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
passes: [{
|
|
57
|
+
customer: { firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com' },
|
|
58
|
+
businessData: {
|
|
59
|
+
sectionInfo: 'VIP', rowInfo: 'A', seatNumber: '12',
|
|
60
|
+
ticketType: 'VIP', price: 150, currency: 'USD',
|
|
61
|
+
},
|
|
62
|
+
}],
|
|
63
|
+
options: { deliveryMethod: 'email' },
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Batch passes (auto-polling)
|
|
68
|
+
|
|
69
|
+
For multiple recipients, `generateAndWait` automatically polls until the batch completes:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const result = await client.passes.generateAndWait(
|
|
73
|
+
{
|
|
74
|
+
templateId: 'template-id',
|
|
75
|
+
passes: recipients.map(r => ({
|
|
76
|
+
customer: r,
|
|
77
|
+
businessData: { ticketType: 'General' },
|
|
78
|
+
})),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pollInterval: 2000, // 2s between polls (default)
|
|
82
|
+
onProgress: (status) => {
|
|
83
|
+
console.log(`${status.progressPercentage}% complete`);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Batch status (manual polling)
|
|
90
|
+
|
|
91
|
+
If you need more control over polling, use `generate` + `getBatchStatus`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const initial = await client.passes.generate({
|
|
95
|
+
templateId: 'template-id',
|
|
96
|
+
passes: recipients.map(r => ({
|
|
97
|
+
customer: r,
|
|
98
|
+
businessData: { ticketType: 'General' },
|
|
99
|
+
})),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (initial.batchOperation) {
|
|
103
|
+
let status = await client.passes.getBatchStatus(initial.batchOperation.batchId);
|
|
104
|
+
while (!status.isCompleted) {
|
|
105
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
106
|
+
status = await client.passes.getBatchStatus(initial.batchOperation.batchId);
|
|
107
|
+
console.log(`Progress: ${status.progressPercentage}%`);
|
|
108
|
+
}
|
|
109
|
+
console.log(`Completed: ${status.statistics.successful} successful, ${status.statistics.failed} failed`);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Pass Lifecycle
|
|
114
|
+
|
|
115
|
+
### Lookup
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const pass = await client.passes.lookup({ passId: 'pass-id' });
|
|
119
|
+
// or by pass number
|
|
120
|
+
const pass = await client.passes.lookup({ passNumber: 'LP-001' });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Validate
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const validation = await client.passes.validate('pass-id');
|
|
127
|
+
if (validation.canBeRedeemed) {
|
|
128
|
+
// proceed with redemption
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Redeem
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Generic redemption
|
|
136
|
+
const result = await client.passes.redeem('pass-id');
|
|
137
|
+
|
|
138
|
+
// Event check-in
|
|
139
|
+
const result = await client.passes.checkIn('pass-id', {
|
|
140
|
+
location: { name: 'Gate 1', latitude: 40.71, longitude: -74.00 },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Coupon redemption
|
|
144
|
+
const result = await client.passes.redeemCoupon('pass-id');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Update a pass
|
|
148
|
+
|
|
149
|
+
Update business data or context on an existing pass:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
await client.passes.update('pass-id', {
|
|
153
|
+
businessData: { currentPoints: 750, memberTier: 'Platinum' },
|
|
154
|
+
businessContext: {
|
|
155
|
+
loyalty: { programUpdate: 'Congratulations on reaching Platinum!' },
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Bulk update
|
|
161
|
+
|
|
162
|
+
Update multiple passes at once:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
await client.passes.bulkUpdate({
|
|
166
|
+
passIds: ['pass-1', 'pass-2', 'pass-3'],
|
|
167
|
+
businessData: { memberTier: 'Gold' },
|
|
168
|
+
businessContext: {
|
|
169
|
+
loyalty: { seasonalMessage: 'Happy holidays from our team!' },
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Pass Types
|
|
175
|
+
|
|
176
|
+
### Event Passes
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
await client.passes.generate({
|
|
180
|
+
templateId: 'event-template-id',
|
|
181
|
+
businessContext: { event: { eventName: 'Concert', eventDate: '2026-07-15T18:00:00Z' } },
|
|
182
|
+
passes: [{
|
|
183
|
+
customer: { firstName: 'Jane', lastName: 'Doe' },
|
|
184
|
+
businessData: { sectionInfo: 'A', rowInfo: '1', seatNumber: '5', gateInfo: 'North' },
|
|
185
|
+
}],
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Loyalty Cards
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
await client.passes.generate({
|
|
193
|
+
templateId: 'loyalty-template-id',
|
|
194
|
+
passes: [{
|
|
195
|
+
customer: { firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com' },
|
|
196
|
+
businessData: { membershipNumber: 'MEM-001', currentPoints: 500, memberTier: 'Gold' },
|
|
197
|
+
}],
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Earn points
|
|
201
|
+
await client.passes.loyaltyTransact('pass-id', {
|
|
202
|
+
transactionType: 'earn',
|
|
203
|
+
points: 100,
|
|
204
|
+
description: 'Purchase reward',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Spend points
|
|
208
|
+
await client.passes.loyaltyTransact('pass-id', {
|
|
209
|
+
transactionType: 'spend',
|
|
210
|
+
points: 50,
|
|
211
|
+
description: 'Reward redemption',
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Coupon Passes
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
await client.passes.generate({
|
|
219
|
+
templateId: 'coupon-template-id',
|
|
220
|
+
businessContext: { coupon: { campaignName: 'Summer Sale', specialMessage: '20% off!' } },
|
|
221
|
+
passes: [{
|
|
222
|
+
customer: { firstName: 'Jane', lastName: 'Doe' },
|
|
223
|
+
businessData: { promoCode: 'SUMMER20', maxUsageCount: 1 },
|
|
224
|
+
}],
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Templates
|
|
229
|
+
|
|
230
|
+
### List and get
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// List templates
|
|
234
|
+
const templates = await client.templates.list({ status: 'Active' });
|
|
235
|
+
|
|
236
|
+
// Get template details
|
|
237
|
+
const template = await client.templates.get('template-id');
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Create a template
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const template = await client.templates.create({
|
|
244
|
+
name: 'VIP Event Pass',
|
|
245
|
+
description: 'Premium event ticket template',
|
|
246
|
+
businessFeatures: {
|
|
247
|
+
passType: 'event',
|
|
248
|
+
hasSeating: true,
|
|
249
|
+
supportedPlatforms: ['apple', 'google'],
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Update a template
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const updated = await client.templates.update('template-id', {
|
|
258
|
+
name: 'VIP Event Pass v2',
|
|
259
|
+
description: 'Updated premium event ticket template',
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Activate / deactivate
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
await client.templates.activate('template-id');
|
|
267
|
+
await client.templates.deactivate('template-id');
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Webhooks
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// Register a webhook
|
|
274
|
+
const webhook = await client.webhooks.create({
|
|
275
|
+
url: 'https://your-app.com/webhooks/livepasses',
|
|
276
|
+
events: ['pass.generated', 'pass.redeemed', 'batch.completed'],
|
|
277
|
+
});
|
|
278
|
+
console.log(webhook.secret); // use this to verify webhook signatures
|
|
279
|
+
|
|
280
|
+
// List webhooks
|
|
281
|
+
const webhooks = await client.webhooks.list();
|
|
282
|
+
|
|
283
|
+
// Remove a webhook
|
|
284
|
+
await client.webhooks.delete('webhook-id');
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Error Handling
|
|
288
|
+
|
|
289
|
+
All errors are typed for precise `catch` handling:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import {
|
|
293
|
+
Livepasses,
|
|
294
|
+
LivepassesError,
|
|
295
|
+
AuthenticationError,
|
|
296
|
+
ValidationError,
|
|
297
|
+
ForbiddenError,
|
|
298
|
+
NotFoundError,
|
|
299
|
+
RateLimitError,
|
|
300
|
+
QuotaExceededError,
|
|
301
|
+
BusinessRuleError,
|
|
302
|
+
ApiErrorCodes,
|
|
303
|
+
} from 'livepasses';
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
await client.passes.generate({ ... });
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if (err instanceof AuthenticationError) {
|
|
309
|
+
console.error('Invalid API key');
|
|
310
|
+
} else if (err instanceof ValidationError) {
|
|
311
|
+
console.error('Invalid input:', err.message, err.details);
|
|
312
|
+
} else if (err instanceof ForbiddenError) {
|
|
313
|
+
console.error('Insufficient permissions for this operation');
|
|
314
|
+
} else if (err instanceof RateLimitError) {
|
|
315
|
+
console.error(`Rate limited. Retry after ${err.retryAfter}s`);
|
|
316
|
+
} else if (err instanceof QuotaExceededError) {
|
|
317
|
+
console.error('Monthly pass quota exceeded — upgrade your plan');
|
|
318
|
+
} else if (err instanceof NotFoundError) {
|
|
319
|
+
console.error('Template or pass not found');
|
|
320
|
+
} else if (err instanceof BusinessRuleError) {
|
|
321
|
+
console.error('Business rule:', err.message);
|
|
322
|
+
} else if (err instanceof LivepassesError) {
|
|
323
|
+
// Catch-all for any other API error
|
|
324
|
+
console.error(`API error [${err.code}]: ${err.message} (HTTP ${err.status})`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Error codes
|
|
330
|
+
|
|
331
|
+
Use the `ApiErrorCodes` constant for programmatic error code comparisons:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { LivepassesError, ApiErrorCodes } from 'livepasses';
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
await client.passes.redeem('pass-id');
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if (err instanceof LivepassesError) {
|
|
340
|
+
switch (err.code) {
|
|
341
|
+
case ApiErrorCodes.PASS_ALREADY_USED:
|
|
342
|
+
console.error('This pass has already been redeemed');
|
|
343
|
+
break;
|
|
344
|
+
case ApiErrorCodes.PASS_EXPIRED:
|
|
345
|
+
console.error('This pass has expired');
|
|
346
|
+
break;
|
|
347
|
+
case ApiErrorCodes.TEMPLATE_INACTIVE:
|
|
348
|
+
console.error('The template for this pass is inactive');
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
console.error(`Unhandled error: ${err.code}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Exception hierarchy
|
|
358
|
+
|
|
359
|
+
| Error Class | HTTP Status | When |
|
|
360
|
+
|-------------|------------|------|
|
|
361
|
+
| `AuthenticationError` | 401 | Invalid, expired, or revoked API key |
|
|
362
|
+
| `ValidationError` | 400 | Request validation failed |
|
|
363
|
+
| `ForbiddenError` | 403 | Insufficient permissions |
|
|
364
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
365
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
366
|
+
| `QuotaExceededError` | 403 | API quota or subscription limit exceeded |
|
|
367
|
+
| `BusinessRuleError` | 422 | Business rule violation (pass expired, already used, etc.) |
|
|
368
|
+
|
|
369
|
+
## Pagination
|
|
370
|
+
|
|
371
|
+
### Manual pagination
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const page1 = await client.passes.list({ page: 1, pageSize: 50 });
|
|
375
|
+
console.log(`Page 1 of ${page1.pagination.totalPages} (${page1.pagination.totalItems} total)`);
|
|
376
|
+
|
|
377
|
+
// Get next page
|
|
378
|
+
if (page1.pagination.currentPage < page1.pagination.totalPages) {
|
|
379
|
+
const page2 = await client.passes.list({ page: 2, pageSize: 50 });
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Auto-pagination
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
for await (const pass of client.passes.listAutoPaginate({ templateId: 'tpl-id' })) {
|
|
387
|
+
console.log(pass.id);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Configuration
|
|
392
|
+
|
|
393
|
+
| Option | Default | Description |
|
|
394
|
+
|--------|---------|-------------|
|
|
395
|
+
| `baseUrl` | `https://api.livepasses.com` | API base URL |
|
|
396
|
+
| `timeout` | `30000` | Request timeout in ms |
|
|
397
|
+
| `maxRetries` | `3` | Max retries for failed requests (429, 5xx) |
|
|
398
|
+
|
|
399
|
+
### Automatic retries
|
|
400
|
+
|
|
401
|
+
The SDK automatically retries on:
|
|
402
|
+
- **429 Too Many Requests** — honors `Retry-After` header
|
|
403
|
+
- **5xx Server Errors** — exponential backoff with jitter
|
|
404
|
+
|
|
405
|
+
## TypeScript
|
|
406
|
+
|
|
407
|
+
The SDK is written in TypeScript and ships with full type declarations. All request params and response types are exported:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import type { GeneratePassesParams, PassGenerationResult, BatchStatusResult } from 'livepasses';
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Examples
|
|
414
|
+
|
|
415
|
+
See the [`examples/`](./examples/) directory for runnable scripts:
|
|
416
|
+
|
|
417
|
+
- **[generate-passes.ts](./examples/generate-passes.ts)** — End-to-end pass generation, lookup, validation, and check-in
|
|
418
|
+
- **[loyalty-workflow.ts](./examples/loyalty-workflow.ts)** — Loyalty card lifecycle: generate, earn points, spend points, update tier
|
|
419
|
+
- **[coupon-workflow.ts](./examples/coupon-workflow.ts)** — Coupon pass generation and redemption
|
|
420
|
+
- **[template-management.ts](./examples/template-management.ts)** — CRUD operations on pass templates
|
|
421
|
+
- **[webhook-setup.ts](./examples/webhook-setup.ts)** — Register, list, and manage webhooks
|
|
422
|
+
|
|
423
|
+
Run any example with:
|
|
424
|
+
```bash
|
|
425
|
+
export LIVEPASSES_API_KEY="your-api-key"
|
|
426
|
+
npx tsx examples/generate-passes.ts
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## License
|
|
430
|
+
|
|
431
|
+
MIT
|