TypeScript SDK
The same API surface as the Python SDK, in TypeScript. ESM, no runtime dependencies, typed inputs and typed errors.
Install
Not on npm yet — install from the repository
npm install @lithora/sdk that works today. Build it from the repository as below. The package name and the import path are already @lithora/sdk, so nothing in the code samples on this page changes when it is published.git clone https://github.com/AADI0009/SaaS-App.git
cd SaaS-App/sdk/typescript && npm install && npm run build
# then, from your own project:
npm install /absolute/path/to/SaaS-App/sdk/typescriptRequires Node 18 or newer — the client uses the platform fetch and AbortSignal, so it has no runtime dependencies at all. It is published as ESM.
Quickstart
Mint a personal access token at Settings → API Tokens, export it as LITHORA_TOKEN, and go.
import { Lithora } from "@lithora/sdk";
const client = new Lithora({ token: process.env.LITHORA_TOKEN });
const me = await client.auth.me();
console.log(me.email);
const tasks = await client.tasks.list("8f1c0c2e-4a2d-4f5b-9c31-6b2a7e0d4411");
console.log(`${tasks.length} tasks`);Constructing the client
const client = new Lithora({
baseUrl: "https://api.lithora.app", // default; http://localhost:8000 for local dev
token: process.env.LITHORA_TOKEN, // a lth_pat_… token, or a login JWT
timeout: 30_000, // milliseconds, per request
});Pass the bare host — the client appends the /api prefix itself. Note that timeouthere is in MILLISECONDS, where the Python SDK’s is in seconds; each follows the convention of its own ecosystem.
Filtering happens on the server
A list call returns a page, not the whole set. Filtering the array it gives you back therefore answers a different question — the matches within one page — and says nothing about what was left out. Pass the filters instead; every one below is a parameter the endpoint declares.
// Filters are applied by the SERVER, not after the fact.
const done = await client.tasks.list(projectId, { status: "done", limit: 20 });
const children = await client.workItems.list({
types: ["task", "note"], // array is joined for you
parentId: "wi_1c2f…",
limit: 50,
});Driving the agent
The agent proposes; you dispose. A chat response with requires_confirmation means nothing has been written — the plan is staged and waiting.
const session = await client.ai.createSession("Triage");
const reply = await client.ai.chat(session.session_id, "Close the stale bugs in Billing");
if (reply.requires_confirmation) {
// NOTHING has been written yet. reply.action_plan describes what would be.
await client.ai.confirmAgentAction(session.session_id, reply.action_plan.action_id, true);
}
// Autonomous runs (overnight triage, CI-failure triage) stage plans the same way:
const pending = await client.ai.listPendingActions();confirmAction() is the deprecated gate
confirmAgentAction(), which posts to /api/ai-workspace/agent/confirm. The similarly named confirmAction() targets the legacy intent-parser endpoint. For a long time it was the only confirm this SDK exposed, so code written against older versions may be approving through a path the agent does not stage plans against — worth checking.Errors
The hierarchy mirrors the Python SDK’s: LithoraError → LithoraApiError → LithoraAuthError (401), plus LithoraAuthChallengeError for an incomplete login and LithoraTimeoutError for a request that ran out of time.
import {
LithoraApiError,
LithoraAuthError,
LithoraTimeoutError,
} from "@lithora/sdk";
try {
await client.tasks.create({ title: "Ship it", project_id: projectId });
} catch (err) {
if (err instanceof LithoraAuthError) {
// 401 — token missing, expired or revoked
} else if (err instanceof LithoraApiError && err.status === 422) {
console.error(err.detail);
} else if (err instanceof LithoraTimeoutError) {
// the request exceeded the client timeout
} else {
throw err;
}
}Resource namespaces
| Namespace | Methods |
|---|---|
| client.auth | login(), me() |
| client.tokens | create(), list(), revoke() |
| client.teams | create(), list(), get(), members() |
| client.projects | create(), list(), get(), update(), patch(), delete() |
| client.tasks | create(), list(), my(), get(), update(), setStatus(), delete(), bulkCreate(), setGithubRepo(), pushToGithub() |
| client.workItems | list(), get(), create(), update(), delete(), createRelation(), relations(), children(), parents(), deleteRelation(), cycleTime(), graph(), prStatus(), resolveRef() |
| client.automations | list(), create(), get(), toggle(), execute(), runs(), runStatus(), export(), versions(), rollback(), templates() |
| client.github | status(), repos(), connectUrl() |
| client.search | query(), recent(), suggestions() |
| client.ai | createSession(), listSessions(), getSession(), chat(), linkItem(), search(), confirmAgentAction(), listPendingActions() |
| client.runners | create(), list(), revoke(), updateManifest(), dispatch(), jobs(), audit() |
| client.sprints | list(), create(), plan(), velocity(), get(), update(), close(), summary() |
| client.webhooks | list(), create(), eventTypes(), get(), update(), delete(), logs(), clearLogs(), stats(), regenerateSecret(), test() |
| client.data | fields(), export_(), importTasks(), importEntity() |
This table and the Python SDK’s are kept in step by a test, not by discipline: CI compares the (verb, path) sets of both SDKs’ source and fails when TypeScript is missing an endpoint Python exposes. That check exists because the two had drifted by 26 endpoints before anyone noticed.
There is no auth.register() in either SDK — the route it called was removed from the API for an account-takeover defect. Accounts are created only through the email-OTP signup flow in the web app.