snapapi-sdk 1.3.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.
Files changed (4) hide show
  1. package/README.md +131 -0
  2. package/index.d.ts +177 -0
  3. package/index.js +300 -0
  4. package/package.json +17 -0
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # snapapi-js
2
+
3
+ Official Node.js SDK for [SnapAPI](https://snapapi.tech) — capture any website as an image.
4
+
5
+ Zero dependencies. Uses native `fetch` (Node 18+).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install snapapi-js
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```js
16
+ const SnapAPI = require('snapapi-js');
17
+ const fs = require('fs');
18
+
19
+ const snap = new SnapAPI('snap_your_key_here');
20
+
21
+ // Basic screenshot
22
+ const image = await snap.screenshot('example.com');
23
+ fs.writeFileSync('screenshot.png', image);
24
+
25
+ // With options
26
+ const webp = await snap.screenshot('example.com', {
27
+ format: 'webp',
28
+ darkMode: true,
29
+ fullPage: true,
30
+ });
31
+ fs.writeFileSync('screenshot.webp', webp);
32
+
33
+ // Mobile screenshot (iPhone 14)
34
+ const mobile = await snap.screenshot('example.com', { device: 'iphone14' });
35
+
36
+ // Capture a specific element
37
+ const header = await snap.screenshot('example.com', { selector: 'header' });
38
+
39
+ // Get structured page data (great for AI agents)
40
+ const meta = await snap.screenshot('example.com', { meta: true });
41
+ console.log(meta.title, meta.description);
42
+ console.log(meta.headings); // [{level: 1, text: "..."}, ...]
43
+ console.log(meta.links); // [{text: "...", href: "..."}, ...]
44
+ console.log(meta.text); // Extracted visible text (first 2000 chars)
45
+
46
+ // Check your usage
47
+ const usage = await snap.usage();
48
+ console.log(`${usage.usage.count} screenshots this month`);
49
+ ```
50
+
51
+ ## Screenshot Options
52
+
53
+ | Option | Type | Description |
54
+ |------------|---------|--------------------------------------|
55
+ | `width` | number | Viewport width in pixels |
56
+ | `height` | number | Viewport height in pixels |
57
+ | `format` | string | `"png"`, `"jpeg"`, or `"webp"` |
58
+ | `quality` | number | Image quality 1-100 (jpeg/webp only) |
59
+ | `fullPage` | boolean | Capture the full scrollable page |
60
+ | `delay` | number | Wait ms before capturing |
61
+ | `darkMode` | boolean | Emulate dark color scheme |
62
+ | `blockAds` | boolean | Block common ad networks |
63
+ | `cache` | boolean | Use cached result if available |
64
+ | `selector` | string | CSS selector to capture |
65
+ | `device` | string | Device preset: `"iphone14"`, `"pixel7"`, `"ipad"`, `"desktop"`, etc. |
66
+ | `meta` | boolean | Return page metadata as JSON |
67
+
68
+ ## Error Handling
69
+
70
+ ```js
71
+ const { SnapAPIError } = require('snapapi-js');
72
+
73
+ try {
74
+ const image = await snap.screenshot('example.com');
75
+ } catch (err) {
76
+ if (err instanceof SnapAPIError) {
77
+ console.error(err.status, err.message);
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Scheduled Monitors
83
+
84
+ ```js
85
+ // Create a monitor — captures every 15 minutes
86
+ const monitor = await snap.createMonitor({
87
+ url: 'https://example.com',
88
+ name: 'Homepage',
89
+ interval_minutes: 15,
90
+ params: { format: 'webp', fullPage: true },
91
+ webhook_url: 'https://your-app.com/webhook',
92
+ });
93
+
94
+ // List all monitors
95
+ const monitors = await snap.listMonitors();
96
+
97
+ // Pause / resume
98
+ await snap.updateMonitor(monitor.id, { status: 'paused' });
99
+ await snap.updateMonitor(monitor.id, { status: 'active' });
100
+
101
+ // Browse snapshots
102
+ const { snapshots } = await snap.listSnapshots(monitor.id, { limit: 10 });
103
+
104
+ // Download a snapshot image
105
+ const image = await snap.getSnapshot(monitor.id, snapshots[0].id);
106
+ fs.writeFileSync('snapshot.webp', image);
107
+
108
+ // Delete a monitor (cascades to snapshots)
109
+ await snap.deleteMonitor(monitor.id);
110
+ ```
111
+
112
+ > Monitors require **Starter** tier or above. See [pricing](https://snapapi.tech/#pricing) for limits.
113
+
114
+ ## Account Management
115
+
116
+ ```js
117
+ // Full account info
118
+ const account = await snap.account();
119
+
120
+ // Update settings
121
+ await snap.updateSettings({ name: 'My App' });
122
+
123
+ // Sign up (no API key needed)
124
+ const snap = new SnapAPI('placeholder');
125
+ const result = await snap.signup('you@example.com');
126
+ console.log(result.key); // Your new API key
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,177 @@
1
+ declare class SnapAPIError extends Error {
2
+ name: 'SnapAPIError';
3
+ status: number;
4
+ body: unknown;
5
+ constructor(message: string, status: number, body: unknown);
6
+ }
7
+
8
+ interface SnapAPIOptions {
9
+ baseUrl?: string;
10
+ }
11
+
12
+ interface ScreenshotOptions {
13
+ width?: number;
14
+ height?: number;
15
+ format?: 'png' | 'jpeg' | 'webp';
16
+ quality?: number;
17
+ fullPage?: boolean;
18
+ delay?: number;
19
+ darkMode?: boolean;
20
+ blockAds?: boolean;
21
+ cache?: boolean;
22
+ selector?: string;
23
+ device?: 'iphone14' | 'iphone14pro' | 'iphone15' | 'pixel7' | 'pixel8' | 'galaxys23' | 'ipad' | 'ipadpro' | 'desktop';
24
+ meta?: boolean;
25
+ }
26
+
27
+ interface MetadataResult {
28
+ url: string;
29
+ title: string;
30
+ description: string;
31
+ og_title: string;
32
+ og_image: string;
33
+ og_type: string;
34
+ favicon: string;
35
+ viewport: string;
36
+ canonical: string;
37
+ language: string;
38
+ headings: Array<{ level: number; text: string }>;
39
+ links: Array<{ text: string; href: string }>;
40
+ }
41
+
42
+ interface RenderOptions {
43
+ width?: number;
44
+ height?: number;
45
+ format?: 'png' | 'jpeg' | 'webp';
46
+ quality?: number;
47
+ }
48
+
49
+ interface UsageResponse {
50
+ tier: string;
51
+ usage: {
52
+ month: string;
53
+ count: number;
54
+ };
55
+ limits: Record<string, unknown>;
56
+ }
57
+
58
+ interface SignupResponse {
59
+ key: string;
60
+ tier: string;
61
+ limit: number;
62
+ }
63
+
64
+ interface AccountSettings {
65
+ name?: string;
66
+ allowed_domains?: string[];
67
+ }
68
+
69
+ interface MonitorParams {
70
+ url: string;
71
+ name?: string;
72
+ interval_minutes: number;
73
+ params?: Partial<ScreenshotOptions>;
74
+ webhook_url?: string;
75
+ }
76
+
77
+ interface MonitorUpdateParams {
78
+ name?: string;
79
+ interval_minutes?: number;
80
+ status?: 'active' | 'paused';
81
+ params?: Partial<ScreenshotOptions>;
82
+ webhook_url?: string;
83
+ }
84
+
85
+ interface Monitor {
86
+ id: string;
87
+ name: string;
88
+ url: string;
89
+ interval_minutes: number;
90
+ status: string;
91
+ params: Record<string, unknown>;
92
+ webhook_url: string | null;
93
+ created_at: string;
94
+ last_run: string | null;
95
+ next_run: string | null;
96
+ run_count: number;
97
+ error_count: number;
98
+ last_error: string | null;
99
+ }
100
+
101
+ interface Snapshot {
102
+ id: string;
103
+ monitor_id: string;
104
+ taken_at: string;
105
+ file_size: number;
106
+ status: string;
107
+ error: string | null;
108
+ meta: Record<string, unknown>;
109
+ }
110
+
111
+ interface SnapshotListResponse {
112
+ snapshots: Snapshot[];
113
+ total: number;
114
+ limit: number;
115
+ offset: number;
116
+ }
117
+
118
+ declare class SnapAPI {
119
+ constructor(apiKey: string, options?: SnapAPIOptions);
120
+
121
+ /**
122
+ * Capture a screenshot of a URL.
123
+ * Returns a Buffer (image) when meta is false/omitted,
124
+ * or a metadata object when meta is true.
125
+ */
126
+ screenshot(url: string, options?: ScreenshotOptions & { meta?: false }): Promise<Buffer>;
127
+ screenshot(url: string, options: ScreenshotOptions & { meta: true }): Promise<Record<string, unknown>>;
128
+
129
+ /**
130
+ * Extract metadata from a URL without taking a screenshot.
131
+ * Available on all plans.
132
+ */
133
+ metadata(url: string): Promise<MetadataResult>;
134
+
135
+ /**
136
+ * Render raw HTML to an image. Requires Starter tier or above.
137
+ */
138
+ render(html: string, options?: RenderOptions): Promise<Buffer>;
139
+
140
+ /** Get current usage stats for this API key. */
141
+ usage(): Promise<UsageResponse>;
142
+
143
+ /** Get full account information. */
144
+ account(): Promise<Record<string, unknown>>;
145
+
146
+ /** Update account settings. */
147
+ updateSettings(settings: AccountSettings): Promise<Record<string, unknown>>;
148
+
149
+ /** Sign up for a free SnapAPI account. Does not require an API key. */
150
+ signup(email: string): Promise<SignupResponse>;
151
+
152
+ // ─── Monitors (Scheduled Screenshots) ───
153
+
154
+ /** Create a new monitor. Requires Starter tier or above. */
155
+ createMonitor(params: MonitorParams): Promise<Monitor>;
156
+
157
+ /** List all monitors for this API key. */
158
+ listMonitors(): Promise<Monitor[]>;
159
+
160
+ /** Get a single monitor by ID. */
161
+ getMonitor(id: string): Promise<Monitor>;
162
+
163
+ /** Update a monitor (name, interval, status, params, webhook_url). */
164
+ updateMonitor(id: string, updates: MonitorUpdateParams): Promise<Monitor>;
165
+
166
+ /** Delete a monitor and all its snapshots. */
167
+ deleteMonitor(id: string): Promise<void>;
168
+
169
+ /** List snapshots for a monitor. */
170
+ listSnapshots(monitorId: string, options?: { limit?: number; offset?: number }): Promise<SnapshotListResponse>;
171
+
172
+ /** Download a specific snapshot image. */
173
+ getSnapshot(monitorId: string, snapshotId: string): Promise<Buffer>;
174
+ }
175
+
176
+ export = SnapAPI;
177
+ export { SnapAPIError };
package/index.js ADDED
@@ -0,0 +1,300 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_BASE_URL = 'https://snapapi.tech';
4
+
5
+ class SnapAPIError extends Error {
6
+ constructor(message, status, body) {
7
+ super(message);
8
+ this.name = 'SnapAPIError';
9
+ this.status = status;
10
+ this.body = body;
11
+ }
12
+ }
13
+
14
+ function toSnakeCase(str) {
15
+ return str.replace(/[A-Z]/g, (ch) => '_' + ch.toLowerCase());
16
+ }
17
+
18
+ class SnapAPI {
19
+ /**
20
+ * @param {string} apiKey - Your SnapAPI key (e.g. "snap_...")
21
+ * @param {object} [options]
22
+ * @param {string} [options.baseUrl] - API base URL
23
+ */
24
+ constructor(apiKey, options = {}) {
25
+ if (!apiKey || typeof apiKey !== 'string') {
26
+ throw new SnapAPIError('API key is required', 0, null);
27
+ }
28
+ this.apiKey = apiKey;
29
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
30
+ }
31
+
32
+ /**
33
+ * Internal request helper.
34
+ * @private
35
+ */
36
+ async _request(method, path, { query, json, needsAuth = true } = {}) {
37
+ let url = `${this.baseUrl}${path}`;
38
+
39
+ if (query) {
40
+ const params = new URLSearchParams();
41
+ for (const [k, v] of Object.entries(query)) {
42
+ if (v !== undefined && v !== null) {
43
+ params.set(k, String(v));
44
+ }
45
+ }
46
+ const qs = params.toString();
47
+ if (qs) url += '?' + qs;
48
+ }
49
+
50
+ const headers = {};
51
+ if (needsAuth) {
52
+ headers['x-api-key'] = this.apiKey;
53
+ }
54
+
55
+ const fetchOptions = { method, headers };
56
+
57
+ if (json) {
58
+ headers['content-type'] = 'application/json';
59
+ fetchOptions.body = JSON.stringify(json);
60
+ }
61
+
62
+ const res = await fetch(url, fetchOptions);
63
+
64
+ if (!res.ok) {
65
+ let body = null;
66
+ let message = `SnapAPI request failed: ${res.status} ${res.statusText}`;
67
+ try {
68
+ body = await res.json();
69
+ if (body.error) message = body.error;
70
+ else if (body.message) message = body.message;
71
+ } catch {
72
+ // response wasn't JSON
73
+ }
74
+ throw new SnapAPIError(message, res.status, body);
75
+ }
76
+
77
+ return res;
78
+ }
79
+
80
+ /**
81
+ * Capture a screenshot of a URL.
82
+ *
83
+ * @param {string} url - The webpage URL to capture
84
+ * @param {object} [options]
85
+ * @param {number} [options.width] - Viewport width in px
86
+ * @param {number} [options.height] - Viewport height in px
87
+ * @param {string} [options.format] - "png" | "jpeg" | "webp"
88
+ * @param {number} [options.quality] - Image quality 1-100 (jpeg/webp)
89
+ * @param {boolean} [options.fullPage] - Capture full scrollable page
90
+ * @param {number} [options.delay] - Wait ms before capture
91
+ * @param {boolean} [options.darkMode] - Emulate dark color scheme
92
+ * @param {boolean} [options.blockAds] - Block common ad networks
93
+ * @param {boolean} [options.cache] - Use cached result if available
94
+ * @param {string} [options.selector] - CSS selector to capture
95
+ * @param {string} [options.device] - Device preset: "iphone14", "pixel7", "ipad", "desktop", etc.
96
+ * @param {boolean} [options.meta] - Return page metadata instead of image
97
+ * @returns {Promise<Buffer|object>} Image buffer, or metadata object when meta=true
98
+ */
99
+ async screenshot(url, options = {}) {
100
+ if (!url || typeof url !== 'string') {
101
+ throw new SnapAPIError('URL is required', 0, null);
102
+ }
103
+
104
+ const query = { url };
105
+
106
+ for (const [key, value] of Object.entries(options)) {
107
+ if (value === undefined || value === null) continue;
108
+ const snakeKey = toSnakeCase(key);
109
+ query[snakeKey] = value;
110
+ }
111
+
112
+ const wantMeta = !!options.meta;
113
+
114
+ const res = await this._request('GET', '/v1/screenshot', { query });
115
+
116
+ if (wantMeta) {
117
+ return res.json();
118
+ }
119
+
120
+ const arrayBuffer = await res.arrayBuffer();
121
+ return Buffer.from(arrayBuffer);
122
+ }
123
+
124
+ /**
125
+ * Extract metadata from a URL without taking a screenshot.
126
+ * Returns title, description, OG tags, favicon, headings, links, and more.
127
+ * Available on all plans.
128
+ * @param {string} url - The webpage URL to extract metadata from
129
+ * @returns {Promise<object>}
130
+ */
131
+ async metadata(url) {
132
+ if (!url || typeof url !== 'string') {
133
+ throw new SnapAPIError('URL is required', 0, null);
134
+ }
135
+ const res = await this._request('GET', '/v1/metadata', { query: { url } });
136
+ return res.json();
137
+ }
138
+
139
+ /**
140
+ * Render raw HTML to an image (PNG, JPEG, or WebP).
141
+ * Perfect for OG cards, email previews, and dashboard exports.
142
+ * Requires Starter tier or above.
143
+ * @param {string} html - Full HTML string to render (max 2MB)
144
+ * @param {object} [options]
145
+ * @param {number} [options.width] - Viewport width in px (default 1200)
146
+ * @param {number} [options.height] - Viewport height in px (default 630)
147
+ * @param {string} [options.format] - "png" | "jpeg" | "webp" (default "png")
148
+ * @param {number} [options.quality] - Image quality 1-100 for jpeg/webp (default 90)
149
+ * @returns {Promise<Buffer>}
150
+ */
151
+ async render(html, options = {}) {
152
+ if (!html || typeof html !== 'string') {
153
+ throw new SnapAPIError('html is required', 0, null);
154
+ }
155
+ const res = await this._request('POST', '/v1/render', {
156
+ json: {
157
+ html,
158
+ width: options.width || 1200,
159
+ height: options.height || 630,
160
+ format: options.format || 'png',
161
+ quality: options.quality || 90,
162
+ },
163
+ });
164
+ const arrayBuffer = await res.arrayBuffer();
165
+ return Buffer.from(arrayBuffer);
166
+ }
167
+
168
+ /**
169
+ * Get current usage stats for this API key.
170
+ * @returns {Promise<{ tier: string, usage: { month: string, count: number }, limits: object }>}
171
+ */
172
+ async usage() {
173
+ const res = await this._request('GET', '/v1/usage');
174
+ return res.json();
175
+ }
176
+
177
+ /**
178
+ * Get full account information.
179
+ * @returns {Promise<object>}
180
+ */
181
+ async account() {
182
+ const res = await this._request('GET', '/v1/account');
183
+ return res.json();
184
+ }
185
+
186
+ /**
187
+ * Update account settings.
188
+ * @param {object} settings
189
+ * @param {string} [settings.name]
190
+ * @param {string[]} [settings.allowed_domains]
191
+ * @returns {Promise<object>}
192
+ */
193
+ async updateSettings(settings) {
194
+ if (!settings || typeof settings !== 'object') {
195
+ throw new SnapAPIError('Settings object is required', 0, null);
196
+ }
197
+ const res = await this._request('PATCH', '/v1/account', { json: settings });
198
+ return res.json();
199
+ }
200
+
201
+ // ─── Monitors (Scheduled Screenshots) ───
202
+
203
+ /**
204
+ * Create a new monitor.
205
+ * @param {object} params
206
+ * @param {string} params.url - URL to monitor
207
+ * @param {string} [params.name] - Display name
208
+ * @param {number} params.interval_minutes - Capture interval in minutes
209
+ * @param {object} [params.params] - Screenshot options (width, height, format, etc.)
210
+ * @param {string} [params.webhook_url] - Webhook URL for notifications
211
+ * @returns {Promise<object>}
212
+ */
213
+ async createMonitor(params) {
214
+ const res = await this._request('POST', '/v1/monitors', { json: params });
215
+ return res.json();
216
+ }
217
+
218
+ /**
219
+ * List all monitors for this API key.
220
+ * @returns {Promise<object[]>}
221
+ */
222
+ async listMonitors() {
223
+ const res = await this._request('GET', '/v1/monitors');
224
+ return res.json();
225
+ }
226
+
227
+ /**
228
+ * Get a single monitor by ID.
229
+ * @param {string} id
230
+ * @returns {Promise<object>}
231
+ */
232
+ async getMonitor(id) {
233
+ const res = await this._request('GET', `/v1/monitors/${id}`);
234
+ return res.json();
235
+ }
236
+
237
+ /**
238
+ * Update a monitor.
239
+ * @param {string} id
240
+ * @param {object} updates - Fields to update (name, interval_minutes, status, params, webhook_url)
241
+ * @returns {Promise<object>}
242
+ */
243
+ async updateMonitor(id, updates) {
244
+ const res = await this._request('PATCH', `/v1/monitors/${id}`, { json: updates });
245
+ return res.json();
246
+ }
247
+
248
+ /**
249
+ * Delete a monitor and all its snapshots.
250
+ * @param {string} id
251
+ * @returns {Promise<void>}
252
+ */
253
+ async deleteMonitor(id) {
254
+ await this._request('DELETE', `/v1/monitors/${id}`);
255
+ }
256
+
257
+ /**
258
+ * List snapshots for a monitor.
259
+ * @param {string} monitorId
260
+ * @param {object} [options]
261
+ * @param {number} [options.limit] - Max results (default 50)
262
+ * @param {number} [options.offset] - Pagination offset
263
+ * @returns {Promise<object>}
264
+ */
265
+ async listSnapshots(monitorId, options = {}) {
266
+ const res = await this._request('GET', `/v1/monitors/${monitorId}/snapshots`, { query: options });
267
+ return res.json();
268
+ }
269
+
270
+ /**
271
+ * Download a specific snapshot image.
272
+ * @param {string} monitorId
273
+ * @param {string} snapshotId
274
+ * @returns {Promise<Buffer>}
275
+ */
276
+ async getSnapshot(monitorId, snapshotId) {
277
+ const res = await this._request('GET', `/v1/monitors/${monitorId}/snapshots/${snapshotId}`);
278
+ const arrayBuffer = await res.arrayBuffer();
279
+ return Buffer.from(arrayBuffer);
280
+ }
281
+
282
+ /**
283
+ * Sign up for a free SnapAPI account. Does not require an API key.
284
+ * @param {string} email
285
+ * @returns {Promise<{ key: string, tier: string, limit: number }>}
286
+ */
287
+ async signup(email) {
288
+ if (!email || typeof email !== 'string') {
289
+ throw new SnapAPIError('Email is required', 0, null);
290
+ }
291
+ const res = await this._request('POST', '/v1/signup', {
292
+ json: { email },
293
+ needsAuth: false,
294
+ });
295
+ return res.json();
296
+ }
297
+ }
298
+
299
+ module.exports = SnapAPI;
300
+ module.exports.SnapAPIError = SnapAPIError;
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "snapapi-sdk",
3
+ "version": "1.3.0",
4
+ "description": "Official Node.js SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "screenshot",
10
+ "api",
11
+ "webpage-capture",
12
+ "snapapi"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ }
17
+ }