quadqr-js 1.0.2 → 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.
@@ -28,20 +28,30 @@ If no version is supplied, QuadQR automatically chooses the smallest version tha
28
28
  ```js
29
29
  import {
30
30
  encodeText,
31
- renderToCanvas
31
+ renderToCanvas,
32
+ renderToSVG
32
33
  } from "quadqr-js/browser";
33
34
 
34
35
  const code = encodeText("Rendered in the browser");
35
36
 
36
37
  renderToCanvas(code, document.querySelector("#qr"), {
37
- moduleSize: 12,
38
+ imageSize: 720,
38
39
  quietZone: 4,
39
40
  style: "classic"
40
41
  });
42
+
43
+ const svg = renderToSVG(code, {
44
+ imageSize: 720,
45
+ quietZone: 4
46
+ });
41
47
  ```
42
48
 
43
49
  Available presentation styles are `classic`, `depth`, `soft`, and `inset`. Styling does not change the encoded matrix.
44
50
 
51
+ `imageSize` is the exact square output dimension in pixels. If you omit both `imageSize` and `moduleSize`, the renderer defaults to 720 × 720 px. `moduleSize` is still available for low-level pixels-per-module sizing.
52
+
53
+ To add a centered logo, load it as an `Image` and pass it as `logo.source`. Transparent logo pixels remain transparent. Use `clearBackground: true` when you want a padded white area behind the logo. `quietZone` is measured in modules and defaults to `4`.
54
+
45
55
  ## Scan an uploaded image
46
56
 
47
57
  ```js
@@ -125,16 +135,21 @@ const code = await encodeSecureText("Application-managed secret", {
125
135
 
126
136
  The key must be exactly 32 bytes. Never embed the secret key inside the same QuadQR payload.
127
137
 
128
- ## Generate and scan PNG files in Node.js
138
+ ## Generate PNG and SVG files in Node.js
129
139
 
130
140
  ```js
131
141
  import { encodeText } from "quadqr-js";
132
- import { savePNG, scanFile } from "quadqr-js/node";
142
+ import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
133
143
 
134
144
  const code = encodeText("Generated on Node.js");
135
145
 
136
146
  await savePNG(code, "quadqr.png", {
137
- moduleSize: 12,
147
+ imageSize: 720,
148
+ quietZone: 4
149
+ });
150
+
151
+ await saveSVG(code, "quadqr.svg", {
152
+ imageSize: 720,
138
153
  quietZone: 4
139
154
  });
140
155
 
@@ -142,13 +157,13 @@ const result = await scanFile("quadqr.png");
142
157
  console.log(result.text);
143
158
  ```
144
159
 
145
- PNG generation and decoding are built into the Node.js adapter.
160
+ PNG generation/decoding and SVG generation are built into the Node.js adapter.
146
161
 
147
162
  ## Use a script tag
148
163
 
149
164
  ```html
150
165
  <canvas id="qr"></canvas>
151
- <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0.1/dist/quadqr.min.js"></script>
166
+ <script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
152
167
  <script>
153
168
  const code = QuadQR.encodeText("No build step");
154
169
  QuadQR.renderToCanvas(code, document.querySelector("#qr"));
