AmberPen Get started

Incremental proofreading

When a user is editing a document, most of the text stays the same between proofreading runs. Incremental proofreading detects the small parts that changed and only re-proofreads those — the untouched remainder never goes back through a model, so repeat requests are faster and cheaper.

This happens entirely in the SDK, on your side. The API is stateless: it never stores your text, and only ever receives the chunks that need checking.

How to enable it

Pass a stable textId with your proofread request. That is the only change; the response shape is identical to a regular request, with your textId echoed back as textId.

import { applyEdits, createAmberPenClient } from "@amber-pen/sdk";

const amberPen = createAmberPenClient({ apiKey });

// First request: the full document is proofread, and the
// SDK remembers it under the textId.
await amberPen.proofread({
	textId: "chapter-1",
	text: draft,
});

// Later, after the author edits a paragraph: only the
// changed chunks are sent to the API.
const result = await amberPen.proofread({
	textId: "chapter-1",
	text: updatedDraft,
});

// Edits still cover the whole document you sent.
const correctedText = applyEdits(updatedDraft, result.edits);

How it works

  • The first request with a given textId proofreads the full text. The SDK keeps that text and its edits in memory.
  • On each subsequent request, the SDK diffs the new text against the remembered version to locate the blocks that changed. A block is a line, delimited by line breaks.
  • Around every change, a chunk of roughly 800 characters of context on each side is taken by default, rounded to the closest block boundary so a block is never cut in half; chunks that touch are merged.
  • For proofread, every chunk is sent to the API in parallel. For proofreadStream, changed chunks are streamed in document order.
  • Edits from those chunks replace the remembered edits in the same ranges. Edits outside them are kept and shifted to their new offsets.
  • The result is one edits array covering the whole document, with offsets relative to the text you just sent. It is a complete result, not a patch: you can replace your whole suggestion set with it.

If nothing changed since the previous request, no request is sent at all and the previous edits are returned as they are.

Chunk size and boundaries

Set incrementalChunkSize when creating the client to change how many characters of context are included on each side of a change. The value is approximate: after applying it, the SDK rounds both edges to newline-delimited block boundaries. This guarantees that every chunk starts at the beginning of a block—including a paragraph represented as one line—and never partway through it.

const amberPen = createAmberPenClient({
	apiKey,
	// Context on each side of a change before rounding to
	// newline-delimited block boundaries. Defaults to 800.
	incrementalChunkSize: 1_200,
	// How long a textId's text and edits stay in memory.
	// Defaults to 24 hours.
	incrementalTtlMs: 60 * 60 * 1000,
});

Streaming

Pass the same textId to proofreadStream to stream an incremental result. The SDK caches a streamed text and its edits only after every required chunk completes successfully. If the iterator is cancelled, fails, or is not fully consumed, the last completed snapshot remains intact and the partial result is never cached.

Memory and expiry

Texts and edits are held in the client's memory, per textId, for 24 hours by default. Expired entries are dropped, and a textId whose entry is gone is simply treated as new: the next request proofreads the full text again. Set incrementalTtlMs to change the window.

Because the cache lives in one client instance, incremental proofreading works best when the same long-lived instance handles a document's requests. A short-lived process — a serverless function that constructs a client per invocation, for example — starts with an empty cache each time, so every request proofreads the full text.

Choosing a textId

  • Use one textId per document (or per independently edited section, like a chapter) and keep it stable across requests.
  • Always send the complete document text, not just the changed part — the SDK computes the diff for you, and edit offsets stay valid against your full text.

When not to use it

  • One-off texts that are proofread once — a textId adds nothing.