@chitmark/sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,208 +1,77 @@
1
- # @chitmark/sdk
1
+ # Chitmark SDK for TypeScript
2
2
 
3
- Official TypeScript and JavaScript SDK for Chitmark.
4
-
5
- - [Trust Decisions](https://chitmark.com/docs/quickstart/trust-decisions) lets you screen costly actions such as signups, credit grants, and API requests with `verify`, `feedback`, and `challenge`.
6
- - [Agent Attribution](https://chitmark.com/docs/guides/agent-attribution) lets you recognize returning site sessions and evaluate signed proof from linked agents.
7
- - [Agent Paid Access](https://chitmark.com/docs/guides/agent-paid-access) lets you buy or sell controlled access to API resources with hosted checkout and seller-controlled delivery.
3
+ Use Chitmark from your backend to screen costly actions, recognize enrolled agents with signed proof, and manage paid API access.
8
4
 
9
5
  [![npm version](https://img.shields.io/npm/v/@chitmark/sdk.svg)](https://www.npmjs.com/package/@chitmark/sdk)
10
6
 
11
- Try it without a key: [run the playground](https://chitmark.com/playground). Live service health: [chitmark.com/status](https://chitmark.com/status).
12
-
13
7
  ## Install
14
8
 
9
+ Requires Node.js 24 or later. The package ships TypeScript declarations and supports ESM and CommonJS.
10
+
15
11
  ```bash
16
12
  npm install @chitmark/sdk
17
- # or
18
- pnpm add @chitmark/sdk
19
13
  ```
20
14
 
21
- Requires Node >= 24. Runs on Node, browsers, Cloudflare Workers, and other edge runtimes (see the `/edge` entrypoint below).
22
-
23
- ## Trust Decisions quick start
24
-
25
- ```typescript
26
- import { Chitmark } from "@chitmark/sdk";
27
-
28
- const chitmark = new Chitmark({
29
- apiKey: process.env.CHITMARK_API_KEY!,
30
- // Optional, defaults shown
31
- onDegraded: "challenge", // timeouts return a challenge, they never throw or allow
32
- timeoutMs: 800, // local base URLs default to 2000ms
33
- piiMode: "hashed", // 'hashed' | 'none' | 'raw'
34
- env: "production",
35
- });
36
-
37
- const verdict = await chitmark.verify({
38
- action: "signup",
39
- session: "sess_9f3a",
40
- surface: "app.acme.com/signup",
41
- subject: {
42
- email: "buyer@acmecorp.com", // hashed client-side before the wire
43
- ip: "203.0.113.7", // truncated to /24 client-side
44
- userAgent: req.headers["user-agent"],
45
- headers: req.headers,
46
- },
47
- });
48
-
49
- if (verdict.decision === "challenge") {
50
- // stop and run the challenge flow (below)
51
- }
52
- // Persist verdict.eventId on the account row: feedback joins only on that id.
53
- ```
15
+ Create a scoped API key in the [Chitmark dashboard](https://chitmark.com/dashboard) and set `CHITMARK_API_KEY` in your server environment.
54
16
 
55
- ## The three verbs
17
+ ## Resolve agent attribution
56
18
 
57
- | Verb | Method | Endpoint | When |
58
- | :------------ | :--------------------- | :------------------- | :--------------------------- |
59
- | **verify** | `chitmark.verify()` | `POST /v1/verify` | At signup / trial / action |
60
- | **feedback** | `chitmark.feedback()` | `POST /v1/feedback` | After an outcome is known |
61
- | **challenge** | `chitmark.challenge()` | `POST /v1/challenge` | On `challenge` or `degraded` |
19
+ Use a key with `attribution:read`. Replace the sample request with the inbound request from your server.
62
20
 
63
- Store-outage degradation on verify is **HTTP 200** with a Verdict
64
- (`degraded: true`); the SDK deserializes it like any other Verdict. Verify
65
- never returns HTTP 503. Timeouts and transport errors synthesize a local
66
- degraded challenge Verdict and never throw on `verify`. HTTP 4xx / 429 Error
67
- responses throw `ChitmarkApiError`. Set `onDegraded` to `challenge` (the only
68
- supported value), never allow on degraded.
69
-
70
- ### Report outcomes
71
-
72
- 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.
21
+ ```ts
22
+ import { Chitmark } from "@chitmark/sdk";
73
23
 
74
- ```typescript
75
- // At signup: persist the join key
76
- db.accounts.update(userId, { chitmarkEventId: verdict.eventId });
24
+ const chitmark = new Chitmark({ apiKey: process.env.CHITMARK_API_KEY! });
25
+ const request = new Request("https://app.example.com/agent-task");
26
+ const result = await chitmark.attribution.resolve({ request });
77
27
 
78
- // Later, when a label matures:
79
- await chitmark.feedback({
80
- eventId: account.chitmarkEventId, // the stored join key
81
- outcome: "credit_burn", // converted | chargeback | abuse_confirmed | ...
82
- value: 87.4, // measured magnitude: `unit` rides along (default "usd"; also "credits" | "count")
83
- observedAt: "2026-08-06T04:00:00Z",
84
- });
28
+ console.log(result.identity.recognized, result.principal.email);
85
29
  ```
86
30
 
87
- Exact duplicate feedback bodies derive the same warehouse id (`eventId` + `outcome` + `value` + `unit` + `observedAt`), so connector retries with a stable business timestamp never double-count a burned value. Prefer an `Idempotency-Key` anyway; omitting `observedAt` uses server time and is not safe for bare retries.
31
+ The SDK reads fresh signed evidence from `Chitmark-Agent-Proof`. Without evidence, the sample returns an unknown identity and null email. An enrolled agent can sign for different recipients in a network-enabled deployment. Each site receives its own subject, and principal claims require a current grant for the recipient and purpose. Email also requires consent, `attribution:email`, and raw mode on an eligible plan.
88
32
 
89
- ### Handle a challenge
33
+ For relative request URLs, pass your configured HTTPS origin as `site`. See the [Attribution quickstart](https://chitmark.com/docs/guides/agent-attribution/) and [network proof guide](https://chitmark.com/docs/guides/agent-attribution-network/).
90
34
 
91
- 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.
35
+ ## Screen a signup
92
36
 
93
- ```typescript
94
- import { createHash } from "node:crypto";
37
+ Use a key with `verify:write`. Generate and retain a session ID on your server for the attempt.
95
38
 
96
- const issued = await chitmark.challenge({
97
- eventId: verdict.eventId,
98
- session: "sess_9f3a", // the challenge binds to this session
99
- });
100
-
101
- if (issued.instructions.type === "pow") {
102
- const { challengeId, instructions } = issued;
103
- const prefix = "0".repeat(instructions.difficulty);
104
- let nonce = 0;
105
- for (; ; nonce++) {
106
- const hex = createHash("sha256")
107
- .update(`${challengeId}:${instructions.seed}:${nonce}`)
108
- .digest("hex");
109
- if (hex.startsWith(prefix)) break;
110
- }
111
- await chitmark.completeChallenge({
112
- eventId: verdict.eventId,
113
- challengeId,
114
- session: "sess_9f3a",
115
- proof: { type: "proof_of_work", nonce: String(nonce) },
116
- });
117
- // Re-verify with context: { challengeId }; completion alone does not authorize.
118
- }
119
- ```
120
-
121
- ### Verify the receipt
122
-
123
- Every production verdict ships a `verdictToken`: an ES256 JWT bound to session, origin, and event. Verify authenticity, then authorize on the decision:
39
+ ```ts
40
+ import { randomUUID } from "node:crypto";
41
+ import { Chitmark, isEligibleAllow } from "@chitmark/sdk";
124
42
 
125
- ```typescript
126
- const claims = await chitmark.verifyVerdictToken(verdict.verdictToken, {
127
- session: "sess_9f3a",
128
- tenantId: "org_acme",
43
+ const chitmark = new Chitmark({ apiKey: process.env.CHITMARK_API_KEY! });
44
+ const verdict = await chitmark.verify({
45
+ action: "signup",
46
+ session: randomUUID(),
47
+ surface: "app.example.com/signup",
48
+ subject: { email: "buyer@example.com" },
129
49
  });
130
50
 
131
- if (claims.degraded || claims.decision !== "allow") {
132
- throw new ChallengeRequiredError(); // HTTP 428 / challenge response
133
- }
134
-
135
- // Only now trust the allow.
136
- ```
137
-
138
- Rejects expired tokens, unknown keys, bad signatures, and session or origin mismatches with typed `VerdictTokenError` codes. A verified token that is not a non-degraded `allow` must still fail closed.
139
-
140
- Cache JWKS using HTTP headers: respect `Cache-Control` (`max-age=60`), and when `ETag` or `Last-Modified` are present use conditional requests (`If-None-Match` / `If-Modified-Since`) instead of a hard-coded refresh interval. Refresh immediately on unknown `kid`. Do not fetch on every verification. Every signed JWT requires `kid`. Keys are EC P-256 for ES256: require JWT `alg === "ES256"` before verifying (`wrong_algorithm` otherwise); never trust the token-requested alg. Rotation overlaps old and new keys; keep the retiring key until tokens under it expire (≥ 5 minutes + 60 seconds). An empty JWKS (`keys: []`) means verification is impossible: fail closed; never treat an empty set as proof a token is valid (local/CI may also emit `unsigned.<eventId>`). Pass a cached `jwks` option into `verifyVerdictToken` to skip the network call.
141
-
142
- ### PII modes
143
-
144
- | Mode | Behavior |
145
- | :----------------- | :--------------------------------------------------------- |
146
- | `hashed` (default) | SHA-256 email, /24 IP truncation, allowlisted form fields |
147
- | `none` | Derived/header-shape signals only (Web Bot Auth preserved) |
148
- | `raw` | Tenant opt-in only; triggers higher compliance review |
149
-
150
- ## Framework integrations
151
-
152
- Express middleware and a Next.js route helper ship in the package. They normalize transient failures, challenge responses (HTTP 428), and deny (HTTP 403) for you. Handle raw status codes when using the low-level `Chitmark` client or curl.
153
-
154
- ```typescript
155
- // Express: 403 on deny, 428 + challenge payload on challenge
156
- // 428 means the request is not rejected permanently; a required verification
157
- // precondition must be satisfied before the action can proceed.
158
- import { chitmarkGate } from "@chitmark/sdk";
159
- app.post("/signup", chitmarkGate(chitmark, "signup"), signupHandler);
160
-
161
- // Next.js App Router
162
- import { verifyNextRequest } from "@chitmark/sdk";
163
- const verdict = await verifyNextRequest(req, chitmark, { action: "signup" });
51
+ console.log(verdict.decision, isEligibleAllow(verdict), verdict.eventId);
164
52
  ```
165
53
 
166
- ## Cloudflare Workers
54
+ Grant value only after an explicit, non-degraded allow. Timeouts and transport failures on `verify` produce a degraded challenge. The default data mode hashes email before sending it. Store the returned `eventId` for outcome reporting.
167
55
 
168
- The `/edge` entrypoint is the same client with a request-first helper:
56
+ | Method | Use |
57
+ | --------------------------------- | --------------------------------------------------------------------------- |
58
+ | `verify` | Get an allow, challenge, or deny verdict. |
59
+ | `challenge` / `completeChallenge` | Issue and complete an additional check. Reuse the attempt's session. |
60
+ | `feedback` | Report an outcome against the stored `eventId` with a `feedback:write` key. |
61
+ | `verifyVerdictToken` | Verify a signed receipt against the expected session and tenant. |
169
62
 
170
- ```typescript
171
- import { Chitmark } from "@chitmark/sdk/edge";
172
-
173
- const chitmark = new Chitmark({ apiKey: env.CHITMARK_API_KEY });
174
- const verdict = await chitmark.verifyRequest(req, { action: "signup" });
175
- ```
176
-
177
- ## Agent Attribution
178
-
179
- Use an Attribution read key to recognize site sessions or resolve an authorized principal association. Unknown is a successful outcome, and email is nullable.
180
-
181
- ```ts
182
- const result = await chitmark.attribution.resolve({ request });
183
- console.log(result.identity.recognized, result.principal.email);
184
- ```
185
-
186
- See the [Agent Attribution guide](https://chitmark.com/docs/guides/agent-attribution/) for request adapters, link grants, and disclosure rules.
63
+ See the [Trust Decisions quickstart](https://chitmark.com/docs/quickstart/trust-decisions/), [challenge flow](https://chitmark.com/docs/api/challenge/), and [verdict token guide](https://chitmark.com/docs/guides/verdict-tokens/).
187
64
 
188
65
  ## Agent Paid Access
189
66
 
190
- Call `chitmark.charge(request)` to request a quote, continue through hosted checkout, or check purchase status. Sellers use separate credentials to reserve execution and record delivery. See the [Agent Paid Access guide](https://chitmark.com/docs/guides/agent-paid-access) for scopes and a complete buyer and seller example.
67
+ Use `chitmark.charge(request)` to request a quote, continue checkout, or read purchase status. Sellers use separate scopes to reserve execution and record delivery. See the [Agent Paid Access guide](https://chitmark.com/docs/guides/agent-paid-access/).
191
68
 
192
- ## Agent integration
193
-
194
- Using Cursor, Claude Code, Codex, or another coding agent? Point it at
195
- [chitmark.com/SKILL.md](https://chitmark.com/SKILL.md), or paste this into your
196
- prompt: `Integrate Chitmark into my app following https://chitmark.com/SKILL.md`.
197
-
198
- ## Resources
69
+ ## Framework integrations
199
70
 
200
- - [Agent integration skill](https://chitmark.com/SKILL.md)
201
- - [API reference](https://chitmark.com/docs)
202
- - [Quickstart](https://chitmark.com/docs/quickstart)
203
- - [OpenAPI contract](https://chitmark.com/openapi.yaml)
204
- - [Examples repository](https://github.com/nonameuserd/chitmark-examples)
71
+ - Express and Next.js use `chitmarkGate` and `verifyNextRequest`. [Framework guide](https://chitmark.com/docs/guides/drop-ins/)
72
+ - Workers use `ChitmarkEdge` from `@chitmark/sdk/edge`. [TypeScript reference](https://chitmark.com/docs/sdk/typescript/)
73
+ - Try a verdict without an API key in the [playground](https://chitmark.com/playground/).
205
74
 
206
75
  ## License
207
76
 
208
- Proprietary: see [LICENSE](LICENSE).
77
+ Proprietary. The package includes the license terms in `LICENSE`.
package/dist/edge.cjs CHANGED
@@ -2,21 +2,47 @@
2
2
 
3
3
  // src/attribution.ts
4
4
  function attributionEvidence(options, mode) {
5
- const { request, agentProof, session, piiMode = mode } = options;
5
+ const { request, agentProof, session, purpose, piiMode = mode } = options;
6
6
  const headers = request.headers;
7
7
  const header = typeof headers.get === "function" ? headers.get("chitmark-agent-proof") : Object.entries(headers).find(
8
8
  ([name]) => name.toLowerCase() === "chitmark-agent-proof"
9
9
  )?.[1];
10
- let site = options.site;
11
- if (!site && request.url?.startsWith("https://")) site = new URL(request.url).origin;
12
- let proof = agentProof;
13
- if (!proof && typeof header === "string" && header.length <= 2048 && site) {
10
+ let url;
11
+ if (!options.site && request.url) {
14
12
  try {
15
- proof = JSON.parse(header);
13
+ url = new URL(request.url);
16
14
  } catch {
17
15
  }
18
16
  }
19
- return { site, session, piiMode, agentProof: proof };
17
+ const site = options.site ?? (url?.protocol === "https:" ? url.origin : void 0);
18
+ const proof = agentProof ?? (site ? parseProof(header) : void 0);
19
+ return { site, session, piiMode, purpose, agentProof: proof };
20
+ }
21
+ function parseProof(header) {
22
+ if (typeof header !== "string" || header.length > 2048) return;
23
+ try {
24
+ const value = JSON.parse(header);
25
+ if (!value || typeof value.nonce !== "string" || value.nonce.length > 36 || typeof value.issuedAt !== "number" || !Number.isSafeInteger(value.issuedAt) || typeof value.signature !== "string" || value.signature.length > 86)
26
+ return;
27
+ const { nonce, issuedAt, signature } = value;
28
+ if ("iss" in value) {
29
+ const key = value.agentKey;
30
+ if (value.iss === "chitmark-agent-key-v1" && key?.kty === "OKP" && key.crv === "Ed25519" && typeof key.x === "string" && key.x.length === 43 && !("d" in key)) {
31
+ return {
32
+ iss: value.iss,
33
+ agentKey: { kty: key.kty, crv: key.crv, x: key.x },
34
+ nonce,
35
+ issuedAt,
36
+ signature
37
+ };
38
+ }
39
+ return;
40
+ }
41
+ if (typeof value.linkId === "string" && value.linkId.length <= 36)
42
+ return { linkId: value.linkId, nonce, issuedAt, signature };
43
+ } catch {
44
+ }
45
+ return void 0;
20
46
  }
21
47
 
22
48
  // src/credentials.ts
@@ -153,7 +179,7 @@ var DEFAULT_BASE_URLS = {
153
179
  };
154
180
  var DEFAULT_TIMEOUT_MS = 800;
155
181
  var LOCAL_TIMEOUT_MS = 2e3;
156
- var SDK_VERSION = "0.8.0";
182
+ var SDK_VERSION = "0.9.0";
157
183
  function defaultTimeoutMs(baseUrl) {
158
184
  try {
159
185
  const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "").toLowerCase();