@@ -172,3 +187,61 @@ npx quadqr-js decode secure.png --password "my-password"
172
187
  ```
173
188
 
174
189
  See [CLI.md](./CLI.md) for the full command reference.
190
+
191
+ ## Compression and signed payloads
192
+
193
+ Compression is available directly on the normal encode APIs. You do not need to select a payload type or use a separate payload mode:
194
+
195
+ ```js
196
+ import { encodeText } from "quadqr-js";
197
+
198
+ const code = encodeText("repeat repeat repeat repeat", {
199
+ compression: "auto",
200
+ ecc: "Q"
201
+ });
202
+ ```
203
+
204
+ `auto` keeps the original payload untouched when compression would not save space.
205
+
206
+ For offline integrity verification:
207
+
208
+ ```js
209
+ import { generateSigningKeyPair, encodeSignedText, decodeMatrix, verifyDecodedSignature } from "quadqr-js";
210
+
211
+ const keys = await generateSigningKeyPair();
212
+ const signed = await encodeSignedText("ticket data", {
213
+ compression: "auto",
214
+ privateKey: keys.privateKey,
215
+ keyId: keys.keyId
216
+ });
217
+
218
+ const result = await verifyDecodedSignature(decodeMatrix(signed.matrix), {
219
+ publicKey: keys.publicKey
220
+ });
221
+ console.log(result.signatureVerified, result.signatureTrusted);
222
+ ```
223
+
224
+ Signing metadata is internal. The private key signs; the trusted public key verifies and remains outside the QuadQR by default. The optional `keyId` lets applications select the correct trusted public key without embedding it in every symbol.
225
+
226
+ ## Print mode
227
+
228
+ ```js
229
+ renderToCanvas(code, canvas, {
230
+ imageSize: 1200,
231
+ mode: "print",
232
+ quietZone: 4
233
+ });
234
+ ```
235
+
236
+ Print mode uses safer defaults but should still be tested with the actual printer, paper, physical size, and target phones.
237
+
238
+ ## Scanability testing
239
+
240
+ ```js
241
+ import { assessScanability } from "quadqr-js";
242
+
243
+ const report = assessScanability(code, { imageSize: 480 });
244
+ console.log(report.score, report.rating);
245
+ ```
246
+
247
+ The browser demo includes an interactive stress lab for testing one distortion at a time or running the full suite.
@@ -0,0 +1,100 @@
1
+ # High Density Mode (Experimental)
2
+
3
+ High Density Mode is an experimental QuadQR option implemented with the Triangle16 cell layout. It keeps the existing QuadQR geometry, finder patterns, timing pattern, alignment patterns, calibration, Spectrum ECC, CRC, security envelope, compression, and signing behavior, but changes how payload data cells are represented.
4
+
5
+ ## Physical cell
6
+
7
+ A Triangle16 payload cell is split by one fixed `/` diagonal:
8
+
9
+ ```text
10
+ +---------+
11
+ | AAAAAA /|
12
+ | AAAAA /B|
13
+ | AAAA /BB|
14
+ | AAA /BBB|
15
+ | AA /BBBB|
16
+ | A /BBBBB|
17
+ | /BBBBBBB|
18
+ +---------+
19
+ ```
20
+
21
+ `A` is the upper-left triangle and `B` is the lower-right triangle. Each triangle independently uses the existing RGBW alphabet:
22
+
23
+ ```text
24
+ R = 00
25
+ G = 01
26
+ B = 10
27
+ W = 11
28
+ ```
29
+
30
+ The pair therefore has 16 states:
31
+
32
+ ```text
33
+ R/R R/G R/B R/W
34
+ G/R G/G G/B G/W
35
+ B/R B/G B/B B/W
36
+ W/R W/G W/B W/W
37
+ ```
38
+
39
+ The first triangle carries the high 2 bits and the second triangle carries the low 2 bits. One Triangle16 body cell therefore carries 4 raw bits, so one byte occupies two body cells instead of four RGBW cells.
40
+
41
+ ## Protected header stays solid
42
+
43
+ The protected bootstrap/header intentionally does not use mixed-color triangles. Its normal RGBW header cells are represented as same-color pairs such as `R/R`, `G/G`, `B/B`, and `W/W`.
44
+
45
+ This uses four cells per protected header byte, exactly like RGBW, but makes it much easier for the decoder to recover the mode flag when the image is blurred, skewed, resized, or color-shifted. Header flag bit 6 declares Triangle16 for the ECC-protected body.
46
+
47
+ ## Scanner sampling
48
+
49
+ The scanner does not sample the diagonal or the exact module center. For each data cell it samples two small regions well inside the triangles, approximately around `(0.28, 0.28)` and `(0.72, 0.72)` in normalized module coordinates.
50
+
51
+ Each triangle is classified independently against the calibrated RGBW palette. The cell confidence is the weaker of the two triangle confidences. That confidence feeds the existing confidence-aware Spectrum ECC path, allowing an ambiguous triangle pair to become a useful erasure candidate instead of an arbitrary hard error.
52
+
53
+ The normal image and camera scanner automatically detects High Density Mode and attempts Triangle16 dual-region sampling when needed. No separate scan mode is required. If ordinary finder/alignment geometry is good enough to locate the symbol but not precise enough to decode the half-cell regions, the scanner performs one bounded **precise-alignment recovery** pass. That pass uses denser alignment-pattern probes and a finer sub-module search, then retries the same dual-triangle classifier.
54
+
55
+ ## Rendering rules
56
+
57
+ High Density Mode uses exact hard-edged Triangle16 payload cells. Decorative payload styles are intentionally bypassed in this experimental mode because rounded, inset, soft, or depth effects can reduce the usable sampling area or contaminate the diagonal boundary. Structural modules keep the normal QuadQR rendering behavior.
58
+
59
+ PNG/ImageData, Canvas, SVG, browser, and Node rendering all support Triangle16.
60
+
61
+ ## API
62
+
63
+ ```js
64
+ import { encodeText } from "quadqr-js";
65
+
66
+ const code = encodeText("High-density QuadQR", {
67
+ ecc: "M",
68
+ highDensity: true
69
+ });
70
+ ```
71
+
72
+ High Density Mode is disabled by default. Existing callers remain on normal RGBW mode unless `highDensity: true` is explicitly supplied.
73
+
74
+ ## Capacity
75
+
76
+ Raw body density is:
77
+
78
+ ```text
79
+ RGBW 4 states 2 bits/body cell
80
+ Triangle16 16 states 4 bits/body cell
81
+ ```
82
+
83
+ The protected header remains RGBW-equivalent, and ECC/CRC overhead is unchanged at the byte level, so usable payload capacity is close to but not exactly 2x for a fixed matrix version.
84
+
85
+ Use `getVersionInfo(version, { ecc, highDensity: true })` or the demo capacity calculator for the exact value.
86
+
87
+ ## Reliability caveat
88
+
89
+ Triangle16 doubles raw body bits per cell, but it also halves the spatial area available to each independently classified color. Its meaningful real-world metric is not only bits per matrix cell. It is reliable payload bytes at a fixed physical size, camera distance, angle, lighting condition, resize/compression pipeline, and print quality.
90
+
91
+ The branch should therefore be stress-tested before treating High Density Mode as stable. Important cases include:
92
+
93
+ - perspective and partial finder degradation;
94
+ - defocus and motion blur;
95
+ - low camera pixel coverage per module;
96
+ - JPEG compression and repeated image resizing;
97
+ - shadows, glare, white-balance shifts, and saturation changes;
98
+ - cheap printing, ink spread, paper tint, and camera recapture.
99
+
100
+ If full 16-state reliability becomes the limiting factor, a restricted triangle alphabet can be explored later without changing the basic physical-cell experiment.
package/docs/NODE.md CHANGED
@@ -1,123 +1,137 @@
1
- # Node.js
2
-
3
- QuadQR uses the same matrix codec and scanner core in Node.js and the browser. The `quadqr-js/node` entry adds Node-specific PNG, file, and buffer helpers.
4
-
5
- ## Requirements
6
-
7
- Node.js 20.19 or newer.
8
-
9
- ## ESM
10
-
11
- ```js
12
- import { encodeText } from "quadqr-js";
13
- import { savePNG, scanFile } from "quadqr-js/node";
14
- ```
15
-
16
- ## CommonJS
17
-
18
- ```js
19
- const QuadQR = require("quadqr-js");
20
- const QuadQRNode = require("quadqr-js/node");
21
- ```
22
-
23
- ## Generate a PNG
24
-
25
- ```js
26
- const code = QuadQR.encodeText("Server generated");
27
-
28
- await QuadQRNode.savePNG(code, "output.png", {
29
- moduleSize: 12,
30
- quietZone: 4
31
- });
32
- ```
33
-
34
- Use `toPNG()` when you need an in-memory `Buffer`:
35
-
36
- ```js
37
- const png = QuadQRNode.toPNG(code);
38
- ```
39
-
40
- This works well for HTTP responses, object storage, attachments, and other buffer-based workflows.
41
-
42
- ## Scan a PNG file
43
-
44
- ```js
45
- const result = await QuadQRNode.scanFile("output.png");
46
- console.log(result.text);
47
- ```
48
-
49
- Or scan a buffer:
50
-
51
- ```js
52
- const result = await QuadQRNode.scanBuffer(pngBuffer);
53
- ```
54
-
55
- PNG generation and decoding do not require external native dependencies.
56
-
57
- ## Other image formats
58
-
59
- For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
60
-
61
- ```bash
62
- npm install sharp
63
- ```
64
-
65
- If `sharp` is not installed, decode the image to RGBA in your own image pipeline and pass the pixels to `scanImageData()`.
66
-
67
- ## Secure password workflow
68
-
69
- ```js
70
- import {
71
- decryptDecoded,
72
- encodeSecureText
73
- } from "quadqr-js";
74
- import {
75
- savePNG,
76
- scanFile
77
- } from "quadqr-js/node";
78
-
79
- const code = await encodeSecureText("private", {
80
- security: {
81
- mode: "password",
82
- password: process.env.QUADQR_PASSWORD
83
- }
84
- });
85
-
86
- await savePNG(code, "secure.png");
87
-
88
- const locked = await scanFile("secure.png");
89
-
90
- const unlocked = await decryptDecoded(locked, {
91
- password: process.env.QUADQR_PASSWORD
92
- });
93
-
94
- console.log(unlocked.text);
95
- ```
96
-
97
- ## Secure raw-key workflow
98
-
99
- ```js
100
- import {
101
- decryptDecoded,
102
- encodeSecureText,
103
- generateRaw256Key
104
- } from "quadqr-js";
105
- import { toPNG, scanBuffer } from "quadqr-js/node";
106
-
107
- const key = generateRaw256Key();
108
-
109
- const code = await encodeSecureText("machine secret", {
110
- security: {
111
- mode: "raw-key",
112
- key
113
- }
114
- });
115
-
116
- const png = toPNG(code);
117
- const locked = await scanBuffer(png);
118
- const unlocked = await decryptDecoded(locked, { key });
119
-
120
- console.log(unlocked.text);
121
- ```
122
-
123
- Keep long-lived application keys in an appropriate secret-management or platform key-storage system.
1
+ # Node.js
2
+
3
+ QuadQR uses the same matrix codec and scanner core in Node.js and the browser. The `quadqr-js/node` entry adds Node-specific PNG, file, and buffer helpers.
4
+
5
+ ## Requirements
6
+
7
+ Node.js 20.19 or newer.
8
+
9
+ ## ESM
10
+
11
+ ```js
12
+ import { encodeText } from "quadqr-js";
13
+ import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
14
+ ```
15
+
16
+ ## CommonJS
17
+
18
+ ```js
19
+ const QuadQR = require("quadqr-js");
20
+ const QuadQRNode = require("quadqr-js/node");
21
+ ```
22
+
23
+ ## Generate a PNG
24
+
25
+ ```js
26
+ const code = QuadQR.encodeText("Server generated");
27
+
28
+ await QuadQRNode.savePNG(code, "output.png", {
29
+ imageSize: 720,
30
+ quietZone: 4
31
+ });
32
+ ```
33
+
34
+ ## Generate an SVG
35
+
36
+ ```js
37
+ await QuadQRNode.saveSVG(code, "output.svg", {
38
+ imageSize: 720,
39
+ quietZone: 4,
40
+ style: "classic"
41
+ });
42
+ ```
43
+
44
+ `imageSize` is the exact PNG/SVG output dimension in pixels. If you omit both `imageSize` and `moduleSize`, rendering defaults to 720 × 720 px. `moduleSize` remains available for legacy pixels-per-module sizing.
45
+
46
+ Use `toSVG()` when you need the SVG string in memory. SVG logo sources can be URL/data URL strings. For PNG generation with a logo through `renderToImageData()`, pass decoded ImageData-like RGBA pixels as the logo source.
47
+
48
+ Use `toPNG()` when you need an in-memory `Buffer`:
49
+
50
+ ```js
51
+ const png = QuadQRNode.toPNG(code);
52
+ ```
53
+
54
+ This works well for HTTP responses, object storage, attachments, and other buffer-based workflows.
55
+
56
+ ## Scan a PNG file
57
+
58
+ ```js
59
+ const result = await QuadQRNode.scanFile("output.png");
60
+ console.log(result.text);
61
+ ```
62
+
63
+ Or scan a buffer:
64
+
65
+ ```js
66
+ const result = await QuadQRNode.scanBuffer(pngBuffer);
67
+ ```
68
+
69
+ PNG generation and decoding do not require external native dependencies.
70
+
71
+ ## Other image formats
72
+
73
+ For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
74
+
75
+ ```bash
76
+ npm install sharp
77
+ ```
78
+
79
+ If `sharp` is not installed, decode the image to RGBA in your own image pipeline and pass the pixels to `scanImageData()`.
80
+
81
+ ## Secure password workflow
82
+
83
+ ```js
84
+ import {
85
+ decryptDecoded,
86
+ encodeSecureText
87
+ } from "quadqr-js";
88
+ import {
89
+ savePNG,
90
+ scanFile
91
+ } from "quadqr-js/node";
92
+
93
+ const code = await encodeSecureText("private", {
94
+ security: {
95
+ mode: "password",
96
+ password: process.env.QUADQR_PASSWORD
97
+ }
98
+ });
99
+
100
+ await savePNG(code, "secure.png");
101
+
102
+ const locked = await scanFile("secure.png");
103
+
104
+ const unlocked = await decryptDecoded(locked, {
105
+ password: process.env.QUADQR_PASSWORD
106
+ });
107
+
108
+ console.log(unlocked.text);
109
+ ```
110
+
111
+ ## Secure raw-key workflow
112
+
113
+ ```js
114
+ import {
115
+ decryptDecoded,
116
+ encodeSecureText,
117
+ generateRaw256Key
118
+ } from "quadqr-js";
119
+ import { toPNG, scanBuffer } from "quadqr-js/node";
120
+
121
+ const key = generateRaw256Key();
122
+
123
+ const code = await encodeSecureText("machine secret", {
124
+ security: {
125
+ mode: "raw-key",
126
+ key
127
+ }
128
+ });
129
+
130
+ const png = toPNG(code);
131
+ const locked = await scanBuffer(png);
132
+ const unlocked = await decryptDecoded(locked, { key });
133
+
134
+ console.log(unlocked.text);
135
+ ```
136
+
137
+ Keep long-lived application keys in an appropriate secret-management or platform key-storage system.
package/docs/README.md CHANGED
@@ -1,41 +1,60 @@
1
- # QuadQR Documentation
2
-
3
- QuadQR is an experimental four-state RGBW matrix symbology. Each data cell carries exactly two bits using red, green, blue, or white. The JavaScript library supports encoding, rendering, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG workflows, CDN usage, a CLI, TypeScript declarations, and optional prebuilt WebAssembly acceleration.
4
-
5
- ## Live links
6
-
7
- - [Documentation Site](https://akanshsirohi.github.io/QuadQR/docs-site/)
8
- - [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
9
- - [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
10
- - [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
11
-
12
- ## Documentation
13
-
14
- - [Getting Started](./GETTING_STARTED.md)
15
- - [API Reference](./API.md)
16
- - [Browser and CDN](./BROWSER_CDN.md)
17
- - [Node.js](./NODE.md)
18
- - [CLI](./CLI.md)
19
- - [Secure Payloads](./SECURITY.md)
20
- - [WebAssembly](./WASM.md)
21
- - [Wire Format](../FORMAT.md)
22
-
23
- ## Package entry points
24
-
25
- | Entry | Purpose |
26
- | --- | --- |
27
- | `quadqr-js` | Runtime-neutral core API, rendering, scanning, secure payloads, utilities, and optional WASM |
28
- | `quadqr-js/browser` | Browser ESM entry for canvas, files, video, and camera workflows |
29
- | `quadqr-js/node` | Node.js core plus PNG, file, and buffer helpers |
30
- | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
31
- | `quadqr-js/quadqr.min.js` | Classic browser global bundle for CDN/script-tag usage |
32
-
33
- The matrix codec is shared across runtimes. Browser and Node.js adapters only handle environment-specific input and output.
34
-
35
- ## Compatibility note
36
-
37
- QuadQR is a custom experimental format, not ISO QR Code. Standard QR scanner applications cannot decode QuadQR symbols.
38
-
39
- ## License
40
-
41
- QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
1
+ # QuadQR Documentation
2
+
3
+ QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, TypeScript declarations, and optional prebuilt WebAssembly acceleration.
4
+
5
+ ## Live links
6
+
7
+ - [Documentation Site](https://akanshsirohi.github.io/QuadQR/docs-site/)
8
+ - [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
9
+ - [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
10
+ - [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
11
+
12
+ ## Documentation
13
+
14
+ - [Getting Started](./GETTING_STARTED.md)
15
+ - [API Reference](./API.md)
16
+ - [High Density Mode (Experimental)](./HIGH_DENSITY_MODE.md)
17
+ - [Browser and CDN](./BROWSER_CDN.md)
18
+ - [Node.js](./NODE.md)
19
+ - [CLI](./CLI.md)
20
+ - [Secure Payloads](./SECURITY.md)
21
+ - [WebAssembly](./WASM.md)
22
+ - [Wire Format](../FORMAT.md)
23
+ - [Technical Specification](../SPECIFICATION.md)
24
+
25
+ ## Package entry points
26
+
27
+ | Entry | Purpose |
28
+ | --- | --- |
29
+ | `quadqr-js` | Runtime-neutral core API, rendering, scanning, secure payloads, utilities, and optional WASM |
30
+ | `quadqr-js/browser` | Browser ESM entry for canvas, files, video, and camera workflows |
31
+ | `quadqr-js/node` | Node.js core plus PNG/SVG, file, and buffer helpers |
32
+ | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
33
+ | `quadqr-js/quadqr.min.js` | Classic browser global bundle for CDN/script-tag usage |
34
+
35
+ The matrix codec is shared across runtimes. Browser and Node.js adapters only handle environment-specific input and output.
36
+
37
+ ## Compatibility note
38
+
39
+ QuadQR is a custom experimental format, not ISO QR Code. Standard QR scanner applications cannot decode QuadQR symbols.
40
+
41
+ ## License
42
+
43
+ QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
44
+
45
+ ## Advanced reliability and payload features
46
+
47
+ The current library also includes:
48
+
49
+ - normal text/byte payloads with optional internal LZ compression;
50
+ - portable automatic LZ compression;
51
+ - first-class binary `Uint8Array` APIs;
52
+ - Ed25519 signed QuadQR payloads and trusted-key verification;
53
+ - signed + AES-256-GCM encrypted composition;
54
+ - screen and print rendering modes;
55
+ - ECC-aware automatic logo sizing;
56
+ - normalized scanner confidence and detailed debug mode;
57
+ - deterministic scanability/torture testing;
58
+ - an interactive browser stress-test lab and capacity calculator.
59
+
60
+ See [`../SPECIFICATION.md`](../SPECIFICATION.md) for the layering and interoperability rules and [`API.md`](./API.md) for the public APIs.