haruto-sdk
v0.1.0
Published
Official TypeScript/JavaScript SDK for the Haruto grounded PDF parsing API.
Maintainers
Readme
haruto-sdk
Official TypeScript/JavaScript SDK for the Haruto grounded PDF parsing API — self-hosted document intelligence for clinical protocols, claims, faxes, and hundred-thousand-page archives.
Every parsed element comes back grounded: page number + bounding box on each heading, paragraph, table cell, and figure, so downstream systems verify values against the source instead of trusting a transcription.
- Zero dependencies — Node 18+, Bun, Deno, edge runtimes, browsers
- Network-resilient by default — exponential-backoff retries on connection errors and 5xx; uploads retry only when the server never responded (never a double-submit)
- Built for huge documents — async jobs, page-range results, webhooks
npm install haruto-sdkQuickstart
import { Haruto, type ParsedDoc } from "haruto-sdk";
const hr = new Haruto("https://your-haruto-deploy.example.com", "wk_...");
// 1. upload — returns immediately with a job
const job = await hr.parse(fileBlob, { mode: "accurate" });
// 2. wait — polls with backoff, absorbs connection blips
await hr.waitFor(job.job_id);
// 3. fetch the grounded result
const doc: ParsedDoc = await hr.result(job.job_id);
for (const el of doc.elements) {
// el.type "heading" | "paragraph" | "table" | "figure" | ...
// el.page 1-based page number
// el.bbox { x0, y0, x1, y1 } in PDF points, top-left origin
// el.text reading-order text
// el.table cell grid with row/col spans, when el.type === "table"
}Small documents can skip the poll entirely:
const done = await hr.parse(fileBlob, { wait: true }); // blocks until parsedParse options
await hr.parse(fileBlob, {
mode: "accurate", // "accurate" (layout + table models, default) | "fast" (text-layer only, ~100x faster)
ocr: "auto", // "auto" (OCR only pages that need it, default) | "off" | "force"
webhookUrl: "https://your.app/hooks/haruto", // POSTed on completion
filename: "protocol.pdf",
wait: false, // true = block until done (small docs)
});Results
const doc = await hr.result(jobId); // full grounded JSON
const md = await hr.result(jobId, "markdown"); // tables reconstructed
const txt = await hr.result(jobId, "text"); // reading-order text
// large documents: fetch a page range instead of the whole result
const slice = await hr.result(jobId, "json", "1200-1225");Results are retained for a configurable window after completion (24 h by default on a standard deployment), then deleted — parsed content is a delivery, not an archive. A purged result returns 410.
Jobs
await hr.job(jobId); // status, page counts, progress
await hr.jobs(25, 0); // list your jobs
await hr.cancel(jobId); // stop a running job
await hr.requeue(jobId); // resume an interrupted job (crash recovery)waitFor(jobId, pollMs, timeoutMs) polls until the job reaches a terminal state and throws HarutoError if the job failed.
Webhooks
Completion webhooks are signed. Verify with a constant-time compare and a five-minute replay window:
import { verifyWebhook } from "haruto-sdk";
const ok = await verifyWebhook(
webhookSecret,
rawBody, // exact bytes received
req.headers["x-haruto-timestamp"],
req.headers["x-haruto-signature"],
);Errors
Every non-2xx response throws HarutoError with .status and the server's message:
| Status | Meaning |
|---|---|
| 401 | bad or missing API key |
| 402 | page quota exhausted |
| 409 | result requested before the job succeeded |
| 410 | result deleted by the retention window — re-submit |
| 413 | upload exceeds the deployment's size limit |
| 429 | rate limited |
| 0 | network unreachable after all retries |
Self-hosting
Haruto runs entirely on your infrastructure — no LLMs, no external calls; documents never leave your network. Accuracy is measured, never estimated: the engine ships with a reproducible benchmark harness covering 17 parsers (including Reducto, Pulse, and LlamaParse) on exact machine-generated ground truth.
MIT © Harsh Sharma
