Skip to content

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.

  1. Open the Terminal app. On a Mac, press Cmd and Space, type Terminal, and press Enter. On Windows, open PowerShell.
  2. Check that Node is installed. Type node -v and press Enter. A number like v20.11.0 means yes; 18 or higher is fine. An error means you need to install Node from nodejs.org first.
  3. Get your capture token. In Lazy Reader open Settings, then Capture. Under Capture token for scripts, press the copy button next to Capture token.
  4. 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.
  5. Make a folder with two files in it. Save the code below in a file called send.mjs. Put your links in a file called links.txt, one link per line.
  6. 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.

js
// 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 withWhat it means
200The batch went through. The numbers say how many were new, already in your library, broken, or left for later.
400The call was refused, for example "Too many items: 73. Send at most 50 items per call."
401The 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 ​

LimitValue
Links per call50. A call with more is refused.
Text per call4,000,000 characters of text and summaries together.
New items per day500, 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.
PauseAbout 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.

sh
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.