Back to the catalog

io.github.mishrasanjeev/grantex

OAuth 2.0 for AI agents — scoped delegation tokens, audit trails, and revocation.

Open source Open in the app JSON README (API)

About

OAuth 2.0 for AI agents — scoped delegation tokens, audit trails, and revocation.

Details

Kind
MCP servers
Topic
Security & identity
Publisher
mishrasanjeev
Origin
official
Category
ferramentas
Transport
local
Version
0.1.9
Stars
31
Forks
8
Last push
2026-09-07T04:58:06Z
Repository state
ativo
Language
TypeScript
License
NOASSERTION
Added
2026-08-29 04:00:48
Updated
2026-08-29 04:00:48
Origin id
io.github.mishrasanjeev/grantex

README

<div align="center">

# Grantex

### Open-Source AI Agent Authorization and Delegated Access

**What OAuth 2.0 is to humans, Grantex is to agents.**

<br/>

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Spec Version](https://img.shields.io/badge/spec-v1.0--final-green)](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)
[![IETF Draft](https://img.shields.io/badge/IETF-draft--mishra--oauth--agent--grants-blue)](https://datatracker.ietf.org/doc/draft-mishra-oauth-agent-grants/)
[![OAuth Agent Grants](https://img.shields.io/badge/OAuth_agent_grants-self--tested-3fb950)](https://docs.grantex.dev/guides/oauth-agent-grants)
[![CI](https://img.shields.io/github/actions/workflow/status/mishrasanjeev/grantex/ci.yml?branch=main&label=CI)](https://github.com/mishrasanjeev/grantex/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@grantex/sdk)](https://www.npmjs.com/package/@grantex/sdk)
[![PyPI](https://img.shields.io/pypi/v/grantex)](https://pypi.org/project/grantex/)
[![npm downloads](https://img.shields.io/npm/dm/@grantex/sdk?label=npm%20downloads)](https://www.npmjs.com/package/@grantex/sdk)
[![GitHub Stars](https://img.shields.io/github/stars/mishrasanjeev/grantex?style=social)](https://github.com/mishrasanjeev/grantex)
[![Docs](https://img.shields.io/badge/docs-grantex.dev-3fb950)](https://docs.grantex.dev)
[![MCP Tool Server](https://img.shields.io/npm/v/@grantex/mcp?label=MCP%20tool%20server)](https://www.npmjs.com/package/@grantex/mcp)
[![MCP Auth](https://img.shields.io/npm/v/@grantex/mcp-auth?label=MCP%20Auth)](https://www.npmjs.com/package/@grantex/mcp-auth)
[![DPDP Mapping](https://img.shields.io/badge/DPDP-control%20mapping-informational)](https://grantex.dev/dpdp)
[![EU AI Act Mapping](https://img.shields.io/badge/EU_AI_Act-control%20mapping-informational)](https://grantex.dev/dpdp)

<br/>

[Docs](https://docs.grantex.dev) | [Quickstart](https://docs.grantex.dev/quickstart) | [Release JSON](https://grantex.dev/release-status.json) | [LLM Index](https://grantex.dev/llms.txt) | [Spec](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md) | [IETF Draft](https://datatracker.ietf.org/doc/draft-mishra-oauth-agent-grants/)

<br/>

**Ownership:** Grantex is owned by **Orchestrum Technologies LLP**. Inventor and owner: **Sanjeev Kumar**. Contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).

<br/>

<img src="docs/images/flow-diagram.svg" alt="Grantex Protocol Flow" width="100%"/>

<br/>

</div>

## What is Grantex?

Grantex is an open-source delegated authorization protocol and reference implementation for AI agents. It gives each agent a verifiable identity and scoped, time-limited, revocable authority from a human or organization, with multi-agent delegation, service-side verification, and audit records.

Grantex complements OAuth 2.0 and MCP: OAuth handles application and user authorization, MCP connects models to tools, and Grantex proves which agent may perform which action for which principal. Use Grantex when an AI agent acts for a person or organization and a relying service must verify exactly what that agent may do.

## Agent Prepaid Wallets and x402 v2

Repository source includes principal-controlled prepaid wallets for AI agents.
A principal can assign one or multiple wallets, apply assignment, wallet,
agent, shared budget-group, principal, and developer controls, constrain
recipients, resource origins, actions, merchants, purposes, projects, and cost
centers, require an exact human approval, govern reload velocity, and block one
assignment, one wallet, or all wallets available to an agent. Authorizations
reserve value atomically in PostgreSQL and are bound to the agent's DPoP OAuth
identity and the policy-evaluated semantic context.

`@grantex/x402` uses official x402 v2 `PAYMENT-REQUIRED`,
`PAYMENT-SIGNATURE`, and `PAYMENT-RESPONSE` messages. The old simulated
`X-Payment-Proof` path is not used. `sandbox_ledger` is implemented end to end;
external custody records deliberately fail closed unless a configured provider
can verify funding and settlement. The source checkout now includes opt-in
Base native USDC EIP-3009 payments with policy-gated signing, verified funding,
durable signature retries and finalized-chain reconciliation. Python `0.5.0`
and Go `v0.3.0` publish the EVM response and reconciliation APIs; neither adds
an automatic HTTP payment wrapper. Published TypeScript `0.6.0` and x402 `0.4.0`
include the opt-in automatic Base 402/sign/retry flow. All four releases are
registry verified. See the
[Base USDC custody guide](docs/guides/base-usdc-custody.mdx), including the limit
that blocking cannot recall an already issued on-chain signature.

```ts
import { PrepaidWalletAgentClient } from '@grantex/sdk';
import { createX402Agent } from '@grantex/x402';

const walletAgent = new PrepaidWalletAgentClient({ oauthClient, accessToken });
const x402 = createX402Agent({
  authorizePayment: walletAgent.x402Authorizer,
  // walletId is optional; omit it for policy-based wallet selection.
});

const logicalPaymentId = 'order_01JZ8Y6Q2M4N7P9T';
const response = await x402.fetch('https://merchant.example/paid-resource', {
  // The merchant uses this header to recover a response lost after settlement.
  headers: { 'Idempotency-Key': logicalPaymentId },
  // Grantex uses this option to recover a reservation response lost before settlement.
  idempotencyKey: logicalPaymentId,
});
```

For side-effecting resources, the merchant must durably cache the business result
under `Idempotency-Key`. Grantex settlement is idempotent, but a settled payment
authorization cannot be verified or execute protected work a second time.

The OAuth grant must include `wallet:spend` and the exact merchant action scope
advertised as `extra.grantexScope`; wallet assignment policy can restrict that
human-approved authority but cannot add to it.

### Where Grantex fits

Grantex owns delegated agent identity, semantic spend policy, cross-wallet
budgets, exact approvals, atomic reservations, stop controls, and audit. An
AgenticOrg deployment or another agent runtime owns orchestration and the human
interaction. The wallet issuer or custodian remains responsible for KYC/KYB,
AML and sanctions controls, custody, network authorization, MCC/geography/card
controls, settlement, FX, refunds, disputes, fraud, and reconciliation. The
merchant remains responsible for server-trusted price/payee data, order
idempotency, settlement gating, and delivery of the paid result.

See [Agent Wallet Governance](docs/guides/agent-wallet-governance.mdx) for the
complete responsibility matrix, policy composition, exact approval protocol,
and honest residual gap list.

The managed clients are implemented in registry-verified `@grantex/sdk@0.6.0`,
`@grantex/x402@0.4.0`, Python `grantex==0.5.0`, and Go `v0.3.0`. External
custody, principal notification delivery, and
merchant result idempotency remain operator responsibilities.
See the [x402 integration
guide](docs/integrations/x402.mdx), [wallet
lifecycle](docs/features/prepaid-wallets.mdx), and [production-readiness
guide](docs/guides/prepaid-wallet-production.mdx).

### Self-hosted prepaid-wallet dependencies

Running the auth-service does not provision every dependency needed for a
real-money product. Self-hosting operators must explicitly provide and test:

- PostgreSQL migrations `091_agent_prepaid_wallets.sql` and
  `092_layered_wallet_spend_controls.sql`, backups, restore, and reconciliation
  for the append-only wallet ledger and policy decisions;
- HTTPS ingress for the exact OAuth audience, including
  `/v1/prepaid-wallets` and `/v1/prepaid-wallets/**` (a static-host `404` breaks
  agent wallet access even while the origin service is healthy);
- a provider-specific custody, funding, settlement, webhook-deduplication, and
  recovery adapter before using `external` wallets; without one, Grantex
  deliberately returns `503 CUSTODY_ADAPTER_UNAVAILABLE`;
- an SSE/WebSocket notification bridge for reload events when a principal must
  be notified by email, SMS, Slack, WhatsApp, or another external channel; no
  built-in wallet-email delivery is claimed;
- a merchant-side durable result cache keyed by the HTTP `Idempotency-Key` for
  side effects after settlement;
- independent security, provider, reconciliation, incident-response, and
  applicable legal/regulatory review.

The server deployment and each SDK release are separate. Registry consumers
should verify an exact version before installing it rather than inferring
availability from repository manifests. The
  [production-readiness guide](docs/guides/prepaid-wallet-production.mdx) contains
  the full hosting checklist and a maintainer-only PowerShell publication runbook.

### Supply-chain and license controls

The security workflow audits every tracked npm lockfile, every tracked Python
project and requirements surface, and all three Go modules. Dependabot coverage
is checked against the repository manifests, external GitHub Actions and
container images are pinned to immutable digests, and unreviewed npm license
identifiers fail CI. Run `npm run audit:supply-chain` and
`npm run audit:python` locally before a release.

Apache-2.0 covers Grantex source, not third-party dependencies. See
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for the current
Sharp/libvips LGPL and caniuse-lite CC-BY distribution notes, and the
[supply-chain security guide](docs/guides/supply-chain-security.mdx) for the
operator checklist. This inventory is an engineering control, not a legal
non-infringement opinion; distributors must review the exact artifacts they
ship.

## OAuth Agent Grants Profile

The hosted auth service and repository TypeScript client implement the three
roles in candidate `draft-mishra-oauth-agent-grants-03`. The tested profile
requires PAR, PKCE `S256`, DPoP sender constraints, RFC 9207 response issuer
validation, five-minute access tokens, rotating refresh tokens with family
replay revocation, 300-second exact-request recovery when a refresh response is
lost, AES-256-GCM encrypted recovery state with expiry cleanup, same-resource
RFC 8693 attenuation, and RFC 7009 revocation.

| Discovery and endpoints | URL |
|---|---|
| Authorization-server metadata | `https://grantex.dev/.well-known/oauth-authorization-server` |
| PAR / authorize / token / revoke | `https://grantex.dev/oauth/{par,authorize,token,revoke}` |
| Implementation guide | [docs.grantex.dev/guides/oauth-agent-grants](https://docs.grantex.dev/guides/oauth-agent-grants) |
| Evidence | [Implementation report](docs/ietf-draft/implementation-report.md) and [30 behavioral vectors](docs/ietf-draft/test-vectors/oauth-agent-grants-03.json) |

Verification completed with 2,138 auth-service tests, 456 TypeScript SDK tests,
and 255 tests across 23 sequential Docker E2E files. This is self-assessed evidence for
the tested Grantex configuration, not independent interoperability
certification, OAuth Working Group adoption, or IETF endorsement. The
updated `OAuthAgentClient` is published in registry-verified
`@grantex/sdk@0.6.0`.

Revision `-02` remains the current Datatracker publication. Candidate `-03`
must not be uploaded before 2026-09-09 and requires a fresh explicit approval
after the scheduled final review.

## Open Agentic Commerce Protocol (OACP) Authority

Open Agentic Commerce Protocol (OACP) is Grantex's agentic-commerce trust and artifact-authority layer. Grantex governs OACP policy, internal artifact issuance or refusal, verification, and compatibility adapters. AgenticOrg owns buyer and seller AI-agent runtime, merchant self-service onboarding, Shopify connector runtime, future merchant connector setup intent, buyer sessions, channel bridges, OACP cache, and provider-owned capability verification.

Merchant systems such as Shopify, future WooCommerce/ERP sources, POS systems, and provider systems remain the source of record. Provider, bank, POS, and payment rails own mandate, payment, and in-store execution. Grantex signs and verifies artifacts; it is not a merchant connector runtime or a toll booth for every buyer and seller message.

```mermaid
flowchart LR
  merchant[Shopify, future ERP/WooCommerce, POS, provider systems] --> agentic[AgenticOrg buyer and seller runtime]
  agentic -->|redacted authority request| grantex[Grantex OACP authority]
  grantex -->|OACP artifacts or blockers| agentic
  agentic --> buyer[Buyer surfaces]
  agentic -->|capability check or handoff| provider[Pine Labs Plural/P3P, bank/POS/provider rails]
```

| Area | Current posture |
| --- | --- |
| Grantex C6Z authority route | Implemented at `POST /v1/commerce/oacp/c6z/authority-requests` for allowlisted AgenticOrg tenants. |
| Artifact families | 11 internal OACP families are issued or refused with source lineage, TTL, freshness, revocation posture, blocked capabilities, non-sensitive evidence refs, and signature metadata. |
| Protocol adapters | Schema.org, UCP-style, ACP-style, AP2-style, A2A, MCP, and OpenAPI mappings are compatibility mappings derived from OACP artifacts. |
| AgenticOrg runtime | Merchant self-service config, Seller onboarding, Shopify sync, future connector/provider intent capture, cache, buyer Q&A, bridges, and provider capability verification live in AgenticOrg. |
| Payment/order/POS execution | Outside OACP artifact authority. Provider, POS, and merchant systems must execute and confirm; agents must not invent success. |
| Historical Commerce V1 docs | Retained for context, but superseded for the AgenticOrg OACP runtime split. |

Start with the [OACP runtime launch closure PRD](docs/guides/oacp/runtime-launch-closure-prd.mdx), [OACP authority overview](docs/guides/oacp/overview.mdx), [merchant self-service config boundary](docs/guides/oacp/merchant-self-service-config.mdx), [truth inventory](docs/guides/oacp/truth-inventory.mdx), [AgenticOrg integration guide](docs/guides/oacp/agenticorg-integration.mdx), [POS bridge boundary](docs/guides/oacp/pos-bridge-boundary.mdx), and [operator runbook](docs/guides/oacp/operator-runbook.mdx). The older [Commerce V1 overview](docs/guides/commerce-v1-overview.mdx) remains historical/contextual and should not be used to imply that Grantex owns AgenticOrg merchant connector runtime.

## Current Releases

Grantex components are independently versioned. The protocol specification remains **v1.0 Final**; SDK, MCP package, and roadmap milestone versions are separate release lines and do not represent a monorepo-wide version.

Current public releases and repository versions, verified 2026-09-07:

| Component | Published version | Repository version | Reproducible install |
| --- | ---: | ---: | --- |
| TypeScript SDK | `@grantex/sdk` `0.6.0` | `0.6.0` | `npm install @grantex/sdk@0.6.0` |
| x402 Payment Protocol | `@grantex/x402` `0.4.0` | `0.4.0` | `npm install @grantex/x402@0.4.0 @grantex/sdk@0.6.0` |
| Python SDK | `grantex` `0.5.0` | - | `python -m pip install grantex==0.5.0` |
| Go SDK | `github.com/mishrasanjeev/grantex-go` `v0.3.0` (Go 1.26.1+) | - | `go get github.com/mishrasanjeev/grantex-go@v0.3.0` |
| MCP Authorization Server | `@grantex/mcp-auth` `2.0.2` | - | `npm install @grantex/mcp-auth@2.0.2 @grantex/sdk@0.6.0` |

> **Known published-package limits:** MCP Auth `2.0.2` keeps authorization codes
> in process memory, does not render consent,
> and has an incomplete Grantex code handoff. See the [release-status guide](https://docs.grantex.dev/release-status)
> for exact workarounds and deployment boundaries.

> **Repository development status:** the auth service enforces Redis-backed
> Free/Pro/Enterprise developer budgets of
> 100/500/2,000 requests per minute on API-key routes handled by the standard
> auth plugin. Custom-auth quota policy remains open and the managed-service
> rollout remains independent of SDK publication.

Omit a version pin to install the registry's current latest release. See the [release-status documentation](https://docs.grantex.dev/release-status), [COMPATIBILITY.md](COMPATIBILITY.md) for the full package matrix, and [CHANGELOG.md](CHANGELOG.md) for release notes.

- **@grantex/gemma**: Offline consent bundles and on-device verification examples
- **MCP Authorization Server (`@grantex/mcp-auth`)**: Published OAuth 2.1 + PKCE endpoint package; review the documented `2.0.2` single-process, consent, and token-exchange limitations
- **MCP Tool Server (`@grantex/mcp`)**: Agent-facing Grantex tools for MCP clients
- **@grantex/dpdp**: DPDP Act 2023 and EU AI Act control mappings
- **Trust Registry**: Public DID verification registry — `grantex.dev/registry`
- **`grantex verify`**: Token inspection CLI — no account needed
- **Agent CLI Skills**: One-command `SKILL.md` installation for Hermes, OpenClaw, and portable Agent Skills clients
- **Anomaly Detection**: Four implemented SQL-backed checks, lifecycle APIs, and stored rule/channel configuration; notification delivery requires a host worker

---

## SDK quickstart

```bash
npm install @grantex/sdk@0.6.0
```

```typescript
import { Grantex, verifyGrantToken } from '@grantex/sdk';
const gx = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });

// 1. Register an agent, then request authorization from a user
const agent = await gx.agents.register({
  name: 'quickstart-agent',
  description: 'Grantex quickstart agent',
  scopes: ['calendar:read', 'email:send'],
});
const auth = await gx.authorize({
  agentId: agent.id,
  userId: 'user-456',
  scopes: ['calendar:read', 'email:send'],
});

// Live mode requires consent at this URL and returns the code to your callback.
// Sandbox or policy auto-approval can return the code immediately.
if (!auth.code) {
  console.log(`Approve access at: ${auth.consentUrl}`);
} else {
  // 2. Exchange the authorization code for a scoped, signed JWT
  const { grantToken } = await gx.tokens.exchange({ code: auth.code, agentId: agent.id });

  // 3. Verify locally using the issuer's published JWKS
  const grant = await verifyGrantToken(grantToken, {
    jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
  });
  console.log(grant.scopes); // ['calendar:read', 'email:send']
}
```

```bash
python -m pip install grantex==0.5.0               # Python SDK
go get github.com/mishrasanjeev/grantex-go@v0.3.0 # Go SDK (Go 1.26.1+)
npm install @grantex/mcp-auth@2.0.2 @grantex/sdk@0.6.0 # MCP endpoint evaluation
npm install -g @grantex/cli@0.3.0                   # Optional CLI tooling
```

### Hermes, OpenClaw, and any agent CLI

Shell-capable agents use the same JSON-first CLI; no agent-specific SDK is required:

```bash
npm install -g @grantex/cli@0.3.0
grantex agent install --target openclaw  # writes ./skills
grantex agent install --target hermes    # writes ~/.hermes/skills/grantex
grantex agent install --target portable  # writes ./.agents/skills
```

The bundle installs `use-grantex-cli` for delegated-authorization operations and `integrate-grantex` for service-boundary implementation work. For a different host, use `grantex agent install --dir /path/to/skills`. Prefer `--env`, `--file`, or `--stdin` token inputs and keep final enforcement inside the protected service.

> **29 packages** across TypeScript, Python, and Go. Integrations for **Anthropic SDK, LangChain, OpenAI Agents SDK, Google ADK, Strands Agents SDK, CrewAI, Vercel AI, AutoGen, MCP, Express.js, FastAPI**, and **Terraform**. Use the compatibility matrix for versions, the changelog for release notes, and GitHub Actions for current CI status. Fully self-hostable. Apache 2.0.

---

## The Problem

AI agents are booking travel, sending emails, deploying code, and spending money — on behalf of real humans. But:

- **No scoping** — agents get the same access as the key owner
- **No consent** — users never approve what the agent can do
- **No per-agent identity** — you know the key was used, but not which agent or why
- **No revocation granularity** — one agent misbehaves, rotate the key, kill everything
- **No delegation control** — Agent A calls Agent B? Copy-paste credentials
- **No spending limits** — an agent with a cloud API key can provision unlimited resources

OAuth and IAM provide essential foundations, but many agent deployments still rely on shared credentials that do not identify the individual agent or encode its delegated authority.

---

## How It Works

<img src="docs/images/flow-diagram.svg" alt="Grantex Protocol Flow" width="100%"/>

---

## Quickstart

### 1. Register your agent

```typescript
import { Grantex } from '@grantex/sdk';

const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });

const agent = await grantex.agents.register({
  name: 'travel-booker',
  description: 'Books flights and hotels on behalf of users',
  scopes: ['calendar:read', 'payments:initiate:max_500', 'email:send'],
});

console.log(agent.did);
// → did:grantex:ag_01HXYZ123abc...
```

### 2. Request authorization from a user

```typescript
const authRequest = await grantex.authorize({
  agentId: agent.id,
  userId: 'user_abc123',       // your app's user identifier
  scopes: ['calendar:read', 'payments:initiate:max_500'],
  expiresIn: '24h',
  redirectUri: 'https://yourapp.com/auth/callback',
});

// Redirect user to authRequest.consentUrl
// Grantex handles the consent UI — plain language, mobile-first
console.log(authRequest.consentUrl);
// → https://consent.grantex.dev/authorize?req=eyJ...
```

### 3. Exchange the authorization code for a grant token

```typescript
// After user approves, your redirectUri receives a `code`.
// Exchange it for a signed grant token (RS256 JWT):
const token = await grantex.tokens.exchange({
  code,                  // from the redirect callback
  agentId: agent.id,
});

console.log(token.grantToken);  // RS256 JWT — pass this to your agent
console.log(token.scopes);      // ['calendar:read', 'payments:initiate:max_500']
console.log(token.grantId);     // 'grnt_01HXYZ...'
console.log(token.refreshToken); // store securely for active-grant rotation
```

Refresh tokens are single-use and rotate on every accepted refresh. If the
refresh HTTP response is lost after the server commits, retry the same previous
refresh token immediately; Grantex can recover the already-rotated token pair
for five minutes (300 seconds). Refresh does not extend `token.expiresAt`; after
the grant expires, start a new authorization request.

### 4. Verify the token and use it

```typescript
// Verify locally after retrieving the issuer's published JWKS
import { verifyGrantToken } from '@grantex/sdk';

const grant = await verifyGrantToken(token.grantToken, {
  jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
  requiredScopes: ['calendar:read'],
});
console.log(grant.principalId); // 'user_abc123'
console.log(grant.scopes);     // ['calendar:read', 'payments:initiate:max_500']

// Pass to your agent — it's now authorized
await travelAgent.run({ grantToken: token.grantToken, task: 'Book cheapest flight to Delhi on March 1' });
```

### 5. Record the action at the execution boundary

```typescript
// At the trusted execution boundary: explicitly record the outcome
await grantex.audit.log({
  agentId: agent.id,
  agentDid: agent.did,
  grantId: token.grantId,
  principalId: authRequest.principalId,
  action: 'payment.initiated',
  status: 'success',
  metadata: { amount: 420, currency: 'USD', merchant: 'Air India' },
});
```

### 6. Verify a token (service-side)

```typescript
// In any service that receives agent requests — no Grantex account needed
import { verifyGrantToken } from '@grantex/sdk';

const grant = await verifyGrantToken(token.grantToken, {
  jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
  requiredScopes: ['payments:initiate'],
});
// Throws if the token is expired, tampered with, has invalid claims, or lacks required scopes.
// Use grantex.tokens.verify(token.grantToken) when you also need current revocation status.
```

### 7. Give users control over their permissions

```typescript
// Generate a short-lived link for the end-user to view & revoke agent access
const session = await grantex.principalSessions.create({
  principalId: 'user_abc123',
  expiresIn: '2h',
});
// Send session.dashboardUrl to the user via email, in-app notification, etc.
// The short-lived session token is carried in the URL fragment, not the query string.
```

---

## Python SDK

```python
import os

from grantex import Grantex, AuthorizeParams, ExchangeTokenParams

client = Grantex(api_key=os.environ["GRANTEX_API_KEY"])

# Register agent
agent = client.agents.register(
    name="finance-agent",
    scopes=["transactions:read", "payments:initiate:max_100"],
)

# Authorize a user
auth = client.authorize(AuthorizeParams(
    agent_id=agent.id,
    user_id="user_abc123",
    scopes=["transactions:read", "payments:initiate:max_100"],
))
# Redirect user to auth.consent_url — they approve in plain language

# Exchange the authorization code for a grant token
token = client.tokens.exchange(ExchangeTokenParams(code=code, agent_id=agent.id))

# Verify locally after retrieving the issuer's published JWKS
from grantex import verify_grant_token, VerifyGrantTokenOptions

grant = verify_grant_token(token.grant_token, VerifyGrantTokenOptions(
    jwks_uri="https://api.grantex.dev/.well-known/jwks.json",
))
print(grant.scopes)  # ('transactions:read', 'payments:initiate:max_100')

# Log an action
client.audit.log(
    agent_id=agent.id,
    agent_did=agent.did,
    grant_id=token.grant_id,
    principal_id=auth.principal_id,
    action="transaction.read",
    status="success",
    metadata={"account_last4": "4242"},
)
```

---

## The Grant Token

Grantex tokens are standard JWTs (RS256) extended with agent-specific claims. Any service can verify their signatures locally using the issuer's published JWKS. The provided verifiers retrieve those keys from the configured JWKS URL, so applications should account for network availability, caching, and key rotation:

```json
{
  "iss": "https://grantex.dev",
  "sub": "user_abc123",
  "agt": "did:grantex:ag_01HXYZ123abc",
  "dev": "org_yourcompany",
  "scp": ["calendar:read", "payments:initiate:max_500"],
  "iat": 1709000000,
  "exp": 1709086400,
  "jti": "tok_01HXYZ987xyz",
  "grnt": "grnt_01HXYZ456def"
}
```

| Claim | Meaning |
|-------|---------|
| `sub` | The end-user who authorized this agent |
| `agt` | The agent's DID — cryptographically verifiable identity |
| `dev` | The developer org that built the agent |
| `scp` | Exact scopes granted — services should check these |
| `jti` | Unique token ID — used for grant-state and revocation checks |
| `grnt` | Grant record ID — links token to the persisted grant |
| `aud` | Intended audience (optional) — services should reject tokens with a mismatched `aud` |

**Delegation claims** (present on sub-agent tokens):

| Claim | Meaning |
|-------|---------|
| `parentAgt` | DID of the parent agent that spawned this sub-agent |
| `parentGrnt` | Grant ID of the parent grant — full delegation chain is traceable |
| `delegationDepth` | How many hops from the root grant (root = 0) |

---

## Multi-Agent Delegation

Grantex supports multi-agent pipelines where a root agent spawns sub-agents with narrower scopes. Sub-agent tokens carry a full delegation chain that any service can inspect.

```typescript
// Root agent has a grant for ['calendar:read', 'calendar:write', 'email:send']
// It spawns a sub-agent that only needs calendar read access

const delegated = await grantex.grants.delegate({
  parentGrantToken: rootGrantToken,   // root agent's token
  subAgentId: subAgent.id,            // sub-agent to authorize
  scopes: ['calendar:read'],          // must be ⊆ parent scopes
  expiresIn: '1h',                    // capped at parent token's expiry
});

// delegated.grantToken is a fully signed JWT with:
//   parentAgt, parentGrnt, delegationDepth = 1
```

```python
# Python equivalent
delegated = grantex.grants.delegate(
    parent_grant_token=root_grant_token,
    sub_agent_id=sub_agent.id,
    scopes=["calendar:read"],
    expires_in="1h",
)
```

**Constraints enforced by the protocol:**
- Sub-agent scopes must be a strict subset of the parent's scopes — scope escalation is rejected with 400
- Sub-agent token expiry is `min(parent expiry, requested expiry)` — sub-agents can never outlive their parent
- Revoking a root grant cascades to all descendant grants atomically

---

## Advanced Features

<details>
<summary><strong>Enterprise SSO</strong> - OIDC and SAML 2.0, plus an LDAP direct-bind preview</summary>

## Enterprise SSO

Grantex provides OIDC and SAML 2.0 enterprise SSO with multiple identity-provider connections, email-domain routing, enforcement, JIT provisioning, and group-to-scope mapping from identity-provider claims. The LDAP surface is a direct-bind preview: it authenticates a supplied directory identity but does not search directories or retrieve LDAP groups.

**Key capabilities:**
- **Multi-IdP connections** - Configure multiple OIDC and SAML 2.0 identity providers per organization; LDAP connection records support the direct-bind preview
- **OIDC Discovery + JWKS verification** — Automatic endpoint discovery and cryptographic ID token verification
- **SAML 2.0** — Full SAML response parsing with certificate-based signature verification
- **LDAP / Active Directory preview** - Direct-bind authentication only; directory search and LDAP group retrieval are not implemented
- **Domain-based routing** — Automatically route users to the correct IdP based on their email domain
- **JIT provisioning** — Auto-create or update principals on first SSO login
- **Group-to-scope mapping** - Map OIDC or SAML group/role claims to Grantex scopes; this does not retrieve LDAP groups
- **SSO enforcement** — Require SSO authentication for all users in an organization
- **Session management** — Track, list, and revoke active SSO sessions

### TypeScript

```typescript
// Create an OIDC connection
const conn = await grantex.sso.createConnection({
  name: 'Okta Production',
  protocol: 'oidc',
  issuerUrl: 'https://mycompany.okta.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  domains: ['mycompany.com'],
  jitProvisioning: true,
  groupAttribute: 'groups',
  groupMappings: { Engineering: ['read', 'write', 'deploy'], Admins: ['admin'] },
  defaultScopes: ['read'],
});

// Create a SAML 2.0 connection
await grantex.sso.createConnection({
  name: 'Azure AD SAML',
  protocol: 'saml',
  idpEntityId: 'https://sts.windows.net/tenant-id/',
  idpSsoUrl: 'https://login.microsoftonline.com/tenant-id/saml2',
  idpCertificate: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
  spEntityId: 'urn:grantex:mycompany',
  spAcsUrl: 'https://myapp.com/sso/callback/saml',
  domains: ['mycompany.com'],
});

// Enforce SSO for the organization
await grantex.sso.setEnforcement({ enforce: true });

// Handle OIDC callback with verified ID token
const result = await grantex.sso.handleOidcCallback({ code, state });
console.log(result.email, result.mappedScopes, result.sessionId);

// List and revoke sessions
const { sessions } = await grantex.sso.listSessions();
await grantex.sso.revokeSession(sessions[0].id);
```

### Python

```python
# Create an OIDC connection
conn = client.sso.create_connection(CreateSsoConnectionParams(
    name="Okta Production",
    protocol="oidc",
    issuer_url="https://mycompany.okta.com",
    client_id="your-client-id",
    client_secret="your-client-secret",
    domains=["mycompany.com"],
    jit_provisioning=True,
    group_attribute="groups",
    group_mappings={"Engineering": ["read", "write", "deploy"]},
))

# Handle OIDC callback
result = client.sso.handle_oidc_callback(SsoOidcCallbackParams(code=code, state=state))
print(result.email, result.mapped_scopes, result.session_id)
```

### CLI

```bash
# Create a connection
grantex sso connections create --name "Okta" --protocol oidc \
  --issuer-url https://mycompany.okta.com \
  --client-id $CLIENT_ID --client-secret $CLIENT_SECRET \
  --domains mycompany.com --jit-provisioning

# List connections
grantex sso connections list

# Test connectivity
grantex sso connections test sso_01HXYZ...

# Enforce SSO
grantex sso enforce --enable
```

</details>

<details>
<summary><strong>FIDO2 / WebAuthn</strong> — passkey-based consent verification</summary>

## FIDO2 / WebAuthn

Grantex supports passkey-based human presence verification using the FIDO2/WebAuthn standard. When enabled, end-users prove they are physically present during the consent flow by authenticating with a passkey (biometric, security key, or platform authenticator). This raises the assurance level of every grant from "user clicked approve" to "user was cryptographically verified."

### How It Works

1. **Developer enables FIDO** — Set `fidoRequired: true` on your developer profile via `PATCH /v1/me`
2. **User registers a passkey** — During the first consent flow, the user registers a FIDO2 credential (fingerprint, Face ID, YubiKey, etc.)
3. **User authenticates on consent** — On subsequent authorization requests, the user completes a WebAuthn assertion challenge instead of a simple button click
4. **FIDO evidence embedded in grants** — The assertion result is recorded in the grant and can be embedded in Verifiable Credentials as cryptographic proof of human presence

### SDK Usage

```typescript
// Enable FIDO for your developer account
await grantex.updateSettings({ fidoRequired: true, fidoRpName: 'My App' });

// Register a passkey for an end-user (called from the browser)
const options = await grantex.webauthn.registerOptions({ principalId: 'user_abc123' });
// Pass options to navigator.credentials.create() in the browser
const credential = await navigator.credentials.create({ publicKey: options });
await grantex.webauthn.registerVerify({ challengeId: options.challengeId, response: credential });

// List and manage credentials
const creds = await grantex.webauthn.listCredentials('user_abc123');
await grantex.webauthn.deleteCredential(credentialId);
```

```python
# Enable FIDO for your developer account
from grantex import UpdateDeveloperSettingsParams, WebAuthnRegistrationVerifyParams

client.update_settings(UpdateDeveloperSettingsParams(
    fido_required=True,
    fido_rp_name="My App",
))

# Register a passkey (server-side portion)
options = client.webauthn.register_options(principal_id="user_abc123")
# Browser performs navigator.credentials.create() and sends response back
result = client.webauthn.register_verify(WebAuthnRegistrationVerifyParams(
    challenge_id=options.challenge_id,
    response=credential_response,
))

# List and manage credentials
creds = client.webauthn.list_credentials("user_abc123")
client.webauthn.delete_credential(credential_id)
```

### WebAuthn API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/v1/webauthn/register/options` | Generate passkey registration options |
| `POST` | `/v1/webauthn/register/verify` | Verify registration and store credential |
| `GET` | `/v1/webauthn/credentials` | List WebAuthn credentials for a principal |
| `DELETE` | `/v1/webauthn/credentials/:id` | Delete a credential |
| `POST` | `/v1/webauthn/assert/options` | Generate assertion options for consent |
| `POST` | `/v1/webauthn/assert/verify` | Verify assertion during consent |
| `PATCH` | `/v1/me` | Update developer settings (FIDO config) |

</details>

<details>
<summary><strong>Verifiable Credentials</strong> — W3C VC-JWT issuance and verification</summary>

## Verifiable Credentials

Grantex can issue W3C Verifiable Credentials (VCs) alongside standard JWTs. While JWTs are optimized for real-time authorization, VCs provide a portable, tamper-evident, standards-based proof of authorization that can be presented to any verifier — including systems outside the Grantex ecosystem.

### Why VCs Matter for Agents

In agentic commerce, an agent acting on your behalf needs to prove its authorization to third-party services that may not integrate with Grantex directly. A Verifiable Credential is a self-contained, cryptographically signed document that any party can verify using the issuer's published DID document — no API calls, no accounts, no trust relationships required.

### How It Works

When exchanging an authorization code for a grant token, pass `credentialFormat: "vc-jwt"` to receive a Verifiable Credential alongside the standard grant token:

```typescript
const result = await grantex.tokens.exchange({
  code,
  agentId: agent.id,
  credentialFormat: 'vc-jwt',   // opt-in to VC issuance
});

console.log(result.grantToken);         // standard RS256 JWT (unchanged)
console.log(result.verifiableCredential); // W3C VC-JWT
```

```python
result = client.tokens.exchange(ExchangeTokenParams(
    code=code,
    agent_id=agent.id,
    credential_format="vc-jwt",
))

print(result.verifiable_credential)  # W3C VC-JWT
```

### Credential Types

| Type | Description |
|------|-------------|
| `AgentGrantCredential` | Issued for direct grants — attests that a principal authorized an agent with specific scopes |
| `DelegatedGrantCredential` | Issued for delegated grants — includes the full delegation chain |

### Verifying a VC

```typescript
const verification = await grantex.credentials.verify(vcJwt);
console.log(verification.valid);
console.log(verification.credentialSubject);
console.log(verification.issuer); // "did:web:grantex.dev"
```

```python
verification = client.credentials.verify(vc_jwt)
print(verification.valid)
print(verification.credential_subject)
```

### Revocation via StatusList2021

Grantex implements the W3C StatusList2021 revocation mechanism. Each credential references a status list entry. When a grant is revoked, the corresponding bit in the status list is flipped, and any verifier checking the credential sees it as revoked.

```typescript
// Check a specific credential's status
const cred = await grantex.credentials.get(credentialId);
console.log(cred.status); // "active" or "revoked"

// List credentials with filters
const { credentials } = await grantex.credentials.list({
  grantId: 'grnt_01HXYZ...',
  status: 'active',
});
```

### FIDO Evidence in VCs

When FIDO is enabled and the user completes a WebAuthn assertion during consent, the VC includes a `fidoEvidence` field that cryptographically proves human presence at the time of authorization. This is compatible with the Mastercard Verifiable Intent specification for agentic commerce.

### DID Infrastructure

Grantex publishes a W3C DID document at `/.well-known/did.json` (`did:web:grantex.dev`). This document contains the public keys used to sign Verifiable Credentials, enabling any party to verify credentials without contacting Grantex:

```bash
curl https://api.grantex.dev/.well-known/did.json
```

### Verifiable Credentials API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/v1/credentials/:id` | Retrieve a Verifiable Credential |
| `GET` | `/v1/credentials` | List Verifiable Credentials |
| `POST` | `/v1/credentials/verify` | Verify a VC-JWT |
| `GET` | `/v1/credentials/status/:id` | StatusList2021 credential |
| `GET` | `/.well-known/did.json` | W3C DID document |

</details>

<details>
<summary><strong>MPP Agent Identity</strong> — verifiable agent identity for machine payments</summary>

## MPP Agent Identity

MPP (Machine Payments Protocol) defines HTTP 402 payment flows for software clients. Grantex can attach an `AgentPassportCredential` based on W3C Verifiable Credentials 2.0 so a configured merchant can verify agent, principal, category, amount-limit, expiry, and delegation claims after obtaining the issuer keys and current status data. The credential carries authorization context; it does not prove that a payment settled, an order was approved, or a provider accepted the transaction.

### Why Agent Passports?

A wallet or payment-source identifier may not tell a merchant which internal agent is acting, which principal delegated authority, or which policy limits apply. An agent passport can supply that context when both sides integrate and enforce it.

The current Grantex passport shape uses Ed25519 signatures, a W3C VC 2.0 data model, configured purchase categories, transaction ceilings, expiry, delegation context, and StatusList2021-style status data. A relying merchant must still validate the issuer, refresh keys and status according to its risk policy, enforce the claims at the protected action, and run its own payment, fraud, sanctions, order, and settlement controls.

### How It Works

| Step | Who | What |
|------|-----|------|
| **1. Issue** | Authorized application | Requests an `AgentPassportCredential` with configured categories, amount ceiling, delegation context, and expiry |
| **2. Store** | Agent host | Stores the credential as sensitive authorization material |
| **3. Present** | Agent host | Attaches the credential to a supported MPP request when the integration is configured |
| **4. Verify** | Merchant service | Verifies signature, issuer, expiry, category, amount, and sufficiently current status data |
| **5. Decide** | Merchant and payment systems | Apply merchant policy plus independent payment, order, provider, and settlement checks before execution |
| **6. Record** | Each participating system | Records the events it is configured to observe; credential verification alone is not an execution or settlement audit log |

> **Verification boundary:** cached keys can support local signature checks after retrieval. Current revocation requires refreshed status data, and cache policy determines how quickly a relying service observes a change.
### Credential Structure

| Field | Description |
|-------|-------------|
| `id` | `urn:grantex:passport:<ulid>` |
| `issuer` | `did:web:grantex.dev` |
| `credentialSubject.id` | Agent DID (`did:grantex:ag_...`) |
| `credentialSubject.humanPrincipal` | DID of the authorizing human |
| `credentialSubject.organizationDID` | Org DID (`did:web:<domain>`) |
| `credentialSubject.grantId` | Links to underlying Grantex grant |
| `credentialSubject.allowedMPPCategories` | `inference`, `compute`, `data`, `storage`, `search`, `media`, `delivery`, `browser`, `general` |
| `credentialSubject.maxTransactionAmount` | `{ amount, currency }` ceiling per transaction |
| `credentialSubject.delegationDepth` | Inherited from grant delegation chain |
| `credentialStatus` | StatusList2021 revocation entry |
| `proof` | Ed25519Signature2020 |

### Issue a Passport

**TypeScript:**

```typescript
import { Grantex } from '@grantex/sdk';

const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });

const passport = await grantex.passports.issue({
  agentId: 'ag_01HXYZ...',
  grantId: 'grnt_01HXYZ...',
  allowedMPPCategories: ['inference', 'compute'],
  maxTransactionAmount: { amount: 50, currency: 'USDC' },
  paymentRails: ['tempo'],
  expiresIn: '24h',
});
// passport.passportId   → "urn:grantex:passport:01HXYZ..."
// passport.encodedCredential → base64url for X-Grantex-Passport header
```

**cURL:**

```bash
curl -X POST https://api.grantex.dev/v1/passport/issue \
  -H "Authorization: Bearer $GRANTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "ag_01HXYZ...",
    "grantId": "grnt_01HXYZ...",
    "allowedMPPCategories": ["inference", "compute"],
    "maxTransactionAmount": { "amount": 50, "currency": "USDC" },
    "expiresIn": "24h"
  }'
```

### Attach to MPP Requests

```typescript
import { createMppPassportMiddleware } from '@grantex/mpp';

const middleware = createMppPassportMiddleware({ passport });
const enrichedRequest = await middleware(new Request(url, init));
// enrichedRequest now has X-Grantex-Passport header
const response = await fetch(enrichedRequest);
```

### Verify on the Merchant Side

**Standalone verification:**

```typescript
import { verifyPassport } from '@grantex/mpp';

const verified = await verifyPassport(encodedCredential, {
  requiredCategories: ['inference'],
  maxAmount: 10,
});
// verified.humanDID        → "did:grantex:user_alice"
// verified.organizationDID → "did:web:acme.com"
// verified.allowedCategories → ["inference", "compute"]
// verified.maxTransactionAmount → { amount: 50, currency: "USDC" }
```

**Express middleware (one-liner):**

```typescript
import { requireAgentPassport } from '@grantex/mpp';

app.use('/api/inference', requireAgentPassport({
  requiredCategories: ['inference'],
  maxAmount: 10,
}));
// req.agentPassport is populated on valid requests
// 403 with typed error code on invalid requests
```

### Trust Registry

Query any organization's verified trust level — no authentication required:

```bash
curl https://api.grantex.dev/v1/trust-registry/did:web:grantex.dev
# {"organizationDID":"did:web:grantex.dev","trustLevel":"soc2","domains":["grantex.dev"]}
```

```typescript
import { lookupOrgTrust } from '@grantex/mpp';

const record = await lookupOrgTrust('did:web:acme.com');
// record.trustLevel → "verified" | "soc2" | "basic"
// record.verificationMethod → "dns-txt" | "manual" | "soc2"
```

### Revocation

Revoking a passport updates server-side status immediately. Local verifiers observe the change only after an online revocation check or status-data refresh, so cached results can lag:

```typescript
await grantex.passports.revoke('urn:grantex:passport:01HXYZ...');
// Revocation-aware verification rejects after checking refreshed status data
```

### Error Codes

| Code | HTTP | Description |
|------|------|-------------|
| `PASSPORT_EXPIRED` | 403 | Credential `validUntil` has passed |
| `PASSPORT_REVOKED` | 403 | StatusList2021 bit is set |
| `INVALID_SIGNATURE` | 403 | Signature verification failed |
| `UNTRUSTED_ISSUER` | 403 | Issuer DID not in trusted list |
| `CATEGORY_MISMATCH` | 403 | Categories don't cover required service |
| `AMOUNT_EXCEEDED` | 403 | Max amount below required threshold |
| `MISSING_PASSPORT` | 403 | No `X-Grantex-Passport` header |
| `MALFORMED_CREDENTIAL` | 403 | Invalid base64url or missing VC fields |

### MPP Agent Identity API Endpoints

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `POST` | `/v1/passport/issue` | API key | Issue AgentPassportCredential |
| `GET` | `/v1/passports` | API key | List passports (filter by agentId, grantId, status) |
| `GET` | `/v1/passport/:id` | API key | Retrieve passport by ID |
| `POST` | `/v1/passport/:id/revoke` | API key | Revoke passport (StatusList2021) |
| `GET` | `/v1/trust-registry/:orgDID` | None | Look up org trust record (public) |
| `GET` | `/v1/trust-registry` | API key | List all trust records (admin) |

See [`packages/mpp/`](packages/mpp/) for full package docs. Demo: [grantex.dev/mpp-demo](https://grantex.dev/mpp-demo).

</details>

<details>
<summary><strong>SD-JWT Selective Disclosure</strong> — privacy-preserving credential presentation</summary>

## SD-JWT Selective Disclosure

Grantex supports SD-JWT (Selective Disclosure JWT) for privacy-preserving credential presentation. While a standard VC-JWT reveals all claims to every verifier, SD-JWT lets the holder choose exactly which fields to disclose — keeping everything else hidden.

### Why SD-JWT?

In agentic commerce, different verifiers need different levels of information. A payment processor needs to know the agent's scopes and budget, but not the principal's identity. A compliance auditor needs the principal and timestamps, but not the scopes. SD-JWT enables minimum-disclosure presentations that satisfy each verifier's requirements without over-sharing.

### How It Works

When exchanging an authorization code, pass `credentialFormat: "sd-jwt"` to receive an SD-JWT credential:

```typescript
const result = await grantex.tokens.exchange({
  code,
  agentId: agent.id,
  credentialFormat: 'sd-jwt',   // opt-in to SD-JWT issuance
});

console.log(result.grantToken);         // standard RS256 JWT (unchanged)
console.log(result.sdJwt);              // SD-JWT with selective disclosure
```

```python
result = client.tokens.exchange(ExchangeTokenParams(
    code=code,
    agent_id=agent.id,
    credential_format="sd-jwt",
))

print(result.sd_jwt)  # SD-JWT with selective disclosure
```

### Creating a Presentation

The holder selects which claims to disclose when presenting to a verifier:

```typescript
const presentation = await grantex.credentials.present({
  sdJwt: result.sdJwt,
  disclosedClaims: ['scopes', 'agentId'],   // only reveal these fields
});

// Send presentation to the verifier — they see scopes and agentId,
// but principalId, developerId, grantId, etc. remain hidden
```

```python
presentation = client.credentials.present(
    sd_jwt=result.sd_jwt,
    disclosed_claims=["scopes", "agent_id"],
)
```

### SD-JWT Format

An SD-JWT consists of: `<issuer-jwt>~<disclosure1>~<disclosure2>~...~`

Each disclosure is a base64url-encoded JSON array `[salt, claim-name, claim-value]`. The verifier can only see claims for which a disclosure is provided.

### Disclosable Claims

| Claim | Description |
|-------|-------------|
| `principalId` | The end-user who authorized the grant |
| `developerId` | The developer who owns the agent |
| `scopes` | The authorized scopes |
| `agentId` | The agent's DID |
| `grantId` | The grant record identifier |
| `issuedAt` | When the credential was issued |
| `expiresAt` | When the credential expires |

### SD-JWT API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/v1/token` | Exchange code for grant token + SD-JWT (with `credentialFormat: "sd-jwt"`) |
| `POST` | `/v1/credentials/verify` | Verify an SD-JWT presentation |

</details>

<details>
<summary><strong>Budget Controls</strong> — per-agent spending limits with atomic debit</summary>

## Budget Controls

Grantex provides per-grant budget controls that let developers cap how much an agent can spend. Budget allocations are enforced atomically — if a debit would exceed the remaining balance, it fails with a 402 `INSUFFICIENT_BUDGET` error. Threshold alerts fire at 50% and 80% consumption, and the remaining budget is embedded in grant tokens via the `bdg` JWT claim.

```typescript
// Allocate a budget to a grant
const budget = await grantex.budgets.allocate({
  grantId: 'grnt_01HXYZ...',
  amount: 1000,
  currency: 'USD',
});

// Debit against the budget
const debit = await grantex.budgets.debit({
  grantId: 'grnt_01HXYZ...',
  amount: 42.50,
  description: 'Flight booking',
});
console.log(debit.remaining); // 957.50

// Check the current balance
const balance = await grantex.budgets.balance('grnt_01HXYZ...');
console.log(balance.remainingBudget);

// List all budget transactions for a grant
const { transactions } = await grantex.budgets.transactions('grnt_01HXYZ...');
```

```python
# Allocate a budget to a grant
budget = client.budgets.allocate(
    grant_id="grnt_01HXYZ...",
    amount=1000,
    currency="USD",
)

# Debit against the budget
debit = client.budgets.debit(
    grant_id="grnt_01HXYZ...",
    amount=42.50,
    description="Flight booking",
)
print(debit.remaining)  # 957.50

# Check the current balance
balance = client.budgets.balance("grnt_01HXYZ...")

# List all budget transactions
transactions = client.budgets.transactions("grnt_01HXYZ...")
```

### Budget API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/v1/budget/allocate` | Create a budget allocation for a grant |
| `POST` | `/v1/budget/debit` | Debit against a grant's budget (402 if insufficient) |
| `GET` | `/v1/budget/balance/:grantId` | Get remaining balance for a grant |
| `GET` | `/v1/budget/allocations` | List all budget allocations |
| `GET` | `/v1/budget/transactions/:grantId` | List transactions and total spend for a grant |

</details>

<details>
<summary><strong>Event Streaming</strong> — real-time SSE and WebSocket</summary>

## Event Streaming

Grantex provides real-time event streaming via Server-Sent Events (SSE) and WebSocket. Subscribe to authorization lifecycle events as they happen — grant creation, revocation, token issuance, and budget threshold alerts. Events are published to both webhooks and streaming endpoints, so you can choose push or pull.

```typescript
// SSE — async generator, automatically reconnects
for await (const event of grantex.events.stream()) {
  console.log(event.type);   // 'grant.created', 'token.issued', etc.
  console.log(event.data);
}

// Convenience wrapper with a callback
grantex.events.subscribe((event) => {
  if (event.type === 'budget.threshold') {
    alert(`Grant ${event.data.grantId} is at ${event.data.percentage}% budget`);
  }
});
```

```python
# SSE — async generator
async for event in client.events.stream():
    print(event.type)   # 'grant.created', 'grant.revoked', etc.
    print(event.data)

# Convenience wrapper
async def handler(event):
    if event.type == "budget.exhausted":
        await revoke_grant(event.data["grant_id"])

await client.events.subscribe(handler)
```

### Event Types

| Event | Description |
|-------|-------------|
| `grant.created` | A new grant was approved by an end-user |
| `grant.revoked` | A grant was revoked (by user, developer, or cascade) |
| `token.issued` | A grant token was exchanged or refreshed |
| `budget.threshold` | A budget allocation crossed 50% or 80% usage |
| `budget.exhausted` | A budget allocation reached 0 remaining |

### Event Streaming API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/v1/events/stream` | SSE event stream (Bearer auth) |
| `GET` | `/v1/events/ws` | WebSocket event stream |

</details>

<details>
<summary><strong>Usage Metering</strong> — track API consumption per developer</summary>

## Usage Metering

Grantex tracks API usage per developer — token exchanges, authorization requests, and verification calls. Use the metering API to monitor consumption, enforce plan limits, and export usage data for billing.

```typescript
// Get current period usage
const usage = await grantex.usage.current();
console.log(usage.tokenExchanges);
console.log(usage.authorizations);
console.log(usage.totalRequests);

// Get usage history over the last 30 days
const { entries } = await grantex.usage.history({ days: 30 });
entries.forEach((e) => console.log(`${e.date}: ${e.totalRequests} requests`));
```

```python
# Get current period usage
usage = client.usage.current()
print(usage.token_exchanges)
print(usage.total_requests)

# Get usage history over the last 30 days
history = client.usage.history(days=30)
for entry in history.entries:
    print(f"{entry.date}: {entry.total_requests} requests")
```

### Usage Metering API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/v1/usage` | Current period usage for the authenticated developer |
| `GET` | `/v1/usage/history?days=N` | Daily usage history for the last N days |

</details>

<details>
<summary><strong>Custom Domains</strong> — register & verify domains for white-labeling</summary>

## Custom Domains

Grantex provides domain registration and DNS-based ownership verification so your account is provisioned and ready to host Grantex on your own domain. Today the API covers registration, verification, listing, and deletion; runtime traffic routing through verified domains (consent UI / API endpoints served from your domain) is on the roadmap and currently still served from `*.grantex.dev`. Track progress in [issues](https://github.com/mishrasanjeev/grantex/issues).

```typescript
// Register a custom domain
const domain = await grantex.domains.create({ domain: 'auth.yourapp.com' });
console.log(domain.verificationToken);
// → Add a DNS TXT record: _grantex-verify.auth.yourapp.com = <token>

// Verify the domain after adding the DNS record
const verified = await grantex.domains.verify(domain.id);
console.log(verified.verified); // true

// List all registered domains
const { domains } = await grantex.domains.list();

// Delete a domain
await grantex.domains.delete(domain.id);
```

```python
# Register a custom domain
domain = client.domains.create(domain="auth.yourapp.com")
print(domain.verification_token)

# Verify after adding DNS TXT record
verified = client.domains.verify(domain.id)
print(verified.verified)  # True

# List all domains
domains = client.domains.list()

# Delete a domain
client.domains.delete(domain.id)
```

### Custom Domains API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/v1/domains` | Register a new custom domain |
| `GET` | `/v1/domains` | List all registered domains |
| `POST` | `/v1/domains/:id/verify` | Verify domain ownership via DNS TXT |
| `DELETE` | `/v1/domains/:id` | Remove a custom domain |

</details>

<details>
<summary><strong>Policy-as-Code</strong> — OPA and Cedar policy backends</summary>

## Policy-as-Code

Grantex supports pluggable policy backends for fine-grained authorization decisions. In addition to the built-in policy engine, you can connect OPA (Open Policy Agent) or Cedar to evaluate authorization requests against externally managed policy bundles. Policy bundles can be synced via direct upload or triggered automatically from a git repository webhook.

```typescript
// Upload a policy bundle
await grantex.policies.sync({
  format: 'opa',           // 'opa' or 'cedar'
  bundle: bundleBuffer,    // policy bundle as Buffer
});

// List policy bundles
const { bundles } = await grantex.policies.bundles();
```

```python
# Upload a policy bundle
client.policies.sync(
    format="opa",
    bundle=bundle_bytes,
)

# List policy bundles
bundles = client.policies.bundles()
```

### Supported Policy Backends

| Backend | Description | Config |
|---------|-------------|--------|
| **Builtin** | Default rule engine — scope matching + delegation constraints | No config needed |
| **OPA** | Open Policy Agent — Rego policies evaluated at `POST /v1/data/grantex/authz` | `POLICY_BACKEND=opa`, `OPA_URL=...` |
| **Cedar** | AWS Cedar — Cedar policies evaluated at `POST /v1/is_authorized` | `POLICY_BACKEND=cedar`, `CEDAR_URL=...` |

### Policy-as-Code API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/v1/policies/sync` | Upload a policy bundle (OPA or Cedar) |
| `POST` | `/v1/policies/sync/webhook` | Git webhook trigger for policy sync |
| `GET` | `/v1/policies/bundles` | List uploaded policy bundles |

</details>

<details>
<summary><strong>DPDP Compliance</strong> — India's Digital Personal Data Protection Act 2023</summary>

## DPDP Act 2023 Compliance

DPDP Act 2023 support includes structured consent records, purpose limitation, data principal rights workflows (access, erasure, grievance), and audit-ready exports. Available in all SDKs and the CLI. This is a technical control mapping, not a legal certification.

```typescript
// Register a consent notice
const notice = await grantex.dpdp.createConsentNotice({
  noticeId: 'privacy-v1',
  version: '1.0',
  title: 'Data Processing Notice',
  content: 'We process your data for...',
  purposes: [{ code: 'analytics', description: 'Usage analytics' }],
});

// Create a consent record linked to a grant
const record = await grantex.dpdp.createConsentRecord({
  grantId: 'grnt_01HXYZ...',
  dataPrincipalId: 'user@example.com',
  purposes: [{ code: 'analytics', description: 'Usage analytics' }],
  consentNoticeId: 'privacy-v1',
  processingExpiresAt: '2027-01-01T00:00:00Z',
});

// Data principal exercises right to access (DPDP §11)
const { records } = await grantex.dpdp.listPrincipalRecords('user@example.com');

// Withdraw consent — optionally revoke grant and delete data
const withdrawal = await grantex.dpdp.withdrawConsent(record.recordId, {
  reason: 'No longer needed',
  revokeGrant: true,
  deleteProcessedData: true,
});

// Fil

More