Documentation

Quickstart

Get your first verdict in 60 seconds. Verify a signup with curl, then wire the same decision into your app with the SDK.

Get your first verdict in 60 seconds

Start with curl to verify a signup, then install the SDK and wire the same decision into your application. Persist the returned eventId; use it later to report what happened. Feedback joins on that id, never on email or IP.

Prefer to see it before touching a terminal? The playground runs the same decision in the browser.

curl -s https://api.chitmark.com/v1/verify \
  -H "Authorization: Bearer $CHITMARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "signup",
    "session": "sess_quickstart_1",
    "surface": "app.acme.com/signup",
    "subject": {
      "emailSha256": "'"$(printf 'user@example.com' | sha256sum | cut -d" " -f1)"'",
      "ipTrunc": "203.0.113.0/24",
      "formDataHashed": { "companyDomain": "acme.example" },
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
      "headers": { "accept": "text/html", "accept-language": "en-US" }
    },
    "context": { "decisionCosts": { "falseAllow": 1, "falseChallenge": 1, "falseDeny": 100 } }
  }' | jq '{decision, actorType, confidence, reasons, eventId, degraded}'

0. Get a key

API keys are issued through the public beta: a ck_… value you send as Authorization: Bearer … on every request. Export it before you start:

export CHITMARK_API_KEY=ck_…
# the SDKs and CLI read CHITMARK_API_KEY automatically

1. Install

Packages: @chitmark/sdk on npm · @chitmark/cli on npm · chitmark on PyPI

npm install @chitmark/sdk

2. Client

Create one shared client. Keep onDegraded: "challenge" (the default).

import { Chitmark } from "@chitmark/sdk";

export const chitmark = new Chitmark({
  apiKey: process.env.CHITMARK_API_KEY!,
  onDegraded: "challenge", // NEVER "allow"
  timeoutMs: 800,
  piiMode: "hashed", // default: raw is opt-in
});

3. Verify the action

Call verify on signup / trial. Persist eventId on the account row before you branch: feedback joins only on that key.

Your application owns what happens next:

DecisionWhat your app does
allowContinue: create the account, issue the grant
challengeHTTP 428, then issue friction with POST /v1/challenge (or your own step-up)
denyStop: do not create the account or issue the grant

Only continue on allow when degraded is false. If degraded is true, the decision is always challenge.

{
  "decision": "challenge",
  "eventId": "evt_01JQ8K3M2N7P9R",
  "confidence": 0.84,
  "actorType": "likely_farming",
  "reasons": ["velocity_burst", "weak_identity"],
  "degraded": false,
  "verdictToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..."
}

4. Feedback when outcomes land

Feedback is asynchronous. Request path: verify → allow / challenge / deny. Outcome path: account + eventId → outcome → feedback. Report credit_burn, converted, chargeback, abuse_confirmed, etc. when the business fact is known, never during the signup request. Security invariant: feedback cannot create attribution; it only attaches an outcome to an existing verify eventId. Unknown or cross-tenant event ids return 404 unknown_event_id.

await chitmark.feedback({
  eventId: account.chitmarkEventId,
  outcome: "credit_burn",
  value: 12.4,
  unit: "usd",
});

5. Join the community

Questions, feedback, and the weekly Agent Farming Ledger discussion happen in Discord.