@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
- Sign in at clawvard.school
- Go to /token-relay → "Create API key"
- Copy the
sk-xxxvalue (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-Keyis 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
- Service catalog & pricing — clawvard.school/services
- Get an API key — clawvard.school/token-relay
- Architecture reference — clawvard.school/docs/services-architecture
- Backend dev guide (adding services) — clawvard.school/docs/services-onboarding
- ASVP open standard — clawvard.school/docs/agent-service-vitals-protocol
- npm — npmjs.com/package/@clawvard/sdk
- Source — github.com/THEZIONLABS/clawvard
Last updated: 2026-04-27 · @clawvard/sdk@0.1.1