@grantex/sdk 0.6.0 → 0.7.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.
Files changed (181) hide show
  1. package/LICENSE +185 -185
  2. package/NOTICE +16 -16
  3. package/README.md +974 -947
  4. package/dist/authorization-details.d.ts +86 -0
  5. package/dist/authorization-details.d.ts.map +1 -0
  6. package/dist/authorization-details.js +209 -0
  7. package/dist/authorization-details.js.map +1 -0
  8. package/dist/canonical.d.ts +47 -0
  9. package/dist/canonical.d.ts.map +1 -0
  10. package/dist/canonical.js +163 -0
  11. package/dist/canonical.js.map +1 -0
  12. package/dist/caps/index.d.ts +19 -0
  13. package/dist/caps/index.d.ts.map +1 -0
  14. package/dist/caps/index.js +19 -0
  15. package/dist/caps/index.js.map +1 -0
  16. package/dist/caps/limits.d.ts +40 -0
  17. package/dist/caps/limits.d.ts.map +1 -0
  18. package/dist/caps/limits.js +109 -0
  19. package/dist/caps/limits.js.map +1 -0
  20. package/dist/caps/memory.d.ts +15 -0
  21. package/dist/caps/memory.d.ts.map +1 -0
  22. package/dist/caps/memory.js +57 -0
  23. package/dist/caps/memory.js.map +1 -0
  24. package/dist/caps/meter.d.ts +107 -0
  25. package/dist/caps/meter.d.ts.map +1 -0
  26. package/dist/caps/meter.js +200 -0
  27. package/dist/caps/meter.js.map +1 -0
  28. package/dist/caps/postgres.d.ts +64 -0
  29. package/dist/caps/postgres.d.ts.map +1 -0
  30. package/dist/caps/postgres.js +221 -0
  31. package/dist/caps/postgres.js.map +1 -0
  32. package/dist/caps/redis.d.ts +45 -0
  33. package/dist/caps/redis.d.ts.map +1 -0
  34. package/dist/caps/redis.js +173 -0
  35. package/dist/caps/redis.js.map +1 -0
  36. package/dist/client.d.ts +12 -0
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +517 -57
  39. package/dist/client.js.map +1 -1
  40. package/dist/decisions/action.d.ts +51 -0
  41. package/dist/decisions/action.d.ts.map +1 -0
  42. package/dist/decisions/action.js +237 -0
  43. package/dist/decisions/action.js.map +1 -0
  44. package/dist/decisions/index.d.ts +11 -0
  45. package/dist/decisions/index.d.ts.map +1 -0
  46. package/dist/decisions/index.js +8 -0
  47. package/dist/decisions/index.js.map +1 -0
  48. package/dist/decisions/verify.d.ts +103 -0
  49. package/dist/decisions/verify.d.ts.map +1 -0
  50. package/dist/decisions/verify.js +277 -0
  51. package/dist/decisions/verify.js.map +1 -0
  52. package/dist/denials.d.ts +132 -0
  53. package/dist/denials.d.ts.map +1 -0
  54. package/dist/denials.js +129 -0
  55. package/dist/denials.js.map +1 -0
  56. package/dist/evidence/build.d.ts +63 -0
  57. package/dist/evidence/build.d.ts.map +1 -0
  58. package/dist/evidence/build.js +201 -0
  59. package/dist/evidence/build.js.map +1 -0
  60. package/dist/evidence/canonical.d.ts +3 -0
  61. package/dist/evidence/canonical.d.ts.map +1 -0
  62. package/dist/evidence/canonical.js +3 -0
  63. package/dist/evidence/canonical.js.map +1 -0
  64. package/dist/evidence/checks.d.ts +13 -0
  65. package/dist/evidence/checks.d.ts.map +1 -0
  66. package/dist/evidence/checks.js +406 -0
  67. package/dist/evidence/checks.js.map +1 -0
  68. package/dist/evidence/document.d.ts +8 -0
  69. package/dist/evidence/document.d.ts.map +1 -0
  70. package/dist/evidence/document.js +298 -0
  71. package/dist/evidence/document.js.map +1 -0
  72. package/dist/evidence/hashing.d.ts +37 -0
  73. package/dist/evidence/hashing.d.ts.map +1 -0
  74. package/dist/evidence/hashing.js +106 -0
  75. package/dist/evidence/hashing.js.map +1 -0
  76. package/dist/evidence/index.d.ts +24 -0
  77. package/dist/evidence/index.d.ts.map +1 -0
  78. package/dist/evidence/index.js +24 -0
  79. package/dist/evidence/index.js.map +1 -0
  80. package/dist/evidence/result.d.ts +88 -0
  81. package/dist/evidence/result.d.ts.map +1 -0
  82. package/dist/evidence/result.js +88 -0
  83. package/dist/evidence/result.js.map +1 -0
  84. package/dist/evidence/schema-1.0.d.ts +2 -0
  85. package/dist/evidence/schema-1.0.d.ts.map +1 -0
  86. package/dist/evidence/schema-1.0.js +1398 -0
  87. package/dist/evidence/schema-1.0.js.map +1 -0
  88. package/dist/evidence/schema.d.ts +13 -0
  89. package/dist/evidence/schema.d.ts.map +1 -0
  90. package/dist/evidence/schema.js +264 -0
  91. package/dist/evidence/schema.js.map +1 -0
  92. package/dist/evidence/signature.d.ts +23 -0
  93. package/dist/evidence/signature.d.ts.map +1 -0
  94. package/dist/evidence/signature.js +136 -0
  95. package/dist/evidence/signature.js.map +1 -0
  96. package/dist/evidence/trace.d.ts +23 -0
  97. package/dist/evidence/trace.d.ts.map +1 -0
  98. package/dist/evidence/trace.js +69 -0
  99. package/dist/evidence/trace.js.map +1 -0
  100. package/dist/evidence/verify.d.ts +29 -0
  101. package/dist/evidence/verify.d.ts.map +1 -0
  102. package/dist/evidence/verify.js +163 -0
  103. package/dist/evidence/verify.js.map +1 -0
  104. package/dist/http.d.ts +12 -1
  105. package/dist/http.d.ts.map +1 -1
  106. package/dist/http.js +11 -5
  107. package/dist/http.js.map +1 -1
  108. package/dist/index.d.ts +16 -3
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +16 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/manifest.d.ts +178 -9
  113. package/dist/manifest.d.ts.map +1 -1
  114. package/dist/manifest.js +379 -16
  115. package/dist/manifest.js.map +1 -1
  116. package/dist/purpose.d.ts +29 -0
  117. package/dist/purpose.d.ts.map +1 -0
  118. package/dist/purpose.js +60 -0
  119. package/dist/purpose.js.map +1 -0
  120. package/dist/resources/agents.js +3 -3
  121. package/dist/resources/agents.js.map +1 -1
  122. package/dist/resources/anomalies.d.ts +3 -1
  123. package/dist/resources/anomalies.d.ts.map +1 -1
  124. package/dist/resources/anomalies.js +7 -1
  125. package/dist/resources/anomalies.js.map +1 -1
  126. package/dist/resources/audit.js +1 -1
  127. package/dist/resources/audit.js.map +1 -1
  128. package/dist/resources/budgets.js +2 -2
  129. package/dist/resources/budgets.js.map +1 -1
  130. package/dist/resources/credentials.js +1 -1
  131. package/dist/resources/credentials.js.map +1 -1
  132. package/dist/resources/decisions.d.ts +69 -0
  133. package/dist/resources/decisions.d.ts.map +1 -0
  134. package/dist/resources/decisions.js +115 -0
  135. package/dist/resources/decisions.js.map +1 -0
  136. package/dist/resources/domains.js +2 -2
  137. package/dist/resources/domains.js.map +1 -1
  138. package/dist/resources/dpdp.js +4 -4
  139. package/dist/resources/dpdp.js.map +1 -1
  140. package/dist/resources/grants.js +2 -2
  141. package/dist/resources/grants.js.map +1 -1
  142. package/dist/resources/policies.js +3 -3
  143. package/dist/resources/policies.js.map +1 -1
  144. package/dist/resources/scim.js +5 -5
  145. package/dist/resources/scim.js.map +1 -1
  146. package/dist/resources/tokens.d.ts.map +1 -1
  147. package/dist/resources/tokens.js +5 -1
  148. package/dist/resources/tokens.js.map +1 -1
  149. package/dist/resources/vault.js +2 -2
  150. package/dist/resources/vault.js.map +1 -1
  151. package/dist/resources/webauthn.d.ts +6 -1
  152. package/dist/resources/webauthn.d.ts.map +1 -1
  153. package/dist/resources/webauthn.js +5 -1
  154. package/dist/resources/webauthn.js.map +1 -1
  155. package/dist/resources/webhooks.js +1 -1
  156. package/dist/resources/webhooks.js.map +1 -1
  157. package/dist/revocations/feed.d.ts +61 -0
  158. package/dist/revocations/feed.d.ts.map +1 -0
  159. package/dist/revocations/feed.js +282 -0
  160. package/dist/revocations/feed.js.map +1 -0
  161. package/dist/revocations/index.d.ts +20 -0
  162. package/dist/revocations/index.d.ts.map +1 -0
  163. package/dist/revocations/index.js +19 -0
  164. package/dist/revocations/index.js.map +1 -0
  165. package/dist/revocations/set.d.ts +59 -0
  166. package/dist/revocations/set.d.ts.map +1 -0
  167. package/dist/revocations/set.js +87 -0
  168. package/dist/revocations/set.js.map +1 -0
  169. package/dist/scopes.d.ts +26 -0
  170. package/dist/scopes.d.ts.map +1 -0
  171. package/dist/scopes.js +37 -0
  172. package/dist/scopes.js.map +1 -0
  173. package/dist/types.d.ts +121 -4
  174. package/dist/types.d.ts.map +1 -1
  175. package/dist/types.js +0 -1
  176. package/dist/types.js.map +1 -1
  177. package/dist/verify.d.ts +31 -2
  178. package/dist/verify.d.ts.map +1 -1
  179. package/dist/verify.js +236 -29
  180. package/dist/verify.js.map +1 -1
  181. package/package.json +92 -84
