snapapi-client 1.0.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 (3) hide show
  1. package/README.md +73 -0
  2. package/index.js +159 -0
  3. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # snapapi
2
+
3
+ Official JavaScript SDK for the [SnapAPI](https://snapapi.tech) web intelligence API.
4
+
5
+ ```bash
6
+ npm install snapapi
7
+ ```
8
+
9
+ Zero dependencies. Node.js 18+ required.
10
+
11
+ ## Quick Start
12
+
13
+ ```js
14
+ const SnapAPI = require('snapapi');
15
+ const client = new SnapAPI(); // reads SNAPAPI_KEY env var
16
+
17
+ // Screenshot
18
+ const png = await client.screenshot('https://github.com');
19
+ require('fs').writeFileSync('screenshot.png', png);
20
+
21
+ // Metadata
22
+ const meta = await client.metadata('https://github.com');
23
+ console.log(meta.og_title, meta.og_image);
24
+
25
+ // Page analysis
26
+ const page = await client.analyze('https://stripe.com');
27
+ console.log(page.page_type, page.primary_cta, page.technologies);
28
+
29
+ // URL → PDF
30
+ const pdf = await client.pdf('https://github.com', { format: 'A4' });
31
+ require('fs').writeFileSync('page.pdf', pdf);
32
+
33
+ // HTML → image (OG cards, email previews)
34
+ const img = await client.render('<h1 style="padding:60px;font-size:48px">Hello</h1>', { width: 1200, height: 630 });
35
+ require('fs').writeFileSync('card.png', img);
36
+
37
+ // Batch — multiple URLs in parallel
38
+ const results = await client.batch(['https://stripe.com', 'https://vercel.com'], 'metadata');
39
+ results.forEach(r => console.log(r.url, r.og_title));
40
+ ```
41
+
42
+ ## Get a Free API Key
43
+
44
+ 100 calls/month · No credit card · Active in 30 seconds
45
+
46
+ → **[snapapi.tech/start](https://snapapi.tech/start)**
47
+
48
+ ## API
49
+
50
+ ### `new SnapAPI(apiKey?, options?)`
51
+
52
+ | Param | Type | Default |
53
+ |---|---|---|
54
+ | `apiKey` | `string` | `process.env.SNAPAPI_KEY` |
55
+ | `options.baseUrl` | `string` | `https://api.snapapi.tech` |
56
+ | `options.timeout` | `number` | `45000` |
57
+
58
+ ### Methods
59
+
60
+ | Method | Returns | Description |
61
+ |---|---|---|
62
+ | `screenshot(url, opts?)` | `Buffer` | PNG/JPEG/WebP image |
63
+ | `metadata(url)` | `object` | OG tags, title, favicon, canonical |
64
+ | `analyze(url, opts?)` | `object` | CTAs, nav, tech stack, word count |
65
+ | `pdf(url, opts?)` | `Buffer` | PDF binary |
66
+ | `render(html, opts?)` | `Buffer` | HTML → image |
67
+ | `batch(urls, endpoint?, params?)` | `Array` | Parallel multi-URL processing |
68
+
69
+ Full parameter reference: [snapapi.tech/docs](https://snapapi.tech/docs)
70
+
71
+ ## License
72
+
73
+ MIT
package/index.js ADDED
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+ /**
3
+ * snapapi — Official JavaScript SDK for the SnapAPI web intelligence API
4
+ *
5
+ * npm install snapapi
6
+ * Docs: https://snapapi.tech/docs
7
+ * Free key: https://snapapi.tech/start
8
+ *
9
+ * Zero runtime dependencies. Node.js 18+ required (uses built-in fetch).
10
+ */
11
+
12
+ const BASE_URL = 'https://api.snapapi.tech';
13
+
14
+ class SnapAPIError extends Error {
15
+ constructor(status, message) {
16
+ super(`SnapAPI ${status}: ${message}`);
17
+ this.name = 'SnapAPIError';
18
+ this.status = status;
19
+ }
20
+ }
21
+
22
+ class SnapAPI {
23
+ /**
24
+ * @param {string} [apiKey] - API key. Falls back to SNAPAPI_KEY env var.
25
+ * @param {object} [options]
26
+ * @param {string} [options.baseUrl] - Override API base URL.
27
+ * @param {number} [options.timeout] - Request timeout in ms (default 45000).
28
+ */
29
+ constructor(apiKey, options = {}) {
30
+ this._key = apiKey || (typeof process !== 'undefined' && process.env.SNAPAPI_KEY) || '';
31
+ this._base = (options.baseUrl || BASE_URL).replace(/\/$/, '');
32
+ this._timeout = options.timeout || 45_000;
33
+ if (!this._key) {
34
+ throw new SnapAPIError(0, 'No API key. Set SNAPAPI_KEY env var or pass apiKey to constructor. Free key at https://snapapi.tech/start');
35
+ }
36
+ }
37
+
38
+ async _get(path, params = {}) {
39
+ const qs = new URLSearchParams(
40
+ Object.fromEntries(Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)]))
41
+ ).toString();
42
+ const url = `${this._base}${path}${qs ? '?' + qs : ''}`;
43
+ const signal = AbortSignal.timeout(this._timeout);
44
+ const res = await fetch(url, { headers: { 'x-api-key': this._key }, signal });
45
+ if (!res.ok) {
46
+ let msg = `HTTP ${res.status}`;
47
+ try { const j = await res.json(); msg = j.error || msg; } catch {}
48
+ throw new SnapAPIError(res.status, msg);
49
+ }
50
+ return res;
51
+ }
52
+
53
+ async _post(path, body) {
54
+ const signal = AbortSignal.timeout(this._timeout);
55
+ const res = await fetch(`${this._base}${path}`, {
56
+ method: 'POST',
57
+ headers: { 'x-api-key': this._key, 'Content-Type': 'application/json' },
58
+ body: JSON.stringify(body),
59
+ signal,
60
+ });
61
+ if (!res.ok) {
62
+ let msg = `HTTP ${res.status}`;
63
+ try { const j = await res.json(); msg = j.error || msg; } catch {}
64
+ throw new SnapAPIError(res.status, msg);
65
+ }
66
+ return res;
67
+ }
68
+
69
+ /**
70
+ * Capture a screenshot of any URL.
71
+ * @param {string} url
72
+ * @param {object} [opts]
73
+ * @param {'png'|'jpeg'|'webp'} [opts.format='png']
74
+ * @param {number} [opts.width=1280]
75
+ * @param {number} [opts.height=800]
76
+ * @param {boolean} [opts.full_page=false]
77
+ * @param {boolean} [opts.dark_mode=false]
78
+ * @param {string} [opts.device] - e.g. 'iphone14', 'pixel7', 'ipad'
79
+ * @param {string} [opts.selector] - CSS selector to capture only that element
80
+ * @param {number} [opts.delay] - Extra ms to wait before capture
81
+ * @returns {Promise<Buffer>} Raw image bytes
82
+ */
83
+ async screenshot(url, opts = {}) {
84
+ const res = await this._get('/v1/screenshot', { url, ...opts });
85
+ return Buffer.from(await res.arrayBuffer());
86
+ }
87
+
88
+ /**
89
+ * Extract metadata from a URL (OG tags, title, favicon, canonical, etc.)
90
+ * @param {string} url
91
+ * @returns {Promise<object>}
92
+ */
93
+ async metadata(url) {
94
+ const res = await this._get('/v1/metadata', { url });
95
+ return res.json();
96
+ }
97
+
98
+ /**
99
+ * Full page analysis — CTAs, nav, tech stack, word count, load time.
100
+ * @param {string} url
101
+ * @param {object} [opts]
102
+ * @param {boolean} [opts.screenshot=false] - Include base64 screenshot in response
103
+ * @returns {Promise<object>}
104
+ */
105
+ async analyze(url, opts = {}) {
106
+ const res = await this._get('/v1/analyze', { url, ...opts });
107
+ return res.json();
108
+ }
109
+
110
+ /**
111
+ * Convert any URL to a PDF.
112
+ * @param {string} url
113
+ * @param {object} [opts]
114
+ * @param {'A4'|'A3'|'A5'|'Letter'|'Legal'|'Tabloid'} [opts.format='A4']
115
+ * @param {boolean} [opts.landscape=false]
116
+ * @param {number} [opts.margin_top=20]
117
+ * @param {number} [opts.margin_bottom=20]
118
+ * @param {number} [opts.margin_left=20]
119
+ * @param {number} [opts.margin_right=20]
120
+ * @param {boolean} [opts.print_background=true]
121
+ * @param {number} [opts.scale=1]
122
+ * @returns {Promise<Buffer>} Raw PDF bytes
123
+ */
124
+ async pdf(url, opts = {}) {
125
+ const res = await this._get('/v1/pdf', { url, ...opts });
126
+ return Buffer.from(await res.arrayBuffer());
127
+ }
128
+
129
+ /**
130
+ * Render raw HTML to a pixel-perfect image.
131
+ * @param {string} html - Full HTML string
132
+ * @param {object} [opts]
133
+ * @param {number} [opts.width=1200]
134
+ * @param {number} [opts.height=630]
135
+ * @param {'png'|'jpeg'|'webp'} [opts.format='png']
136
+ * @returns {Promise<Buffer>} Raw image bytes
137
+ */
138
+ async render(html, opts = {}) {
139
+ const res = await this._post('/v1/render', { html, width: 1200, height: 630, format: 'png', ...opts });
140
+ return Buffer.from(await res.arrayBuffer());
141
+ }
142
+
143
+ /**
144
+ * Process multiple URLs in parallel.
145
+ * @param {string[]} urls
146
+ * @param {'screenshot'|'metadata'|'analyze'} [endpoint='screenshot']
147
+ * @param {object} [params] - Extra params forwarded to each URL call
148
+ * @returns {Promise<Array>} Array of per-URL results
149
+ */
150
+ async batch(urls, endpoint = 'screenshot', params = {}) {
151
+ const res = await this._post('/v1/batch', { urls, endpoint, params });
152
+ const data = await res.json();
153
+ return data.results ?? [];
154
+ }
155
+ }
156
+
157
+ module.exports = SnapAPI;
158
+ module.exports.SnapAPI = SnapAPI;
159
+ module.exports.SnapAPIError = SnapAPIError;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "snapapi-client",
3
+ "version": "1.0.0",
4
+ "description": "Official JavaScript SDK for the SnapAPI web intelligence API — screenshots, metadata, PDFs, page analysis, HTML rendering, and batch processing",
5
+ "main": "index.js",
6
+ "exports": {
7
+ ".": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node test.js"
11
+ },
12
+ "keywords": [
13
+ "screenshot", "screenshot-api", "web-scraping", "puppeteer-alternative",
14
+ "playwright-alternative", "headless-browser", "metadata", "pdf",
15
+ "page-analysis", "html-to-image", "og-image", "snapapi"
16
+ ],
17
+ "author": "SnapAPI <hello@snapapi.tech>",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/Boehner/snapapi-js"
22
+ },
23
+ "homepage": "https://snapapi.tech",
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "files": [
28
+ "index.js",
29
+ "README.md"
30
+ ]
31
+ }