Import links with a script
If you have a long list of links, for example an export from another app, you can send them all to Lazy Reader at once. Every link is saved like any other save and gets its own summary. You can do it with a small script on your computer, as shown here, or ask Claude to do it with the import_items tool (see Claude tools).
No import screen exists in the app. This page is the whole method.
Before you start
You need:
- A computer with the Terminal app (Mac) or PowerShell (Windows).
- Node, version 18 or newer. The steps below check it.
- Your capture token from Lazy Reader.
- An OpenRouter key saved in Lazy Reader. Every link without a summary is summarized with your key, so a long list costs more than a few saves.
Step by step
These steps assume you have never used a terminal before.
- Open the Terminal app. On a Mac, press Cmd and Space, type Terminal, and press Enter. On Windows, open PowerShell.
- Check that Node is installed. Type
node -vand press Enter. A number likev20.11.0means yes; 18 or higher is fine. An error means you need to install Node from nodejs.org first. - Get your capture token. In Lazy Reader open Settings, then Capture. Under Capture token for scripts, press the copy button next to Capture token.
- Give the token to the terminal. Type
export LAZYREADER_TOKEN=and paste the token straight after it, with no spaces, then press Enter. In PowerShell, type$env:LAZYREADER_TOKEN="..."with your token between the quotes. This lasts until you close the window. - Make a folder with two files in it. Save the code below in a file called
send.mjs. Put your links in a file calledlinks.txt, one link per line. - In the terminal, go to that folder and run
node send.mjs links.txt.
The script sends 50 links at a time and waits one minute between batches. For each batch it prints a line such as 200 48 created, 2 duplicates, 0 errors, 0 skipped.
// node send.mjs links.txt
import { readFileSync } from 'node:fs';
const [file] = process.argv.slice(2);
const base = process.env.LAZYREADER_URL || 'https://lazyreader.app';
const links = readFileSync(file, 'utf8').split('\n').map((l) => l.trim()).filter(Boolean);
for (let i = 0; i < links.length; i += 50) {
const res = await fetch(`${base}/api/customImport`, {
method: 'POST',
headers: { 'x-reader-token': process.env.LAZYREADER_TOKEN, 'content-type': 'application/json' },
body: JSON.stringify({ items: links.slice(i, i + 50).map((url) => ({ url })) })
});
const out = await res.json();
const why = out.results?.find((r) => r.status === 'skipped')?.error;
console.log(res.status, out.error ?? `${out.created} created, ${out.duplicates} duplicates, ${out.errors} errors, ${out.skipped} skipped${why ? ` (${why})` : ''}`);
if (!res.ok || out.skipped) break;
if (i + 50 < links.length) await new Promise((r) => setTimeout(r, Number(process.env.PAUSE_MS ?? 60000)));
}Reading the answer
| The line starts with | What it means |
|---|---|
200 | The batch went through. The numbers say how many were new, already in your library, broken, or left for later. |
400 | The call was refused, for example "Too many items: 73. Send at most 50 items per call." |
401 | The token is wrong or missing. Copy it again from Settings, and check that step 4 was done in this same terminal window. |
A link that is not a valid http or https address says "Not a valid link". One longer than 2,048 characters says "Link is too long". One bad link never stops the rest of the batch.
Limits
| Limit | Value |
|---|---|
| Links per call | 50. A call with more is refused. |
| Text per call | 4,000,000 characters of text and summaries together. |
| New items per day | 500, from scripts and Claude together, over a rolling 24 hours. Links past the limit are not saved and the script stops with "Daily limit of 500 new items reached. Send the rest tomorrow." Duplicates do not count. |
| Pause | About a minute between calls of 50, so your model does not hit its own rate limit. |
Sending the same links again adds nothing. A link already in your library, saved any other way, is left as it is.
For developers
The script calls POST https://lazyreader.app/api/customImport with the capture token in the x-reader-token header. The body is { "items": [ { "url": "..." } ] }. Each item can also carry title, text (the full text, if you have it) and summary. An item with a summary is ready at once and no model runs for it.
curl -X POST https://lazyreader.app/api/customImport \
-H "x-reader-token: $LAZYREADER_TOKEN" \
-H "content-type: application/json" \
-d '{"items":[{"url":"https://example.com/a"}]}'WARNING
Your capture token lets anyone save to your library. Do not share it. If it leaks, press Regenerate token in Settings, Capture. The old token stops working at once, and so does the Chrome clipper until you connect it again.
Items you import read "Sent by your own automation" on their page.