package/README.md CHANGED
@@ -1,947 +1,974 @@
1
- # @grantex/sdk
2
-
3
- TypeScript SDK for the [Grantex](https://grantex.dev) delegated authorization protocol — OAuth 2.0 for AI agents.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@grantex/sdk)](https://www.npmjs.com/package/@grantex/sdk)
6
- [![License](https://img.shields.io/npm/l/@grantex/sdk)](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)
7
-
8
- > **[Homepage](https://grantex.dev)** | **[Docs](https://docs.grantex.dev)** | **[API Reference](https://docs.grantex.dev/api-reference)** | **[Sign Up Free](https://grantex.dev/dashboard/signup)** | **[GitHub](https://github.com/mishrasanjeev/grantex)**
9
-
10
- ## Installation
11
-
12
- **New in 0.6.0:** the wallet clients preserve the
13
- server's Base USDC `evmPayment` payload and expose `reconcileReservation()` for
14
- finalized settlement/expiry. Use `@grantex/x402@0.4.0` or later for
15
- the automatic 402/payment/retry flow. See [Base custody setup](https://docs.grantex.dev/guides/base-usdc-custody).
16
-
17
- ```bash
18
- npm install @grantex/sdk@0.6.0
19
- ```
20
-
21
- ## Quick Start
22
-
23
- ```typescript
24
- import { Grantex, verifyGrantToken } from '@grantex/sdk';
25
-
26
- const grantex = new Grantex({ apiKey: 'YOUR_API_KEY' });
27
-
28
- // 1. Register an agent
29
- const agent = await grantex.agents.register({
30
- name: 'Email Assistant',
31
- description: 'Reads and sends email on behalf of users',
32
- scopes: ['email:read', 'email:send'],
33
- });
34
-
35
- // 2. Request authorization
36
- const { consentUrl } = await grantex.authorize({
37
- agentId: agent.id,
38
- userId: 'usr_01J...',
39
- scopes: ['email:read', 'email:send'],
40
- });
41
- // Redirect the user to consentUrl — they approve in plain language
42
-
43
- // 3. Exchange authorization code for a grant token
44
- // (your redirect callback receives the `code` after user approves)
45
- const token = await grantex.tokens.exchange({ code, agentId: agent.id });
46
- console.log(token.grantToken); // RS256-signed JWT
47
- console.log(token.scopes); // ['email:read', 'email:send']
48
- console.log(token.grantId); // 'grnt_01J...'
49
-
50
- // 4. Verify locally using keys retrieved from the issuer's JWKS
51
- const grant = await verifyGrantToken(token.grantToken, {
52
- jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
53
- });
54
- console.log(grant.principalId); // 'usr_01J...'
55
-
56
- // 5. Revoke when done
57
- await grantex.tokens.revoke(grant.tokenId);
58
- ```
59
-
60
- ## Configuration
61
-
62
- ```typescript
63
- const grantex = new Grantex({
64
- apiKey: 'gx_....', // or set GRANTEX_API_KEY env var
65
- baseUrl: 'https://api.grantex.dev', // default
66
- issuer: 'https://grantex.dev', // optional when token issuer differs from API host
67
- jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', // optional JWKS override
68
- timeout: 30000, // request timeout in ms (default: 30s)
69
- });
70
- ```
71
-
72
- | Option | Type | Default | Description |
73
- |--------|------|---------|-------------|
74
- | `apiKey` | `string` | `process.env.GRANTEX_API_KEY` | API key for authentication |
75
- | `baseUrl` | `string` | `https://api.grantex.dev` | Base URL of the Grantex API |
76
- | `issuer` | `string` | derived from `jwksUri` | Expected JWT issuer for local signature verification |
77
- | `jwksUri` | `string` | `${baseUrl}/.well-known/jwks.json` | URL from which the verifier retrieves signing keys |
78
- | `timeout` | `number` | `30000` | Request timeout in milliseconds |
79
-
80
- ## OAuth Agent Profile Client
81
-
82
- `OAuthAgentClient` implements the client role in the prepared
83
- `draft-mishra-oauth-agent-grants-03` profile. It discovers and validates RFC
84
- 8414 metadata, creates an ES256 DPoP key by default, uses PAR and PKCE S256,
85
- validates `state` and the RFC 9207 `iss` response parameter, rotates refresh
86
- tokens, performs same-resource RFC 8693 attenuation, and creates DPoP proofs
87
- for protected-resource requests.
88
-
89
- ```typescript
90
- import { OAuthAgentClient } from '@grantex/sdk';
91
-
92
- const client = await OAuthAgentClient.create({
93
- issuer: 'https://grantex.dev',
94
- clientId: 'ag_01J...',
95
- redirectUri: 'https://agent.example/callback',
96
- resource: 'https://api.example/resource',
97
- });
98
-
99
- const pending = await client.beginAuthorization({
100
- scopes: ['grantex.resource.read'],
101
- principalHint: 'principal@example.com',
102
- });
103
-
104
- // Redirect the Principal to pending.authorizationUrl. In the callback:
105
- const tokens = await client.completeAuthorization(callbackUrl);
106
-
107
- const response = await client.fetch(
108
- 'https://api.example/resource',
109
- tokens.access_token,
110
- );
111
-
112
- const narrower = await client.attenuate(tokens.access_token, [
113
- 'grantex.resource.read',
114
- ]);
115
- const refreshAttempt = crypto.randomUUID();
116
- const rotated = await client.refresh(tokens.refresh_token!, {
117
- idempotencyKey: refreshAttempt,
118
- });
119
- await client.revoke(rotated.refresh_token!, 'refresh_token');
120
- ```
121
-
122
- Persist the idempotency key with the old refresh token when lost-response
123
- recovery must survive a caller restart. Repeating both within 300 seconds uses
124
- a fresh DPoP proof and returns the exact committed token values with a
125
- recalculated, non-extended `expires_in`. A
126
- different key or DPoP identity is treated as refresh-token reuse and revokes
127
- the token family. When no key is supplied, the client generates and retains one
128
- for the old refresh token for five minutes in the current process.
129
-
130
- Persist the generated key securely if an instance must survive process
131
- restarts. Supply the matching `privateKey` and `publicJwk` to `create`; both are
132
- required together. `principalHint` is optional account-discovery input and is
133
- not proof of the Principal's identity; live approval still requires the
134
- authorization server's passkey authentication. Plain HTTP endpoints are rejected unless
135
- `allowInsecureLoopback` is enabled for local loopback testing. Revision `-02`
136
- of the draft family is published as an active individual Internet-Draft;
137
- revision `-03` is the working candidate implemented here. Neither is an
138
- IETF-endorsed or independently certified standard.
139
-
140
- ## Agent prepaid wallets (SDK 0.5+)
141
-
142
- `PrepaidWalletAgentClient` uses an `OAuthAgentClient` and DPoP access token to
143
- list assigned wallets, reserve payments, and request threshold reloads.
144
- `PrincipalPrepaidWalletClient` uses a short-lived principal-session token to
145
- create and fund wallets, assign safe-default policy, manage layered spend
146
- policies and exact payment approvals, approve reloads, inspect activity, and
147
- block an assignment, wallet, or all wallets for one agent.
148
- `DeveloperPrepaidWalletPolicyClient` manages tenant-level policy with the
149
- developer API key.
150
-
151
- The access token must include `wallet:spend` and each action scope used in a
152
- payment (for example `weather:read`). Agent wallet listings intentionally omit
153
- custody-provider IDs, wallet addresses, principal IDs, and wallet metadata.
154
-
155
- ```typescript
156
- import {
157
- PrepaidWalletAgentClient,
158
- PrincipalPrepaidWalletClient,
159
- } from '@grantex/sdk';
160
-
161
- const agentWallets = new PrepaidWalletAgentClient({
162
- oauthClient,
163
- accessToken,
164
- });
165
-
166
- const principalWallets = new PrincipalPrepaidWalletClient({
167
- baseUrl: 'https://api.grantex.dev',
168
- sessionToken,
169
- });
170
-
171
- await principalWallets.createSpendPolicy({
172
- name: 'Shared research budget',
173
- scopeType: 'group',
174
- scopeId: 'research-agents',
175
- effect: 'limit',
176
- maxAmount: '1000000',
177
- windowType: 'month',
178
- onExceed: 'require_approval',
179
- purposes: ['research'],
180
- });
181
-
182
- const authorization = await agentWallets.authorizePayment({
183
- amount: '1000',
184
- asset: 'USDC',
185
- network: 'grantex:prepaid',
186
- recipient: 'merchant:weather-api',
187
- resource: 'https://merchant.example/weather',
188
- scope: 'weather:read',
189
- merchantId: 'merchant:weather-api',
190
- purpose: 'research',
191
- projectId: 'climate-2026',
192
- costCenter: 'engineering',
193
- maxTimeoutSeconds: 120,
194
- idempotencyKey: crypto.randomUUID(),
195
- });
196
- ```
197
-
198
- `authorizePayment` returns either a signed reservation or an
199
- `approval_required` response. After the principal approves that exact request,
200
- retry with its `approvalRequestId`, the same wallet, idempotency key, and all
201
- original payment fields. Approval is short-lived and single-use.
202
-
203
- Amounts are atomic-unit integer strings. Layered policy and exact approval are
204
- available in `@grantex/sdk` 0.5.0 and later.
205
-
206
- Self-hosted wallet deployments must also provide correct public resource
207
- routing, migrations `091` and `092`, durable notification delivery, merchant-side
208
- idempotency, and any external custody/provider integration. See [Prepaid Wallet
209
- Production
210
- Readiness](https://docs.grantex.dev/guides/prepaid-wallet-production);
211
- installing the SDK alone does not provide those dependencies.
212
-
213
- ## Commerce V1 / OACP
214
-
215
- The SDK includes a `commerce` resource for the Grantex Commerce V1 control
216
- plane and OACP live-pilot flow.
217
-
218
- ```typescript
219
- import { Grantex } from '@grantex/sdk';
220
-
221
- const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY! });
222
-
223
- // Public merchant publishing profile
224
- const profile = await grantex.commerce.getProfile({
225
- merchantId: 'mch_shopify_mgx0n6_22',
226
- });
227
- console.log(profile.merchant?.merchant_id);
228
-
229
- // Catalog grounding
230
- const catalog = await grantex.commerce.searchCatalog({
231
- merchant_id: 'mch_shopify_mgx0n6_22',
232
- query: 'shirt',
233
- limit: 5,
234
- });
235
-
236
- // Agent cart creation. Commerce write paths require Idempotency-Key.
237
- const cart = await grantex.commerce.createCart({
238
- idempotencyKey: crypto.randomUUID(),
239
- merchant_id: 'mch_shopify_mgx0n6_22',
240
- currency: 'INR',
241
- line_items: [
242
- { variant_id: String(catalog.items[0]?.['variant_id']), quantity: 1 },
243
- ],
244
- });
245
-
246
- // Consent request and Commerce Passport exchange
247
- const consent = await grantex.commerce.createConsentRequest({
248
- merchant_id: 'mch_shopify_mgx0n6_22',
249
- passport_type: 'checkout',
250
- max_amount: Number(cart.data['total_amount']),
251
- currency: 'INR',
252
- });
253
-
254
- // Redirect the buyer to consent.data['consent_url'], then exchange after
255
- // the consent request is granted.
256
- const passport = await grantex.commerce.exchangeConsentForPassport({
257
- consent_request_id: String(consent.data['consent_request_id']),
258
- });
259
-
260
- const payment = await grantex.commerce.createPaymentIntent({
261
- idempotencyKey: crypto.randomUUID(),
262
- merchant_id: 'mch_shopify_mgx0n6_22',
263
- cart_id: String(cart.data['cart_id']),
264
- passport_jwt: String(passport.data['passport_jwt']),
265
- amount_minor_units: Number(cart.data['total_amount']),
266
- currency: 'INR',
267
- provider_key: 'plural',
268
- });
269
-
270
- const checkout = await grantex.commerce.createCheckoutLink(
271
- String(payment.data['payment_intent_id']),
272
- {
273
- idempotencyKey: crypto.randomUUID(),
274
- passport_jwt: String(passport.data['passport_jwt']),
275
- success_url: 'https://buyer.example/success',
276
- cancel_url: 'https://buyer.example/cancel',
277
- },
278
- );
279
- ```
280
-
281
- Plural webhook intake is available at
282
- `https://api.grantex.dev/v1/webhooks/providers/plural`. Provider webhooks are
283
- normally called by the provider dashboard, not by application code. The SDK
284
- also exposes `grantex.commerce.getOpsHealth()` and
285
- `grantex.commerce.listProviderWebhookEvents()` for operator health checks.
286
-
287
- ## PKCE Support
288
-
289
- The SDK includes built-in PKCE (Proof Key for Code Exchange) support using the S256 method for secure authorization flows:
290
-
291
- ```typescript
292
- import { Grantex, generatePkce } from '@grantex/sdk';
293
-
294
- const grantex = new Grantex({ apiKey: 'YOUR_API_KEY' });
295
-
296
- // 1. Generate a PKCE challenge
297
- const pkce = generatePkce();
298
- // pkce.codeVerifier — random 43-char string (keep secret)
299
- // pkce.codeChallenge — SHA-256 hash of verifier (send to server)
300
- // pkce.codeChallengeMethod — 'S256'
301
-
302
- // 2. Pass the challenge when requesting authorization
303
- const { consentUrl } = await grantex.authorize({
304
- agentId: 'ag_01J...',
305
- userId: 'usr_01J...',
306
- scopes: ['files:read'],
307
- codeChallenge: pkce.codeChallenge,
308
- codeChallengeMethod: pkce.codeChallengeMethod,
309
- });
310
-
311
- // 3. Exchange the code with the verifier
312
- const token = await grantex.tokens.exchange({
313
- code: 'auth_code_from_redirect',
314
- agentId: 'ag_01J...',
315
- codeVerifier: pkce.codeVerifier,
316
- });
317
- ```
318
-
319
- ---
320
-
321
- ## API Reference
322
-
323
- ### Authorization
324
-
325
- #### `grantex.authorize(params)`
326
-
327
- Initiate the delegated authorization flow. Returns a consent URL to redirect the user to.
328
-
329
- ```typescript
330
- const request = await grantex.authorize({
331
- agentId: 'ag_01J...',
332
- userId: 'usr_01J...',
333
- scopes: ['files:read', 'email:send'],
334
- audience: 'https://api.example.com', // optional; becomes the JWT aud claim
335
- expiresIn: '24h', // optional
336
- redirectUri: 'https://...' // optional
337
- });
338
-
339
- console.log(request.consentUrl); // redirect user here
340
- console.log(request.authRequestId); // track the request
341
- console.log(request.expiresAt); // ISO 8601 timestamp
342
- ```
343
-
344
- **Returns**: `AuthorizationRequest`
345
-
346
- | Field | Type | Description |
347
- |-------|------|-------------|
348
- | `authRequestId` | `string` | Unique ID for this authorization request |
349
- | `consentUrl` | `string` | URL to redirect the user to for consent |
350
- | `agentId` | `string` | The agent requesting authorization |
351
- | `principalId` | `string` | The user being asked for consent |
352
- | `scopes` | `string[]` | Requested scopes |
353
- | `expiresAt` | `string` | When the request expires (ISO 8601) |
354
- | `status` | `string` | `'pending'`, `'approved'`, `'denied'`, or `'expired'` |
355
-
356
- ---
357
-
358
- ### Agents
359
-
360
- #### `grantex.agents.register(params)`
361
-
362
- Register a new AI agent.
363
-
364
- ```typescript
365
- const agent = await grantex.agents.register({
366
- name: 'Code Review Bot',
367
- description: 'Reviews pull requests and suggests improvements',
368
- scopes: ['repo:read', 'pr:comment'],
369
- });
370
- ```
371
-
372
- #### `grantex.agents.get(agentId)`
373
-
374
- ```typescript
375
- const agent = await grantex.agents.get('ag_01J...');
376
- ```
377
-
378
- #### `grantex.agents.list()`
379
-
380
- ```typescript
381
- const { agents } = await grantex.agents.list();
382
- ```
383
-
384
- #### `grantex.agents.update(agentId, params)`
385
-
386
- ```typescript
387
- const agent = await grantex.agents.update('ag_01J...', {
388
- name: 'Updated Name',
389
- scopes: ['repo:read', 'pr:comment', 'pr:approve'],
390
- });
391
- ```
392
-
393
- #### `grantex.agents.delete(agentId)`
394
-
395
- ```typescript
396
- await grantex.agents.delete('ag_01J...');
397
- ```
398
-
399
- ---
400
-
401
- ### Grants
402
-
403
- #### `grantex.grants.get(grantId)`
404
-
405
- ```typescript
406
- const grant = await grantex.grants.get('grnt_01J...');
407
- ```
408
-
409
- #### `grantex.grants.list(params?)`
410
-
411
- ```typescript
412
- const { grants } = await grantex.grants.list({
413
- agentId: 'ag_01J...', // optional filter
414
- principalId: 'usr_01J...', // optional filter
415
- status: 'active', // 'active' | 'revoked' | 'expired'
416
- page: 1,
417
- pageSize: 20,
418
- });
419
- ```
420
-
421
- #### `grantex.grants.revoke(grantId)`
422
-
423
- ```typescript
424
- await grantex.grants.revoke('grnt_01J...');
425
- ```
426
-
427
- #### `grantex.grants.delegate(params)`
428
-
429
- Create a delegated sub-agent grant (per [SPEC Section 9](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)).
430
-
431
- ```typescript
432
- const delegation = await grantex.grants.delegate({
433
- parentGrantToken: 'eyJhbG...',
434
- subAgentId: 'ag_02K...',
435
- scopes: ['files:read'], // must be subset of parent scopes
436
- expiresIn: '1h', // optional, cannot exceed parent
437
- });
438
-
439
- console.log(delegation.grantToken); // new JWT for the sub-agent
440
- console.log(delegation.grantId);
441
- ```
442
-
443
- #### `grantex.grants.verify(token)`
444
-
445
- Verify a grant token via the API (online verification with real-time revocation check).
446
-
447
- ```typescript
448
- const verified = await grantex.grants.verify('eyJhbG...');
449
- console.log(verified.principalId);
450
- console.log(verified.scopes);
451
- ```
452
-
453
- Throws `GrantexTokenError` when the token is inactive, revoked, expired, or otherwise unusable.
454
-
455
- ---
456
-
457
- ### Tokens
458
-
459
- #### `grantex.tokens.exchange(params)`
460
-
461
- Exchange an authorization code for a grant token. This is the standard way to obtain a grant token after the user approves the consent request.
462
-
463
- ```typescript
464
- const token = await grantex.tokens.exchange({
465
- code: 'auth_code_from_redirect', // from your redirect callback
466
- agentId: 'ag_01J...',
467
- });
468
-
469
- console.log(token.grantToken); // RS256-signed JWT — pass this to your agent
470
- console.log(token.grantId); // grant record ID
471
- console.log(token.scopes); // granted scopes
472
- console.log(token.expiresAt); // ISO 8601 expiry
473
- console.log(token.refreshToken); // for token refresh
474
- ```
475
-
476
- **Returns**: `ExchangeTokenResponse`
477
-
478
- | Field | Type | Description |
479
- |-------|------|-------------|
480
- | `grantToken` | `string` | Signed RS256 JWT — the agent's bearer credential |
481
- | `grantId` | `string` | Grant record ID |
482
- | `scopes` | `string[]` | Scopes the user approved |
483
- | `expiresAt` | `string` | Underlying grant expiry (ISO 8601) |
484
- | `refreshToken` | `string` | Refresh token for rotating credentials while the grant remains active |
485
-
486
- Refresh tokens are single-use and rotate on every accepted refresh. If a response is lost after commit, retry the same previous refresh token and idempotency key. The SDK retains an omitted key for five minutes in the current process; persist an explicit `idempotencyKey` with the old token when recovery must survive restart or failover. Grantex returns the already-rotated pair without extending `expiresAt`; after grant expiry, re-authorize.
487
-
488
- ---
489
-
490
- #### `grantex.tokens.verify(token)`
491
-
492
- Online token verification with revocation status.
493
-
494
- ```typescript
495
- const result = await grantex.tokens.verify('eyJhbG...');
496
- if (result.valid) {
497
- console.log(result.scopes); // ['files:read']
498
- console.log(result.principal); // 'usr_01J...'
499
- console.log(result.agent); // 'ag_01J...'
500
- console.log(result.grantId);
501
- console.log(result.expiresAt);
502
- }
503
- ```
504
-
505
- #### `grantex.tokens.revoke(tokenId)`
506
-
507
- Revoke a token by its JTI. Blocklisted in Redis immediately; all sub-delegated tokens are also invalidated.
508
-
509
- ```typescript
510
- await grantex.tokens.revoke('tok_01J...');
511
- ```
512
-
513
- ---
514
-
515
- ### Local Token Verification
516
-
517
- #### `verifyGrantToken(token, options)`
518
-
519
- Verify a grant token locally with RS256 signing keys retrieved from the published JWKS URL. A bounded process-level resolver cache reuses valid keys for each normalized JWKS URI; initial retrieval and key-rotation refreshes may require network access.
520
-
521
- ```typescript
522
- import { verifyGrantToken } from '@grantex/sdk';
523
-
524
- const grant = await verifyGrantToken('eyJhbG...', {
525
- jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
526
- issuer: 'https://grantex.dev', // optional when issuer differs from JWKS host
527
- requiredScopes: ['files:read'], // optional — rejects if missing
528
- audience: 'https://myapp.com', // optional — validates aud claim
529
- });
530
- ```
531
-
532
- If you call a deployment through a raw Cloud Run URL or another internal host, but the service signs tokens for a canonical public domain, pass `issuer` explicitly. Otherwise issuer validation will reject a valid token because the JWT `iss` claim will not match the transport host.
533
-
534
- **Returns**: `VerifiedGrant`
535
-
536
- | Field | Type | Description |
537
- |-------|------|-------------|
538
- | `tokenId` | `string` | Unique token ID (JWT `jti` claim) |
539
- | `grantId` | `string` | Grant record ID |
540
- | `principalId` | `string` | User who authorized the grant (`sub` claim) |
541
- | `agentDid` | `string` | Agent's DID (`agt` claim) |
542
- | `developerId` | `string` | Developer org ID (`dev` claim) |
543
- | `scopes` | `string[]` | Granted scopes (`scp` claim) |
544
- | `issuedAt` | `number` | Issued-at timestamp (seconds since epoch) |
545
- | `expiresAt` | `number` | Expiry timestamp (seconds since epoch) |
546
- | `parentAgentDid` | `string?` | Parent agent DID (delegation only) |
547
- | `parentGrantId` | `string?` | Parent grant ID (delegation only) |
548
- | `delegationDepth` | `number?` | Delegation depth (0 = root) |
549
-
550
- ---
551
-
552
- ### Audit
553
-
554
- #### `grantex.audit.log(params)`
555
-
556
- Log an auditable action taken by an agent.
557
-
558
- ```typescript
559
- const entry = await grantex.audit.log({
560
- agentId: 'ag_01J...',
561
- agentDid: 'did:grantex:ag_01J...',
562
- grantId: 'grnt_01J...',
563
- principalId: 'usr_01J...',
564
- action: 'email:send',
565
- metadata: { to: 'user@example.com', subject: 'Hello' },
566
- status: 'success', // 'success' | 'failure' | 'blocked'
567
- });
568
- ```
569
-
570
- #### `grantex.audit.list(params?)`
571
-
572
- ```typescript
573
- const { entries } = await grantex.audit.list({
574
- agentId: 'ag_01J...',
575
- action: 'email:send',
576
- since: '2026-01-01T00:00:00Z',
577
- until: '2026-02-28T23:59:59Z',
578
- page: 1,
579
- pageSize: 50,
580
- });
581
- ```
582
-
583
- #### `grantex.audit.get(entryId)`
584
-
585
- ```typescript
586
- const entry = await grantex.audit.get('alog_01J...');
587
- console.log(entry.hash); // SHA-256 hash for tamper evidence
588
- console.log(entry.prevHash); // previous entry hash (chain integrity)
589
- ```
590
-
591
- ---
592
-
593
- ### Webhooks
594
-
595
- #### `grantex.webhooks.create(params)`
596
-
597
- ```typescript
598
- const webhook = await grantex.webhooks.create({
599
- url: 'https://myapp.com/webhooks/grantex',
600
- events: ['grant.created', 'grant.revoked', 'token.issued'],
601
- });
602
- console.log(webhook.secret); // HMAC secret for signature verification
603
- ```
604
-
605
- #### `grantex.webhooks.list()`
606
-
607
- ```typescript
608
- const { webhooks } = await grantex.webhooks.list();
609
- ```
610
-
611
- #### `grantex.webhooks.delete(webhookId)`
612
-
613
- ```typescript
614
- await grantex.webhooks.delete('wh_01J...');
615
- ```
616
-
617
- #### Webhook Signature Verification
618
-
619
- ```typescript
620
- import { verifyWebhookSignature } from '@grantex/sdk';
621
-
622
- // In your webhook handler
623
- verifyWebhookSignature(requestBody, signatureHeader, webhookSecret);
624
- ```
625
-
626
- ---
627
-
628
- ### Policies
629
-
630
- Define fine-grained access control rules for agents.
631
-
632
- #### `grantex.policies.create(params)`
633
-
634
- ```typescript
635
- const policy = await grantex.policies.create({
636
- name: 'Block after hours',
637
- effect: 'deny',
638
- priority: 10,
639
- scopes: ['email:send'],
640
- timeOfDayStart: '18:00',
641
- timeOfDayEnd: '08:00',
642
- });
643
- ```
644
-
645
- #### `grantex.policies.list()`
646
-
647
- ```typescript
648
- const { policies, total } = await grantex.policies.list();
649
- ```
650
-
651
- #### `grantex.policies.get(policyId)` / `update(policyId, params)` / `delete(policyId)`
652
-
653
- ```typescript
654
- const policy = await grantex.policies.get('pol_01J...');
655
-
656
- await grantex.policies.update('pol_01J...', { effect: 'allow' });
657
-
658
- await grantex.policies.delete('pol_01J...');
659
- ```
660
-
661
- ---
662
-
663
- ### Compliance
664
-
665
- #### `grantex.compliance.getSummary(params?)`
666
-
667
- ```typescript
668
- const summary = await grantex.compliance.getSummary({
669
- since: '2026-01-01T00:00:00Z',
670
- until: '2026-02-28T23:59:59Z',
671
- });
672
- console.log(summary.agents); // { total, active, suspended, revoked }
673
- console.log(summary.grants); // { total, active, revoked, expired }
674
- console.log(summary.auditEntries); // { total, success, failure, blocked }
675
- ```
676
-
677
- #### `grantex.compliance.exportGrants(params?)`
678
-
679
- ```typescript
680
- const { grants, total } = await grantex.compliance.exportGrants({
681
- status: 'active',
682
- });
683
- ```
684
-
685
- #### `grantex.compliance.exportAudit(params?)`
686
-
687
- ```typescript
688
- const { entries, total } = await grantex.compliance.exportAudit({
689
- since: '2026-01-01T00:00:00Z',
690
- agentId: 'ag_01J...',
691
- });
692
- ```
693
-
694
- #### `grantex.compliance.evidencePack(params?)`
695
-
696
- Generate a full SOC 2 / GDPR evidence pack with audit chain integrity verification.
697
-
698
- ```typescript
699
- const pack = await grantex.compliance.evidencePack({
700
- framework: 'soc2', // 'soc2' | 'gdpr' | 'all'
701
- since: '2026-01-01T00:00:00Z',
702
- });
703
-
704
- console.log(pack.chainIntegrity.valid); // true
705
- console.log(pack.chainIntegrity.checkedEntries); // 1042
706
- console.log(pack.summary);
707
- console.log(pack.grants);
708
- console.log(pack.auditEntries);
709
- console.log(pack.policies);
710
- ```
711
-
712
- ---
713
-
714
- ### Anomaly Detection
715
-
716
- #### `grantex.anomalies.detect()`
717
-
718
- Run anomaly detection across all agents.
719
-
720
- ```typescript
721
- const { anomalies, total } = await grantex.anomalies.detect();
722
- // anomaly types: 'rate_spike' | 'high_failure_rate' | 'new_principal' | 'off_hours_activity'
723
- ```
724
-
725
- #### `grantex.anomalies.list(params?)`
726
-
727
- ```typescript
728
- const { anomalies } = await grantex.anomalies.list({
729
- unacknowledged: true, // only open anomalies
730
- });
731
- ```
732
-
733
- #### `grantex.anomalies.acknowledge(anomalyId)`
734
-
735
- ```typescript
736
- const anomaly = await grantex.anomalies.acknowledge('anom_01J...');
737
- ```
738
-
739
- ---
740
-
741
- ### Billing
742
-
743
- #### `grantex.billing.getSubscription()`
744
-
745
- ```typescript
746
- const sub = await grantex.billing.getSubscription();
747
- console.log(sub.plan); // 'free' | 'pro' | 'enterprise'
748
- console.log(sub.status); // 'active' | 'past_due' | 'canceled'
749
- console.log(sub.currentPeriodEnd); // ISO 8601 or null
750
- ```
751
-
752
- #### `grantex.billing.createCheckout(params)`
753
-
754
- ```typescript
755
- const { checkoutUrl } = await grantex.billing.createCheckout({
756
- plan: 'pro',
757
- successUrl: 'https://myapp.com/billing/success',
758
- cancelUrl: 'https://myapp.com/billing/cancel',
759
- });
760
- // Redirect user to checkoutUrl
761
- ```
762
-
763
- #### `grantex.billing.createPortal(params)`
764
-
765
- ```typescript
766
- const { portalUrl } = await grantex.billing.createPortal({
767
- returnUrl: 'https://myapp.com/settings',
768
- });
769
- ```
770
-
771
- ---
772
-
773
- ### SCIM 2.0 Provisioning
774
-
775
- Sync users from your identity provider.
776
-
777
- #### Token Management
778
-
779
- ```typescript
780
- // Create a SCIM bearer token
781
- const { token, id, label } = await grantex.scim.createToken({
782
- label: 'Okta SCIM integration',
783
- });
784
- // token is returned once — store it securely
785
-
786
- const { tokens } = await grantex.scim.listTokens();
787
-
788
- await grantex.scim.revokeToken('scimtok_01J...');
789
- ```
790
-
791
- #### User Operations
792
-
793
- ```typescript
794
- // List provisioned users
795
- const { Resources, totalResults } = await grantex.scim.listUsers({
796
- startIndex: 1,
797
- count: 100,
798
- });
799
-
800
- // Create a user
801
- const user = await grantex.scim.createUser({
802
- userName: 'alice@example.com',
803
- displayName: 'Alice',
804
- emails: [{ value: 'alice@example.com', primary: true }],
805
- });
806
-
807
- // Get / Replace / Patch / Delete
808
- const user = await grantex.scim.getUser('scimusr_01J...');
809
-
810
- await grantex.scim.replaceUser('scimusr_01J...', { userName: 'alice@new.com' });
811
-
812
- await grantex.scim.updateUser('scimusr_01J...', [
813
- { op: 'replace', path: 'active', value: false },
814
- ]);
815
-
816
- await grantex.scim.deleteUser('scimusr_01J...');
817
- ```
818
-
819
- ---
820
-
821
- ### SSO (OIDC)
822
-
823
- #### `grantex.sso.createConfig(params)`
824
-
825
- ```typescript
826
- const config = await grantex.sso.createConfig({
827
- issuerUrl: 'https://accounts.google.com',
828
- clientId: 'xxx.apps.googleusercontent.com',
829
- clientSecret: 'GOCSPX-...',
830
- redirectUri: 'https://myapp.com/auth/callback',
831
- });
832
- ```
833
-
834
- #### `grantex.sso.getConfig()` / `deleteConfig()`
835
-
836
- ```typescript
837
- const config = await grantex.sso.getConfig();
838
- await grantex.sso.deleteConfig();
839
- ```
840
-
841
- #### `grantex.sso.getLoginUrl(org)`
842
-
843
- ```typescript
844
- const { authorizeUrl } = await grantex.sso.getLoginUrl('dev_01J...');
845
- // Redirect user to authorizeUrl
846
- ```
847
-
848
- #### `grantex.sso.handleCallback(code, state)`
849
-
850
- ```typescript
851
- const { email, name, sub, developerId } = await grantex.sso.handleCallback(code, state);
852
- ```
853
-
854
- ---
855
-
856
- ## Error Handling
857
-
858
- All errors extend `GrantexError`:
859
-
860
- ```typescript
861
- import {
862
- GrantexError, // base class
863
- GrantexApiError, // API returned an error (has statusCode, body, requestId)
864
- GrantexAuthError, // 401/403 — invalid or missing API key
865
- GrantexTokenError, // token verification failed (invalid signature, expired, etc.)
866
- GrantexNetworkError, // network failure (timeout, DNS, connection refused)
867
- } from '@grantex/sdk';
868
-
869
- try {
870
- await grantex.agents.get('ag_invalid');
871
- } catch (err) {
872
- if (err instanceof GrantexAuthError) {
873
- console.error('Auth failed:', err.statusCode); // 401 or 403
874
- console.error('Request ID:', err.requestId);
875
- } else if (err instanceof GrantexApiError) {
876
- console.error('API error:', err.statusCode, err.body);
877
- } else if (err instanceof GrantexNetworkError) {
878
- console.error('Network error:', err.message, err.cause);
879
- }
880
- }
881
- ```
882
-
883
- ---
884
-
885
- ## Requirements
886
-
887
- - Node.js 18+
888
- - ESM (`"type": "module"` in your package.json, or use dynamic `import()`)
889
-
890
- ## Links
891
-
892
- - [GitHub](https://github.com/mishrasanjeev/grantex)
893
- - [Protocol Specification](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)
894
- - [Python SDK](https://pypi.org/project/grantex/)
895
- - [API Reference](https://api.grantex.dev/.well-known/jwks.json)
896
-
897
- ## Scope Enforcement (v0.3.1)
898
-
899
- Enforce tool-level permissions on **any connector** — define your own manifests or use the 53 pre-built ones.
900
-
901
- ```typescript
902
- import { Grantex, ToolManifest, Permission } from '@grantex/sdk';
903
-
904
- const grantex = new Grantex({ apiKey: 'gx_...' });
905
-
906
- // Define a manifest for any connector — no dependency on Grantex to add support
907
- grantex.loadManifest(new ToolManifest({
908
- connector: 'my-crm',
909
- tools: { search: Permission.READ, create_deal: Permission.WRITE, delete_account: Permission.DELETE },
910
- }));
911
-
912
- const result = await grantex.enforce({ grantToken: token, connector: 'my-crm', tool: 'delete_account' });
913
- // result.allowed = false — "write scope does not permit delete operations"
914
- ```
915
-
916
- **Features:**
917
- - `enforce()` — verify JWT + check tool permission via manifest, <1ms
918
- - `wrapTool()` — auto-enforce on LangChain tools
919
- - `enforceMiddleware()` — Express/Fastify HTTP middleware
920
- - Define custom manifests for any connector: inline, from JSON, or auto-generated via CLI
921
- - 53 pre-built manifests included (Salesforce, HubSpot, Jira, Stripe, SAP, S3, and 47 more)
922
- - Permission hierarchy: `admin > delete > write > read`
923
- - Permissive mode for migration (`enforceMode: 'permissive'`)
924
-
925
- [Full Guide](https://docs.grantex.dev/guides/scope-enforcement) | [API Reference](https://docs.grantex.dev/sdks/typescript/enforce)
926
-
927
- ## Grantex Ecosystem
928
-
929
- | Package | Description |
930
- |---|---|
931
- | [`grantex`](https://pypi.org/project/grantex/) | Python SDK |
932
- | [`@grantex/langchain`](https://www.npmjs.com/package/@grantex/langchain) | LangChain integration |
933
- | [`@grantex/autogen`](https://www.npmjs.com/package/@grantex/autogen) | AutoGen integration |
934
- | [`@grantex/vercel-ai`](https://www.npmjs.com/package/@grantex/vercel-ai) | Vercel AI SDK integration |
935
- | [`grantex-crewai`](https://pypi.org/project/grantex-crewai/) | CrewAI integration |
936
- | [`grantex-openai-agents`](https://pypi.org/project/grantex-openai-agents/) | OpenAI Agents SDK integration |
937
- | [`grantex-adk`](https://pypi.org/project/grantex-adk/) | Google ADK integration |
938
- | [`@grantex/mcp`](https://www.npmjs.com/package/@grantex/mcp) | MCP server for Claude Desktop / Cursor / Windsurf |
939
- | [`@grantex/cli`](https://www.npmjs.com/package/@grantex/cli) | Command-line tool |
940
-
941
- ## License
942
-
943
- [Apache 2.0](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)
944
-
945
- ## Ownership
946
-
947
- Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).
1
+ # @grantex/sdk
2
+
3
+ TypeScript SDK for the [Grantex](https://grantex.dev) delegated authorization protocol — OAuth 2.0 for AI agents.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@grantex/sdk)](https://www.npmjs.com/package/@grantex/sdk)
6
+ [![License](https://img.shields.io/npm/l/@grantex/sdk)](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)
7
+
8
+ > **[Homepage](https://grantex.dev)** | **[Docs](https://docs.grantex.dev)** | **[API Reference](https://docs.grantex.dev/api-reference)** | **[Sign Up Free](https://grantex.dev/dashboard/signup)** | **[GitHub](https://github.com/mishrasanjeev/grantex)**
9
+
10
+ ## Installation
11
+
12
+ **Version 0.7.0:** adds hosted passkey enrollment sessions and
13
+ account-level irregularity response policy methods. The server must enable the
14
+ corresponding feature flags; this SDK does not register a passkey without an
15
+ authenticated customer handoff. Version 0.6.0 added wallet clients that preserve the
16
+ server's Base USDC `evmPayment` payload and expose `reconcileReservation()` for
17
+ finalized settlement/expiry. Use `@grantex/x402@0.4.0` or later for
18
+ the automatic 402/payment/retry flow. Confirm the published SDK version in
19
+ [Release Status](https://docs.grantex.dev/release-status) before installing.
20
+ See [Base custody setup](https://docs.grantex.dev/guides/base-usdc-custody).
21
+
22
+ ```bash
23
+ npm install @grantex/sdk@0.7.0
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { Grantex, verifyGrantToken } from '@grantex/sdk';
30
+
31
+ const grantex = new Grantex({ apiKey: 'YOUR_API_KEY' });
32
+
33
+ // 1. Register an agent
34
+ const agent = await grantex.agents.register({
35
+ name: 'Email Assistant',
36
+ description: 'Reads and sends email on behalf of users',
37
+ scopes: ['email:read', 'email:send'],
38
+ });
39
+
40
+ // 2. Request authorization
41
+ const { consentUrl } = await grantex.authorize({
42
+ agentId: agent.id,
43
+ userId: 'usr_01J...',
44
+ scopes: ['email:read', 'email:send'],
45
+ });
46
+ // Redirect the user to consentUrl — they approve in plain language
47
+
48
+ // 3. Exchange authorization code for a grant token
49
+ // (your redirect callback receives the `code` after user approves)
50
+ const token = await grantex.tokens.exchange({ code, agentId: agent.id });
51
+ console.log(token.grantToken); // RS256-signed JWT
52
+ console.log(token.scopes); // ['email:read', 'email:send']
53
+ console.log(token.grantId); // 'grnt_01J...'
54
+
55
+ // 4. Verify locally using keys retrieved from the issuer's JWKS
56
+ const grant = await verifyGrantToken(token.grantToken, {
57
+ jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
58
+ });
59
+ console.log(grant.principalId); // 'usr_01J...'
60
+
61
+ // 5. Revoke when done
62
+ await grantex.tokens.revoke(grant.tokenId);
63
+ ```
64
+
65
+ ## Configuration
66
+
67
+ ```typescript
68
+ const grantex = new Grantex({
69
+ apiKey: 'gx_....', // or set GRANTEX_API_KEY env var
70
+ baseUrl: 'https://api.grantex.dev', // default
71
+ issuer: 'https://grantex.dev', // optional when token issuer differs from API host
72
+ jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', // optional JWKS override
73
+ timeout: 30000, // request timeout in ms (default: 30s)
74
+ });
75
+ ```
76
+
77
+ | Option | Type | Default | Description |
78
+ |--------|------|---------|-------------|
79
+ | `apiKey` | `string` | `process.env.GRANTEX_API_KEY` | API key for authentication |
80
+ | `baseUrl` | `string` | `https://api.grantex.dev` | Base URL of the Grantex API |
81
+ | `issuer` | `string` | derived from `jwksUri` | Expected JWT issuer for local signature verification |
82
+ | `jwksUri` | `string` | `${baseUrl}/.well-known/jwks.json` | URL from which the verifier retrieves signing keys |
83
+ | `timeout` | `number` | `30000` | Request timeout in milliseconds |
84
+
85
+ ## OAuth Agent Profile Client
86
+
87
+ `OAuthAgentClient` implements the client role in the prepared
88
+ `draft-mishra-oauth-agent-grants-03` profile. It discovers and validates RFC
89
+ 8414 metadata, creates an ES256 DPoP key by default, uses PAR and PKCE S256,
90
+ validates `state` and the RFC 9207 `iss` response parameter, rotates refresh
91
+ tokens, performs same-resource RFC 8693 attenuation, and creates DPoP proofs
92
+ for protected-resource requests.
93
+
94
+ ```typescript
95
+ import { OAuthAgentClient } from '@grantex/sdk';
96
+
97
+ const client = await OAuthAgentClient.create({
98
+ issuer: 'https://grantex.dev',
99
+ clientId: 'ag_01J...',
100
+ redirectUri: 'https://agent.example/callback',
101
+ resource: 'https://api.example/resource',
102
+ });
103
+
104
+ const pending = await client.beginAuthorization({
105
+ scopes: ['grantex.resource.read'],
106
+ principalHint: 'principal@example.com',
107
+ });
108
+
109
+ // Redirect the Principal to pending.authorizationUrl. In the callback:
110
+ const tokens = await client.completeAuthorization(callbackUrl);
111
+
112
+ const response = await client.fetch(
113
+ 'https://api.example/resource',
114
+ tokens.access_token,
115
+ );
116
+
117
+ const narrower = await client.attenuate(tokens.access_token, [
118
+ 'grantex.resource.read',
119
+ ]);
120
+ const refreshAttempt = crypto.randomUUID();
121
+ const rotated = await client.refresh(tokens.refresh_token!, {
122
+ idempotencyKey: refreshAttempt,
123
+ });
124
+ await client.revoke(rotated.refresh_token!, 'refresh_token');
125
+ ```
126
+
127
+ Persist the idempotency key with the old refresh token when lost-response
128
+ recovery must survive a caller restart. Repeating both within 300 seconds uses
129
+ a fresh DPoP proof and returns the exact committed token values with a
130
+ recalculated, non-extended `expires_in`. A
131
+ different key or DPoP identity is treated as refresh-token reuse and revokes
132
+ the token family. When no key is supplied, the client generates and retains one
133
+ for the old refresh token for five minutes in the current process.
134
+
135
+ Persist the generated key securely if an instance must survive process
136
+ restarts. Supply the matching `privateKey` and `publicJwk` to `create`; both are
137
+ required together. `principalHint` is optional account-discovery input and is
138
+ not proof of the Principal's identity; live approval still requires the
139
+ authorization server's passkey authentication. Plain HTTP endpoints are rejected unless
140
+ `allowInsecureLoopback` is enabled for local loopback testing. Revision `-02`
141
+ of the draft family is published as an active individual Internet-Draft;
142
+ revision `-03` is the working candidate implemented here. Neither is an
143
+ IETF-endorsed or independently certified standard.
144
+
145
+ ## Agent prepaid wallets (SDK 0.5+)
146
+
147
+ `PrepaidWalletAgentClient` uses an `OAuthAgentClient` and DPoP access token to
148
+ list assigned wallets, reserve payments, and request threshold reloads.
149
+ `PrincipalPrepaidWalletClient` uses a short-lived principal-session token to
150
+ create and fund wallets, assign safe-default policy, manage layered spend
151
+ policies and exact payment approvals, approve reloads, inspect activity, and
152
+ block an assignment, wallet, or all wallets for one agent.
153
+ `DeveloperPrepaidWalletPolicyClient` manages tenant-level policy with the
154
+ developer API key.
155
+
156
+ The access token must include `wallet:spend` and each action scope used in a
157
+ payment (for example `weather:read`). Agent wallet listings intentionally omit
158
+ custody-provider IDs, wallet addresses, principal IDs, and wallet metadata.
159
+
160
+ ```typescript
161
+ import {
162
+ PrepaidWalletAgentClient,
163
+ PrincipalPrepaidWalletClient,
164
+ } from '@grantex/sdk';
165
+
166
+ const agentWallets = new PrepaidWalletAgentClient({
167
+ oauthClient,
168
+ accessToken,
169
+ });
170
+
171
+ const principalWallets = new PrincipalPrepaidWalletClient({
172
+ baseUrl: 'https://api.grantex.dev',
173
+ sessionToken,
174
+ });
175
+
176
+ await principalWallets.createSpendPolicy({
177
+ name: 'Shared research budget',
178
+ scopeType: 'group',
179
+ scopeId: 'research-agents',
180
+ effect: 'limit',
181
+ maxAmount: '1000000',
182
+ windowType: 'month',
183
+ onExceed: 'require_approval',
184
+ purposes: ['research'],
185
+ });
186
+
187
+ const authorization = await agentWallets.authorizePayment({
188
+ amount: '1000',
189
+ asset: 'USDC',
190
+ network: 'grantex:prepaid',
191
+ recipient: 'merchant:weather-api',
192
+ resource: 'https://merchant.example/weather',
193
+ scope: 'weather:read',
194
+ merchantId: 'merchant:weather-api',
195
+ purpose: 'research',
196
+ projectId: 'climate-2026',
197
+ costCenter: 'engineering',
198
+ maxTimeoutSeconds: 120,
199
+ idempotencyKey: crypto.randomUUID(),
200
+ });
201
+ ```
202
+
203
+ `authorizePayment` returns either a signed reservation or an
204
+ `approval_required` response. After the principal approves that exact request,
205
+ retry with its `approvalRequestId`, the same wallet, idempotency key, and all
206
+ original payment fields. Approval is short-lived and single-use.
207
+
208
+ Amounts are atomic-unit integer strings. Layered policy and exact approval are
209
+ available in `@grantex/sdk` 0.5.0 and later.
210
+
211
+ Self-hosted wallet deployments must also provide correct public resource
212
+ routing, migrations `091` and `092`, durable notification delivery, merchant-side
213
+ idempotency, and any external custody/provider integration. See [Prepaid Wallet
214
+ Production
215
+ Readiness](https://docs.grantex.dev/guides/prepaid-wallet-production);
216
+ installing the SDK alone does not provide those dependencies.
217
+
218
+ ## Commerce V1 / OACP
219
+
220
+ The SDK includes a `commerce` resource for the Grantex Commerce V1 control
221
+ plane and OACP live-pilot flow.
222
+
223
+ ```typescript
224
+ import { Grantex } from '@grantex/sdk';
225
+
226
+ const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY! });
227
+
228
+ // Public merchant publishing profile
229
+ const profile = await grantex.commerce.getProfile({
230
+ merchantId: 'mch_shopify_mgx0n6_22',
231
+ });
232
+ console.log(profile.merchant?.merchant_id);
233
+
234
+ // Catalog grounding
235
+ const catalog = await grantex.commerce.searchCatalog({
236
+ merchant_id: 'mch_shopify_mgx0n6_22',
237
+ query: 'shirt',
238
+ limit: 5,
239
+ });
240
+
241
+ // Agent cart creation. Commerce write paths require Idempotency-Key.
242
+ const cart = await grantex.commerce.createCart({
243
+ idempotencyKey: crypto.randomUUID(),
244
+ merchant_id: 'mch_shopify_mgx0n6_22',
245
+ currency: 'INR',
246
+ line_items: [
247
+ { variant_id: String(catalog.items[0]?.['variant_id']), quantity: 1 },
248
+ ],
249
+ });
250
+
251
+ // Consent request and Commerce Passport exchange
252
+ const consent = await grantex.commerce.createConsentRequest({
253
+ merchant_id: 'mch_shopify_mgx0n6_22',
254
+ passport_type: 'checkout',
255
+ max_amount: Number(cart.data['total_amount']),
256
+ currency: 'INR',
257
+ });
258
+
259
+ // Redirect the buyer to consent.data['consent_url'], then exchange after
260
+ // the consent request is granted.
261
+ const passport = await grantex.commerce.exchangeConsentForPassport({
262
+ consent_request_id: String(consent.data['consent_request_id']),
263
+ });
264
+
265
+ const payment = await grantex.commerce.createPaymentIntent({
266
+ idempotencyKey: crypto.randomUUID(),
267
+ merchant_id: 'mch_shopify_mgx0n6_22',
268
+ cart_id: String(cart.data['cart_id']),
269
+ passport_jwt: String(passport.data['passport_jwt']),
270
+ amount_minor_units: Number(cart.data['total_amount']),
271
+ currency: 'INR',
272
+ provider_key: 'plural',
273
+ });
274
+
275
+ const checkout = await grantex.commerce.createCheckoutLink(
276
+ String(payment.data['payment_intent_id']),
277
+ {
278
+ idempotencyKey: crypto.randomUUID(),
279
+ passport_jwt: String(passport.data['passport_jwt']),
280
+ success_url: 'https://buyer.example/success',
281
+ cancel_url: 'https://buyer.example/cancel',
282
+ },
283
+ );
284
+ ```
285
+
286
+ Plural webhook intake is available at
287
+ `https://api.grantex.dev/v1/webhooks/providers/plural`. Provider webhooks are
288
+ normally called by the provider dashboard, not by application code. The SDK
289
+ also exposes `grantex.commerce.getOpsHealth()` and
290
+ `grantex.commerce.listProviderWebhookEvents()` for operator health checks.
291
+
292
+ ## PKCE Support
293
+
294
+ The SDK includes built-in PKCE (Proof Key for Code Exchange) support using the S256 method for secure authorization flows:
295
+
296
+ ```typescript
297
+ import { Grantex, generatePkce } from '@grantex/sdk';
298
+
299
+ const grantex = new Grantex({ apiKey: 'YOUR_API_KEY' });
300
+
301
+ // 1. Generate a PKCE challenge
302
+ const pkce = generatePkce();
303
+ // pkce.codeVerifier — random 43-char string (keep secret)
304
+ // pkce.codeChallenge — SHA-256 hash of verifier (send to server)
305
+ // pkce.codeChallengeMethod — 'S256'
306
+
307
+ // 2. Pass the challenge when requesting authorization
308
+ const { consentUrl } = await grantex.authorize({
309
+ agentId: 'ag_01J...',
310
+ userId: 'usr_01J...',
311
+ scopes: ['files:read'],
312
+ codeChallenge: pkce.codeChallenge,
313
+ codeChallengeMethod: pkce.codeChallengeMethod,
314
+ });
315
+
316
+ // 3. Exchange the code with the verifier
317
+ const token = await grantex.tokens.exchange({
318
+ code: 'auth_code_from_redirect',
319
+ agentId: 'ag_01J...',
320
+ codeVerifier: pkce.codeVerifier,
321
+ });
322
+ ```
323
+
324
+ ---
325
+
326
+ ## API Reference
327
+
328
+ ### Authorization
329
+
330
+ #### `grantex.authorize(params)`
331
+
332
+ Initiate the delegated authorization flow. Returns a consent URL to redirect the user to.
333
+
334
+ ```typescript
335
+ const request = await grantex.authorize({
336
+ agentId: 'ag_01J...',
337
+ userId: 'usr_01J...',
338
+ scopes: ['files:read', 'email:send'],
339
+ audience: 'https://api.example.com', // optional; becomes the JWT aud claim
340
+ expiresIn: '24h', // optional
341
+ redirectUri: 'https://...' // optional
342
+ });
343
+
344
+ console.log(request.consentUrl); // redirect user here
345
+ console.log(request.authRequestId); // track the request
346
+ console.log(request.expiresAt); // ISO 8601 timestamp
347
+ ```
348
+
349
+ **Returns**: `AuthorizationRequest`
350
+
351
+ | Field | Type | Description |
352
+ |-------|------|-------------|
353
+ | `authRequestId` | `string` | Unique ID for this authorization request |
354
+ | `consentUrl` | `string` | URL to redirect the user to for consent |
355
+ | `agentId` | `string` | The agent requesting authorization |
356
+ | `principalId` | `string` | The user being asked for consent |
357
+ | `scopes` | `string[]` | Requested scopes |
358
+ | `expiresAt` | `string` | When the request expires (ISO 8601) |
359
+ | `status` | `string` | `'pending'`, `'approved'`, `'denied'`, or `'expired'` |
360
+
361
+ ---
362
+
363
+ ### Agents
364
+
365
+ #### `grantex.agents.register(params)`
366
+
367
+ Register a new AI agent.
368
+
369
+ ```typescript
370
+ const agent = await grantex.agents.register({
371
+ name: 'Code Review Bot',
372
+ description: 'Reviews pull requests and suggests improvements',
373
+ scopes: ['repo:read', 'pr:comment'],
374
+ });
375
+ ```
376
+
377
+ #### `grantex.agents.get(agentId)`
378
+
379
+ ```typescript
380
+ const agent = await grantex.agents.get('ag_01J...');
381
+ ```
382
+
383
+ #### `grantex.agents.list()`
384
+
385
+ ```typescript
386
+ const { agents } = await grantex.agents.list();
387
+ ```
388
+
389
+ #### `grantex.agents.update(agentId, params)`
390
+
391
+ ```typescript
392
+ const agent = await grantex.agents.update('ag_01J...', {
393
+ name: 'Updated Name',
394
+ scopes: ['repo:read', 'pr:comment', 'pr:approve'],
395
+ });
396
+ ```
397
+
398
+ #### `grantex.agents.delete(agentId)`
399
+
400
+ ```typescript
401
+ await grantex.agents.delete('ag_01J...');
402
+ ```
403
+
404
+ ---
405
+
406
+ ### Grants
407
+
408
+ #### `grantex.grants.get(grantId)`
409
+
410
+ ```typescript
411
+ const grant = await grantex.grants.get('grnt_01J...');
412
+ ```
413
+
414
+ #### `grantex.grants.list(params?)`
415
+
416
+ ```typescript
417
+ const { grants } = await grantex.grants.list({
418
+ agentId: 'ag_01J...', // optional filter
419
+ principalId: 'usr_01J...', // optional filter
420
+ status: 'active', // 'active' | 'revoked' | 'expired'
421
+ page: 1,
422
+ pageSize: 20,
423
+ });
424
+ ```
425
+
426
+ #### `grantex.grants.revoke(grantId)`
427
+
428
+ ```typescript
429
+ await grantex.grants.revoke('grnt_01J...');
430
+ ```
431
+
432
+ #### `grantex.grants.delegate(params)`
433
+
434
+ Create a delegated sub-agent grant (per [SPEC Section 9](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)).
435
+
436
+ ```typescript
437
+ const delegation = await grantex.grants.delegate({
438
+ parentGrantToken: 'eyJhbG...',
439
+ subAgentId: 'ag_02K...',
440
+ scopes: ['files:read'], // must be subset of parent scopes
441
+ expiresIn: '1h', // optional, cannot exceed parent
442
+ });
443
+
444
+ console.log(delegation.grantToken); // new JWT for the sub-agent
445
+ console.log(delegation.grantId);
446
+ ```
447
+
448
+ #### `grantex.grants.verify(token)`
449
+
450
+ Verify a grant token via the API (online verification with real-time revocation check).
451
+
452
+ ```typescript
453
+ const verified = await grantex.grants.verify('eyJhbG...');
454
+ console.log(verified.principalId);
455
+ console.log(verified.scopes);
456
+ ```
457
+
458
+ Throws `GrantexTokenError` when the token is inactive, revoked, expired, or otherwise unusable.
459
+
460
+ ---
461
+
462
+ ### Tokens
463
+
464
+ #### `grantex.tokens.exchange(params)`
465
+
466
+ Exchange an authorization code for a grant token. This is the standard way to obtain a grant token after the user approves the consent request.
467
+
468
+ ```typescript
469
+ const token = await grantex.tokens.exchange({
470
+ code: 'auth_code_from_redirect', // from your redirect callback
471
+ agentId: 'ag_01J...',
472
+ });
473
+
474
+ console.log(token.grantToken); // RS256-signed JWT — pass this to your agent
475
+ console.log(token.grantId); // grant record ID
476
+ console.log(token.scopes); // granted scopes
477
+ console.log(token.expiresAt); // ISO 8601 expiry
478
+ console.log(token.refreshToken); // for token refresh
479
+ ```
480
+
481
+ **Returns**: `ExchangeTokenResponse`
482
+
483
+ | Field | Type | Description |
484
+ |-------|------|-------------|
485
+ | `grantToken` | `string` | Signed RS256 JWT — the agent's bearer credential |
486
+ | `grantId` | `string` | Grant record ID |
487
+ | `scopes` | `string[]` | Scopes the user approved |
488
+ | `expiresAt` | `string` | Underlying grant expiry (ISO 8601) |
489
+ | `refreshToken` | `string` | Refresh token for rotating credentials while the grant remains active |
490
+
491
+ Refresh tokens are single-use and rotate on every accepted refresh. If a response is lost after commit, retry the same previous refresh token and idempotency key. The SDK retains an omitted key for five minutes in the current process; persist an explicit `idempotencyKey` with the old token when recovery must survive restart or failover. Grantex returns the already-rotated pair without extending `expiresAt`; after grant expiry, re-authorize.
492
+
493
+ ---
494
+
495
+ #### `grantex.tokens.verify(token)`
496
+
497
+ Online token verification with revocation status.
498
+
499
+ ```typescript
500
+ const result = await grantex.tokens.verify('eyJhbG...');
501
+ if (result.valid) {
502
+ console.log(result.scopes); // ['files:read']
503
+ console.log(result.principal); // 'usr_01J...'
504
+ console.log(result.agent); // 'ag_01J...'
505
+ console.log(result.grantId);
506
+ console.log(result.expiresAt);
507
+ }
508
+ ```
509
+
510
+ #### `grantex.tokens.revoke(tokenId)`
511
+
512
+ Revoke a token by its JTI. Blocklisted in Redis immediately; all sub-delegated tokens are also invalidated.
513
+
514
+ ```typescript
515
+ await grantex.tokens.revoke('tok_01J...');
516
+ ```
517
+
518
+ ---
519
+
520
+ ### Local Token Verification
521
+
522
+ #### `verifyGrantToken(token, options)`
523
+
524
+ Verify a grant token locally with RS256 signing keys retrieved from the published JWKS URL. A bounded process-level resolver cache reuses valid keys for each normalized JWKS URI; initial retrieval and key-rotation refreshes may require network access.
525
+
526
+ ```typescript
527
+ import { verifyGrantToken } from '@grantex/sdk';
528
+
529
+ const grant = await verifyGrantToken('eyJhbG...', {
530
+ jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
531
+ issuer: 'https://grantex.dev', // optional when issuer differs from JWKS host
532
+ requiredScopes: ['files:read'], // optional — rejects if missing
533
+ audience: 'https://myapp.com', // optional — validates aud claim
534
+ });
535
+ ```
536
+
537
+ If you call a deployment through a raw Cloud Run URL or another internal host, but the service signs tokens for a canonical public domain, pass `issuer` explicitly. Otherwise issuer validation will reject a valid token because the JWT `iss` claim will not match the transport host.
538
+
539
+ **Returns**: `VerifiedGrant`
540
+
541
+ | Field | Type | Description |
542
+ |-------|------|-------------|
543
+ | `tokenId` | `string` | Unique token ID (JWT `jti` claim) |
544
+ | `grantId` | `string` | Grant record ID |
545
+ | `principalId` | `string` | User who authorized the grant (`sub` claim) |
546
+ | `agentDid` | `string` | Agent's DID (`agt` claim) |
547
+ | `developerId` | `string` | Developer org ID (`dev` claim) |
548
+ | `scopes` | `string[]` | Granted scopes (`scp` claim) |
549
+ | `issuedAt` | `number` | Issued-at timestamp (seconds since epoch) |
550
+ | `expiresAt` | `number` | Expiry timestamp (seconds since epoch) |
551
+ | `parentAgentDid` | `string?` | Parent agent DID (delegation only) |
552
+ | `parentGrantId` | `string?` | Parent grant ID (delegation only) |
553
+ | `delegationDepth` | `number?` | Delegation depth (0 = root) |
554
+
555
+ ---
556
+
557
+ ### Audit
558
+
559
+ #### `grantex.audit.log(params)`
560
+
561
+ Log an auditable action taken by an agent.
562
+
563
+ ```typescript
564
+ const entry = await grantex.audit.log({
565
+ agentId: 'ag_01J...',
566
+ agentDid: 'did:grantex:ag_01J...',
567
+ grantId: 'grnt_01J...',
568
+ principalId: 'usr_01J...',
569
+ action: 'email:send',
570
+ metadata: { to: 'user@example.com', subject: 'Hello' },
571
+ status: 'success', // 'success' | 'failure' | 'blocked'
572
+ });
573
+ ```
574
+
575
+ #### `grantex.audit.list(params?)`
576
+
577
+ ```typescript
578
+ const { entries } = await grantex.audit.list({
579
+ agentId: 'ag_01J...',
580
+ action: 'email:send',
581
+ since: '2026-01-01T00:00:00Z',
582
+ until: '2026-02-28T23:59:59Z',
583
+ page: 1,
584
+ pageSize: 50,
585
+ });
586
+ ```
587
+
588
+ #### `grantex.audit.get(entryId)`
589
+
590
+ ```typescript
591
+ const entry = await grantex.audit.get('alog_01J...');
592
+ console.log(entry.hash); // SHA-256 hash for tamper evidence
593
+ console.log(entry.prevHash); // previous entry hash (chain integrity)
594
+ ```
595
+
596
+ ---
597
+
598
+ ### Webhooks
599
+
600
+ #### `grantex.webhooks.create(params)`
601
+
602
+ ```typescript
603
+ const webhook = await grantex.webhooks.create({
604
+ url: 'https://myapp.com/webhooks/grantex',
605
+ events: ['grant.created', 'grant.revoked', 'token.issued'],
606
+ });
607
+ console.log(webhook.secret); // HMAC secret for signature verification
608
+ ```
609
+
610
+ #### `grantex.webhooks.list()`
611
+
612
+ ```typescript
613
+ const { webhooks } = await grantex.webhooks.list();
614
+ ```
615
+
616
+ #### `grantex.webhooks.delete(webhookId)`
617
+
618
+ ```typescript
619
+ await grantex.webhooks.delete('wh_01J...');
620
+ ```
621
+
622
+ #### Webhook Signature Verification
623
+
624
+ ```typescript
625
+ import { verifyWebhookSignature } from '@grantex/sdk';
626
+
627
+ // In your webhook handler
628
+ verifyWebhookSignature(requestBody, signatureHeader, webhookSecret);
629
+ ```
630
+
631
+ ---
632
+
633
+ ### Policies
634
+
635
+ Define fine-grained access control rules for agents.
636
+
637
+ #### `grantex.policies.create(params)`
638
+
639
+ ```typescript
640
+ const policy = await grantex.policies.create({
641
+ name: 'Block after hours',
642
+ effect: 'deny',
643
+ priority: 10,
644
+ scopes: ['email:send'],
645
+ timeOfDayStart: '18:00',
646
+ timeOfDayEnd: '08:00',
647
+ });
648
+ ```
649
+
650
+ #### `grantex.policies.list()`
651
+
652
+ ```typescript
653
+ const { policies, total } = await grantex.policies.list();
654
+ ```
655
+
656
+ #### `grantex.policies.get(policyId)` / `update(policyId, params)` / `delete(policyId)`
657
+
658
+ ```typescript
659
+ const policy = await grantex.policies.get('pol_01J...');
660
+
661
+ await grantex.policies.update('pol_01J...', { effect: 'allow' });
662
+
663
+ await grantex.policies.delete('pol_01J...');
664
+ ```
665
+
666
+ ---
667
+
668
+ ### Compliance
669
+
670
+ #### `grantex.compliance.getSummary(params?)`
671
+
672
+ ```typescript
673
+ const summary = await grantex.compliance.getSummary({
674
+ since: '2026-01-01T00:00:00Z',
675
+ until: '2026-02-28T23:59:59Z',
676
+ });
677
+ console.log(summary.agents); // { total, active, suspended, revoked }
678
+ console.log(summary.grants); // { total, active, revoked, expired }
679
+ console.log(summary.auditEntries); // { total, success, failure, blocked }
680
+ ```
681
+
682
+ #### `grantex.compliance.exportGrants(params?)`
683
+
684
+ ```typescript
685
+ const { grants, total } = await grantex.compliance.exportGrants({
686
+ status: 'active',
687
+ });
688
+ ```
689
+
690
+ #### `grantex.compliance.exportAudit(params?)`
691
+
692
+ ```typescript
693
+ const { entries, total } = await grantex.compliance.exportAudit({
694
+ since: '2026-01-01T00:00:00Z',
695
+ agentId: 'ag_01J...',
696
+ });
697
+ ```
698
+
699
+ #### `grantex.compliance.evidencePack(params?)`
700
+
701
+ Generate a full SOC 2 / GDPR evidence pack with audit chain integrity verification.
702
+
703
+ ```typescript
704
+ const pack = await grantex.compliance.evidencePack({
705
+ framework: 'soc2', // 'soc2' | 'gdpr' | 'all'
706
+ since: '2026-01-01T00:00:00Z',
707
+ });
708
+
709
+ console.log(pack.chainIntegrity.valid); // true
710
+ console.log(pack.chainIntegrity.checkedEntries); // 1042
711
+ console.log(pack.summary);
712
+ console.log(pack.grants);
713
+ console.log(pack.auditEntries);
714
+ console.log(pack.policies);
715
+ ```
716
+
717
+ ---
718
+
719
+ ### Anomaly Detection
720
+
721
+ In version 0.7.0, `grantex.anomalies.getResponsePolicy()` and
722
+ `grantex.anomalies.setResponsePolicy('alert_only')` control the account-wide
723
+ response of the legacy detector when the server operator enables
724
+ `IRREGULARITY_RESPONSE_POLICY_ENABLED=true`. Default behavior revokes active
725
+ grants for a high/critical finding's agent. Alert-only retains findings and
726
+ events without that automatic revocation. This setting does not disable other
727
+ security controls.
728
+
729
+ Version 0.7.0 also supports
730
+ `grantex.webauthn.createEnrollmentSession({ principalId, authRequestId? })`.
731
+ Call it only on your server after authenticating the customer. Deliver the
732
+ returned one-use hosted link to that exact customer; never expose the API key
733
+ or log the link. Hosted enrollment requires the server's default-off
734
+ `PASSKEY_ENROLLMENT_ENABLED=true` setting. Live consent has no password or
735
+ sandbox fallback.
736
+
737
+ #### `grantex.anomalies.detect()`
738
+
739
+ Run anomaly detection across all agents.
740
+
741
+ ```typescript
742
+ const { anomalies, total } = await grantex.anomalies.detect();
743
+ // anomaly types: 'rate_spike' | 'high_failure_rate' | 'new_principal' | 'off_hours_activity'
744
+ ```
745
+
746
+ #### `grantex.anomalies.list(params?)`
747
+
748
+ ```typescript
749
+ const { anomalies } = await grantex.anomalies.list({
750
+ unacknowledged: true, // only open anomalies
751
+ });
752
+ ```
753
+
754
+ #### `grantex.anomalies.acknowledge(anomalyId)`
755
+
756
+ ```typescript
757
+ const anomaly = await grantex.anomalies.acknowledge('anom_01J...');
758
+ ```
759
+
760
+ ---
761
+
762
+ ### Billing
763
+
764
+ #### `grantex.billing.getSubscription()`
765
+
766
+ ```typescript
767
+ const sub = await grantex.billing.getSubscription();
768
+ console.log(sub.plan); // 'free' | 'pro' | 'enterprise'
769
+ console.log(sub.status); // 'active' | 'past_due' | 'canceled'
770
+ console.log(sub.currentPeriodEnd); // ISO 8601 or null
771
+ ```
772
+
773
+ #### `grantex.billing.createCheckout(params)`
774
+
775
+ ```typescript
776
+ const { checkoutUrl } = await grantex.billing.createCheckout({
777
+ plan: 'pro',
778
+ successUrl: 'https://myapp.com/billing/success',
779
+ cancelUrl: 'https://myapp.com/billing/cancel',
780
+ });
781
+ // Redirect user to checkoutUrl
782
+ ```
783
+
784
+ #### `grantex.billing.createPortal(params)`
785
+
786
+ ```typescript
787
+ const { portalUrl } = await grantex.billing.createPortal({
788
+ returnUrl: 'https://myapp.com/settings',
789
+ });
790
+ ```
791
+
792
+ ---
793
+
794
+ ### SCIM 2.0 Provisioning
795
+
796
+ Sync users from your identity provider.
797
+
798
+ #### Token Management
799
+
800
+ ```typescript
801
+ // Create a SCIM bearer token
802
+ const { token, id, label } = await grantex.scim.createToken({
803
+ label: 'Okta SCIM integration',
804
+ });
805
+ // token is returned once — store it securely
806
+
807
+ const { tokens } = await grantex.scim.listTokens();
808
+
809
+ await grantex.scim.revokeToken('scimtok_01J...');
810
+ ```
811
+
812
+ #### User Operations
813
+
814
+ ```typescript
815
+ // List provisioned users
816
+ const { Resources, totalResults } = await grantex.scim.listUsers({
817
+ startIndex: 1,
818
+ count: 100,
819
+ });
820
+
821
+ // Create a user
822
+ const user = await grantex.scim.createUser({
823
+ userName: 'alice@example.com',
824
+ displayName: 'Alice',
825
+ emails: [{ value: 'alice@example.com', primary: true }],
826
+ });
827
+
828
+ // Get / Replace / Patch / Delete
829
+ const user = await grantex.scim.getUser('scimusr_01J...');
830
+
831
+ await grantex.scim.replaceUser('scimusr_01J...', { userName: 'alice@new.com' });
832
+
833
+ await grantex.scim.updateUser('scimusr_01J...', [
834
+ { op: 'replace', path: 'active', value: false },
835
+ ]);
836
+
837
+ await grantex.scim.deleteUser('scimusr_01J...');
838
+ ```
839
+
840
+ ---
841
+
842
+ ### SSO (OIDC)
843
+
844
+ #### `grantex.sso.createConfig(params)`
845
+
846
+ ```typescript
847
+ const config = await grantex.sso.createConfig({
848
+ issuerUrl: 'https://accounts.google.com',
849
+ clientId: 'xxx.apps.googleusercontent.com',
850
+ clientSecret: 'GOCSPX-...',
851
+ redirectUri: 'https://myapp.com/auth/callback',
852
+ });
853
+ ```
854
+
855
+ #### `grantex.sso.getConfig()` / `deleteConfig()`
856
+
857
+ ```typescript
858
+ const config = await grantex.sso.getConfig();
859
+ await grantex.sso.deleteConfig();
860
+ ```
861
+
862
+ #### `grantex.sso.getLoginUrl(org)`
863
+
864
+ ```typescript
865
+ const { authorizeUrl } = await grantex.sso.getLoginUrl('dev_01J...');
866
+ // Redirect user to authorizeUrl
867
+ ```
868
+
869
+ #### `grantex.sso.handleCallback(code, state)`
870
+
871
+ ```typescript
872
+ const { email, name, sub, developerId } = await grantex.sso.handleCallback(code, state);
873
+ ```
874
+
875
+ ---
876
+
877
+ ## Error Handling
878
+
879
+ All errors extend `GrantexError`:
880
+
881
+ ```typescript
882
+ import {
883
+ GrantexError, // base class
884
+ GrantexApiError, // API returned an error (has statusCode, body, requestId)
885
+ GrantexAuthError, // 401/403 — invalid or missing API key
886
+ GrantexTokenError, // token verification failed (invalid signature, expired, etc.)
887
+ GrantexNetworkError, // network failure (timeout, DNS, connection refused)
888
+ } from '@grantex/sdk';
889
+
890
+ try {
891
+ await grantex.agents.get('ag_invalid');
892
+ } catch (err) {
893
+ if (err instanceof GrantexAuthError) {
894
+ console.error('Auth failed:', err.statusCode); // 401 or 403
895
+ console.error('Request ID:', err.requestId);
896
+ } else if (err instanceof GrantexApiError) {
897
+ console.error('API error:', err.statusCode, err.body);
898
+ } else if (err instanceof GrantexNetworkError) {
899
+ console.error('Network error:', err.message, err.cause);
900
+ }
901
+ }
902
+ ```
903
+
904
+ ---
905
+
906
+ ## Requirements
907
+
908
+ - Node.js 18+
909
+ - ESM (`"type": "module"` in your package.json, or use dynamic `import()`)
910
+
911
+ ## Links
912
+
913
+ - [GitHub](https://github.com/mishrasanjeev/grantex)
914
+ - [Protocol Specification](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)
915
+ - [Python SDK](https://pypi.org/project/grantex/)
916
+ - [API Reference](https://api.grantex.dev/.well-known/jwks.json)
917
+
918
+ ## Scope Enforcement (v0.3.1)
919
+
920
+ Enforce tool-level permissions on **any connector** — define your own manifests or use the 53 pre-built ones.
921
+
922
+ ```typescript
923
+ import { Grantex, ToolManifest, Permission } from '@grantex/sdk';
924
+
925
+ const grantex = new Grantex({ apiKey: 'gx_...' });
926
+
927
+ // Define a manifest for any connector — no dependency on Grantex to add support
928
+ grantex.loadManifest(new ToolManifest({
929
+ connector: 'my-crm',
930
+ tools: { search: Permission.READ, create_deal: Permission.WRITE, delete_account: Permission.DELETE },
931
+ }));
932
+
933
+ const result = await grantex.enforce({ grantToken: token, connector: 'my-crm', tool: 'delete_account' });
934
+ // result.allowed = false — "write scope does not permit delete operations"
935
+ ```
936
+
937
+ **Features:**
938
+ - `enforce()` — verify JWT + check tool permission via manifest, <1ms
939
+ - `wrapTool()` — auto-enforce on LangChain tools
940
+ - `enforceMiddleware()` — Express/Fastify HTTP middleware
941
+ - Define custom manifests for any connector: inline, from JSON, or auto-generated via CLI
942
+ - 53 pre-built manifests included (Salesforce, HubSpot, Jira, Stripe, SAP, S3, and 47 more)
943
+ - Permission hierarchy: `admin > delete > write > read`
944
+ - Permissive mode for migration (`enforceMode: 'permissive'`)
945
+
946
+ [Full Guide](https://docs.grantex.dev/guides/scope-enforcement) | [API Reference](https://docs.grantex.dev/sdks/typescript/enforce)
947
+
948
+ ## Grantex Ecosystem
949
+
950
+ | Package | Description |
951
+ |---|---|
952
+ | [`grantex`](https://pypi.org/project/grantex/) | Python SDK |
953
+ | [`@grantex/langchain`](https://www.npmjs.com/package/@grantex/langchain) | LangChain integration |
954
+ | [`@grantex/autogen`](https://www.npmjs.com/package/@grantex/autogen) | AutoGen integration |
955
+ | [`@grantex/vercel-ai`](https://www.npmjs.com/package/@grantex/vercel-ai) | Vercel AI SDK integration |
956
+ | [`grantex-crewai`](https://pypi.org/project/grantex-crewai/) | CrewAI integration |
957
+ | [`grantex-openai-agents`](https://pypi.org/project/grantex-openai-agents/) | OpenAI Agents SDK integration |
958
+ | [`grantex-adk`](https://pypi.org/project/grantex-adk/) | Google ADK integration |
959
+ | [`@grantex/mcp`](https://www.npmjs.com/package/@grantex/mcp) | MCP server for Claude Desktop / Cursor / Windsurf |
960
+ | [`@grantex/cli`](https://www.npmjs.com/package/@grantex/cli) | Command-line tool |
961
+
962
+ ## License
963
+
964
+ [Apache 2.0](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)
965
+
966
+ ## Ownership
967
+
968
+ Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).
969
+
970
+ ## Source Development
971
+
972
+ Use Node.js 24 LTS and `npm ci` to build or test this checkout with Vitest 5.
973
+ Repository validation steps are in [the dependency upgrade guide](https://docs.grantex.dev/guides/dependency-updates).
974
+ Source-tooling requirements are separate from published package runtime support.