@grantex/sdk 0.5.0 → 0.5.1

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