touchmαrkdocstouchmark.ai

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.

  1. 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 a session_id for it.
  2. Event — something worth pricing or scoring happened. You emit it with a payload (what was produced) and, if it's billable, a base_price_usd (your current price for it).
  3. 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.