Touchmark SDK
Price AI by the quality of its output, not by tokens. Your app emits the events it already produces — model outputs, tool calls, code diffs — and Touchmark returns a quality-adjusted price for each one.
This is the reference for the TypeScript SDK (@touchmark/sdk).
- Quickstart — from install to a running integration in ~10 minutes.
- API reference — every option, method, and type.
- Errors & recovery — every error code and exactly what to do about it.
- Reference example: a resilient
emit— the recovery wrapper worth copying, tested in CI so it never goes stale. - Reference example: consuming valuations — the stream loop that applies fair prices to your bill, tested in CI.
The mental model
Three concepts, and one rule that explains the shape of the whole SDK.
- Session — one pricing context, opened per scope: a Lovable project, a Ramp document, one
API conversation. You identify the scope with your own durable
scope_id; Touchmark mints asession_idfor it. - Event — something worth pricing or scoring happened. You
emitit with apayload(what was produced) and, if it's billable, abase_price_usd(your current price for it). - Valuation — Touchmark's verdict: a quality score and the event's fair price
(
fair_price_usd). You set the event's price to it on your bill. A later re-valuation can revise it — so a past charge can change, and you simply apply the newest absolute.
The rule: scoring runs out-of-band on a judge with no bounded latency, so a valuation is
never ready when emit returns. Therefore emit is fire-and-forget and carries no valuations —
it returns nothing. Valuations arrive only on a separate stream you consume in its own loop.
That one fact is why the API looks the way it does.
import { Touchmark } from "@touchmark/sdk";
const tm = new Touchmark({ api_key: process.env.TOUCHMARK_API_KEY! });
// Open a session for a scope (idempotent — safe to call again on restart).
const { session_id } = await tm.session.start({ scope_id: "project-42", user_id: "user-7" });
// Emit events as they happen. Fire-and-forget — never await this for a price.
tm.session
.emit({
session_id,
event_id: tm.newEventId(), // mint once; persist it with your event so a retry reuses it
event_idx: 0,
event_type: "model_output",
payload: { text: "…generated code…" },
base_price_usd: 0.02, // omit for a context event
})
.catch((err) => console.error("touchmark emit failed", err));
// Apply valuations from their own loop, one per session — run it concurrently with your
// emits (e.g. wrap it in `void (async () => { … })()`); this loop never returns. See the Quickstart.
// In a multi-instance backend, keep it to one consumer per session: reference.md#run-exactly-one-consumer-per-session
for await (const v of tm.session.streamValuations({ session_id })) {
billing.setEventPrice(v.event_id, v.fair_price_usd); // billing is your own system; absolute price — idempotent, gap-proof
}
New here? Start with the Quickstart.
The SDK is a hand-written, idiomatic wrapper over the generated client for Touchmark's wire
contract. You never touch protobuf or gRPC. If you're curious about the protocol underneath, see
proto/touchmark/v1/session.proto and the
platform architecture.