Skip to content

Read and write from a script

A small TypeScript script that searches for yesterday’s notes, reads them, and writes a digest to the pad. It uses fetch only, so it runs on Node 20 or newer, Deno, Bun or a Worker. The token needs search, read and write on the pad.

digest.ts
const BASE = "https://app.inseam.io";
const TOKEN = process.env.INSEAM_TOKEN!;
const headers = { Authorization: `Bearer ${TOKEN}` };
type Hit = { address: string; title: string; snippet?: string; meta: { mtime: string; mime: string } };
async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
const r = await fetch(BASE + path, { ...init, headers: { ...headers, ...(init.headers ?? {}) } });
if (!r.ok) {
const body = await r.json().catch(() => ({}));
throw new Error(`${init.method ?? "GET"} ${path}: ${r.status} ${JSON.stringify(body)}`);
}
return (r.headers.get("content-type") ?? "").includes("json") ? r.json() : (r.text() as unknown as T);
}
// 1. Find notes changed in the last day.
const since = new Date(Date.now() - 86_400_000).toISOString();
const { hits } = await api<{ hits: Hit[] }>("/v1/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ q: "meeting OR todo OR decision", topK: 50, filter: { kind: "text/markdown" } }),
});
const recent = hits.filter((h) => h.meta.mtime >= since);
// 2. Read each one. Reads over 4 MiB are refused; notes are small.
const sections: string[] = [];
for (const hit of recent) {
try {
const text = await api<string>(`/v1/o/${encodeURIComponent(hit.address)}`);
sections.push(`## ${hit.title}\n\n${hit.address}\n\n${text.slice(0, 2000)}`);
} catch (e) {
// A note on a sleeping Mac answers 503 steward-offline. Say so and move on.
sections.push(`## ${hit.title}\n\n${hit.address}\n\n_${(e as Error).message}_`);
}
}
// 3. Write the digest to the pad. It is searchable when the call returns.
const day = new Date().toISOString().slice(0, 10);
const body = `# Digest for ${day}\n\n${recent.length} notes changed since ${since}.\n\n${sections.join("\n\n")}\n`;
const { entry } = await api<{ entry: { address: string; etag: string } }>(`/v1/o/pad/digests/${day}.md`, {
method: "PUT",
headers: { "Content-Type": "text/markdown" },
body,
});
console.log(`wrote ${entry.address} (${entry.etag})`);
Terminal window
INSEAM_TOKEN=ias_… node digest.ts
  • Addresses in URLs. encodeURIComponent(address) works for a full inseam:// address. The short form pad/digests/2026-10-25.md needs no encoding for ordinary names.
  • Do not overwrite a newer version. Send If-Match: {etag} from the last write; a 412 means someone else wrote in between, and details.etag is the current version.
  • Over the limit. A 402 over-limit on the write means the network is full. The bytes are stored and indexed as soon as there is room; see plans and billing.
  • Scheduling. A cron job on any machine works, with the token in its environment. The token can be limited to the hosts it reads and to the pad/digests/ prefix when you create it.