@chitmark/sdk
v0.5.2
Published
Official TypeScript SDK for Chitmark by Open Agent Ledger: trust decisions on agent-mediated actions (verify, feedback, challenge)
Maintainers
Readme
@chitmark/sdk
Official TypeScript / JavaScript SDK for Chitmark: trust decisions on agent-mediated actions, tuned by business outcomes.
AI agents and multi-account farms drain free tiers, trial credits, and API allowances while looking exactly like your best customers. Chitmark scores each action in under 50 ms and returns allow, challenge, or deny. Then your outcomes (conversion, credit burn, chargeback) come back through feedback and tune the next decision.
Try it without a key: run the playground. Live service health: chitmark.com/status.
Install
npm install @chitmark/sdk
# or
pnpm add @chitmark/sdkRequires Node >= 24. Runs on Node, browsers, Cloudflare Workers, and other edge runtimes (see the /edge entrypoint below).
Quick start
import { Chitmark } from "@chitmark/sdk";
const chitmark = new Chitmark({
apiKey: process.env.CHITMARK_API_KEY!,
// Optional, defaults shown
onDegraded: "challenge", // timeouts return a challenge, they never throw or allow
timeoutMs: 800,
piiMode: "hashed", // 'hashed' | 'none' | 'raw'
env: "production",
});
const verdict = await chitmark.verify({
action: "signup",
session: "sess_9f3a",
surface: "app.acme.com/signup",
subject: {
email: "[email protected]", // hashed client-side before the wire
ip: "203.0.113.7", // truncated to /24 client-side
userAgent: req.headers["user-agent"],
headers: req.headers,
},
});
if (verdict.decision === "challenge") {
// stop and run the challenge flow (below)
}
// Persist verdict.eventId on the account row: feedback joins only on that id.The three verbs
| Verb | Method | Endpoint | When |
| :------------ | :--------------------- | :------------------- | :--------------------------- |
| verify | chitmark.verify() | POST /v1/verify | At signup / trial / action |
| feedback | chitmark.feedback() | POST /v1/feedback | After an outcome is known |
| challenge | chitmark.challenge() | POST /v1/challenge | On challenge or degraded |
Timeouts and transport errors return a degraded challenge verdict and never throw on verify. HTTP errors throw ChitmarkApiError with the status attached. Set onDegraded to challenge (the only supported value), never allow on degraded.
Report outcomes
Store the eventId from verify on the account row, then report what happened against that same id. Never guess or derive the id from email or IP.
// At signup: persist the join key
db.accounts.update(userId, { chitmarkEventId: verdict.eventId });
// Later, when a label matures:
await chitmark.feedback({
eventId: account.chitmarkEventId, // the stored join key
outcome: "credit_burn", // converted | chargeback | abuse_confirmed | ...
value: 87.4, // dollar amount: `unit` rides along (default "usd")
observedAt: "2026-08-06T04:00:00Z",
});Exact duplicate feedback bodies derive the same warehouse id, so retrying a connector never double-counts a burned value.
Handle a challenge
When verify returns challenge, issue one, solve it on the client, and complete it. Proof-of-work difficulty is server-issued (4 by default, up to 6 at higher risk tiers): about 65k hashes, milliseconds for one real user, costly at farm scale.
import { createHash } from "node:crypto";
const issued = await chitmark.challenge({
eventId: verdict.eventId,
session: "sess_9f3a", // the challenge binds to this session
});
if (issued.instructions.type === "pow") {
const { challengeId, instructions } = issued;
const prefix = "0".repeat(instructions.difficulty);
let nonce = 0;
for (; ; nonce++) {
const hex = createHash("sha256")
.update(`${challengeId}:${instructions.seed}:${nonce}`)
.digest("hex");
if (hex.startsWith(prefix)) break;
}
await chitmark.completeChallenge({
eventId: verdict.eventId,
challengeId,
session: "sess_9f3a",
proof: { type: "proof_of_work", nonce: String(nonce) },
});
// Re-verify with context: { challengeId } so the next verdict honors it.
}Verify the receipt
Every production verdict ships a verdictToken: an ES256 JWT bound to session, origin, and event. Verify it before acting on high-value decisions:
const claims = await chitmark.verifyVerdictToken(verdict.verdictToken, {
session: "sess_9f3a", // enforce session binding (recommended)
aud: "api.chitmark.com", // enforce origin binding
});
// claims: { eventId, decision, actorType, confidence, jti, exp, ... }Rejects expired tokens, unknown keys, bad signatures, and session or origin mismatches with typed VerdictTokenError codes.
PII modes
| Mode | Behavior |
| :----------------- | :--------------------------------------------------------- |
| hashed (default) | SHA-256 email, /24 IP truncation, allowlisted form fields |
| none | Derived/header-shape signals only (Web Bot Auth preserved) |
| raw | Tenant opt-in only; triggers higher compliance review |
Framework integrations
Express middleware and a Next.js route helper ship in the package:
// Express: 403 on deny, 428 + challenge payload on challenge
import { chitmarkGate } from "@chitmark/sdk";
app.post("/signup", chitmarkGate(chitmark, "signup"), signupHandler);
// Next.js App Router
import { verifyNextRequest } from "@chitmark/sdk";
const verdict = await verifyNextRequest(req, chitmark, { action: "signup" });Cloudflare Workers
The /edge entrypoint is the same client with a request-first helper:
import { Chitmark } from "@chitmark/sdk/edge";
const chitmark = new Chitmark({ apiKey: env.CHITMARK_API_KEY });
const verdict = await chitmark.verifyRequest(req, { action: "signup" });Agent integration
Using Cursor, Claude Code, Codex, or another coding agent? Point it at
chitmark.com/SKILL.md, or paste this into your
prompt: Integrate Chitmark into my app following https://chitmark.com/SKILL.md.
Resources
License
Proprietary: see LICENSE.
