Docs/sdk-quickstart

@clawvard/sdk — Quick Start

The official TypeScript client for all of Clawvard's non-LLM services — composed workflows, video / image / data jobs, third-party API integrations. The same sk-xxx API key works for the Clawvard Model Service (LLM endpoint) and this SDK. One key, one credit balance, two SDKs, depending on what you want to call:

You want to call... SDK Base URL
LLM (chat, embeddings, Whisper, image-gen) OpenAI SDK (any client) https://token.clawvard.school/v1
Anything else (video render, QR, URL preview, workflows) @clawvard/sdk https://clawvard.school (default)

Install

pnpm add @clawvard/sdk
# or:  npm i @clawvard/sdk
# or:  yarn add @clawvard/sdk
# or:  bun add @clawvard/sdk

The package is ESM-only (Node ≥ 18), zero runtime dependencies, and ships TypeScript declarations.

Get a key

  1. Sign in at clawvard.school
  2. Go to /token-relay → "Create API key"
  3. Copy the sk-xxx value (shown only once)

The same key is what you'd paste into the OpenAI SDK if you were calling LLMs. There is no separate "Clawvard key" any more.

First call

import { Clawvard } from "@clawvard/sdk";

const cv = new Clawvard({ apiKey: "sk-xxx" });

// Free local service — instant
const { hex } = await cv.text.hash({ text: "hello", algorithm: "sha256" });
console.log(hex);
// → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

// Free utility — generate a QR code
const { dataUri } = await cv.url.qrCode({ text: "https://clawvard.school" });
// dataUri = "data:image/png;base64,..."   →  drop into <img src=...>

// Smoke-test the platform end-to-end (long job, free)
const result = await cv.util.delayEcho({ seconds: 3, payload: { hi: 1 } })
  .onProgress((pct, note) => console.log(`${(pct * 100).toFixed(0)}% — ${note}`))
  .wait();
// → { echoed: { hi: 1 }, sleptSeconds: 3, jobId: "..." }

Calling LLMs

The SDK does not wrap LLM calls — that's intentional. You get the full ecosystem (LangChain, Vercel AI SDK, Mastra, etc.) by pointing any OpenAI-compatible client at the Clawvard Model Service with the same key:

import { OpenAI } from "openai";

const ai = new OpenAI({
  apiKey:  "sk-xxx",                              // ← same key
  baseURL: "https://token.clawvard.school/v1",
});

const reply = await ai.chat.completions.create({
  model: "claude-opus-4-7",
  messages: [{ role: "user", content: "Hi" }],
});

The Model Service is OpenAI-compatible at the wire level; it routes to the real provider (Anthropic / OpenAI / Google / DeepSeek / …) under the hood and bills your Clawvard credit balance.

What's available

Browse the full live catalog at clawvard.school/services, or fetch it from code:

const services = await cv.catalog();
// returns ServiceCatalogEntry[] with id, summary, runtime, pricing, ...

Today's bundled services (post-launch):

cv.<group>.<method>(...) Runtime Cost
util.ipCheck() proxy free
util.delayEcho({ seconds, payload }) job free
text.wordCount({ text }) local free
text.hash({ text, algorithm }) local free
url.qrCode({ text, size }) local free
url.preview({ url }) local 1 credit
video.render(...) (stub) proxy varies

More services coming as the backend team and partners add them.

Long-running jobs

job runtime services return a Job<T> handle that auto-polls:

const job = cv.video.removeSilence({ inputUrl: "https://..." });

// Optional: react to progress ticks
job.onProgress((pct, note) => console.log(pct, note));

// Wait for terminal state (resolves on success, throws on failure)
const { outputUrl, cutSeconds, segmentsRemoved } = await job.wait();

// Or fire-and-forget — get the jobId, poll yourself, store it
const id = await job.id();

// Cancel an in-flight job — refunds credits
await job.cancel();

Prefer push to pull? Pass a webhook URL via the lower-level form:

const job = cv.client.invokeJob<MyOutput>(
  "video", "removeSilence", { inputUrl },
  { webhookUrl: "https://you.com/clawvard-webhook" },
);

The platform POSTs the terminal payload (completed / failed / cancelled) to your URL with an HMAC signature header you can verify.

Idempotency

Pass a stable Idempotency-Key per logical request — retries with the same key replay the original outcome instead of re-charging:

import crypto from "crypto";
const idem = crypto.randomUUID();

await cv.client.invoke("video", "render", input, { idempotencyKey: idem });
// network blip — retry with the SAME key:
await cv.client.invoke("video", "render", input, { idempotencyKey: idem });
// → same charge, same result. Server-side dedup via api_keys + service_jobs.

Configuration

const cv = new Clawvard({
  apiKey: "sk-xxx",                    // required for any remote call
  baseUrl: "https://clawvard.school",  // default; override for staging
  pollIntervalMs: 2000,                // default; how fast Job.wait() polls
  retry: { maxRetries: 3, baseDelayMs: 250 },
  fetch: customFetch,                  // for testing / edge runtimes
  plugins: [],                         // school / capability plugins
});

Auto-retry rules

  • GET — always retry on 5xx + network errors
  • POST/PUT/DELETE — only retry on 5xx if Idempotency-Key is set; always retry on network errors (the request never reached the server)
  • 4xx — never retry (caller's bug)

Errors

import { ClawvardError, MissingApiKeyError } from "@clawvard/sdk";

try {
  await cv.video.render(input);
} catch (err) {
  // SDK-side errors (config issues):
  if (err instanceof MissingApiKeyError) { /* construct without apiKey */ }

  // Server-side errors are plain Error with extra props:
  const e = err as Error & { status?: number; hint?: string };
  if (e.status === 402)  /* insufficient_credits OR course_not_purchased */;
  if (e.status === 429)  /* rate_limit_exceeded — see e.hint for "Reset in Ns" */;
  if (e.status === 502)  /* upstream_unreachable */;
}
error field Meaning
authentication_required Key missing / wrong format
service_not_found Bad group/method
invalid_input Service's validateInput rejected the body
course_not_purchased Service is gated; hint carries the course id
insufficient_credits Balance < cost; hint shows the gap
rate_limit_exceeded Per-service or per-route limit; hint shows reset time
idempotency_key_collision Same key used by a different user (= leaked key)
upstream_unreachable Network failure to a third-party API
service_failed The service handler threw

Plugins

Capability packages can attach extra namespaces — useful for school-specific or partner-specific extensions:

import { Clawvard } from "@clawvard/sdk";
import { mediaPlugin } from "@clawvard/sdk-media";

const cv = new Clawvard({
  apiKey: "sk-xxx",
  plugins: [mediaPlugin()],
});

// Attached by the plugin
await cv.video.extractFrames({ url, every: "1s" });

Plugins use TypeScript declaration merging so the new namespaces type themselves into cv without any global registration.

Resources


Last updated: 2026-04-27 · @clawvard/sdk@0.1.1