Metadata-Version: 2.5
Name: grantex
Version: 0.4.1
Summary: Python SDK for the Grantex delegated authorization protocol — OAuth 2.0 for AI agents
Project-URL: Homepage, https://grantex.dev
Project-URL: Documentation, https://docs.grantex.dev
Project-URL: Repository, https://github.com/mishrasanjeev/grantex
Project-URL: Issues, https://github.com/mishrasanjeev/grantex/issues
Project-URL: Changelog, https://github.com/mishrasanjeev/grantex/releases
Author-email: Sanjeev Kumar <sanjeev@orchestrum.in>
Maintainer-email: Orchestrum Technologies LLP <sanjeev@orchestrum.in>, Sanjeev Kumar <mishra.sanjeev@gmail.com>
License: Apache-2.0
Keywords: agent-identity,ai-agents,authorization,delegation,did,fido2,grant-token,grantex,jwt,machine-payments,mpp,oauth,revocation,scoped-permissions,sd-jwt,verifiable-credentials,webauthn
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: cryptography>=48.0.1
Requires-Dist: httpx>=0.28
Requires-Dist: pyjwt>=2.13
Provides-Extra: dev
Requires-Dist: mypy>=1.9; extra == 'dev'
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
Requires-Dist: pytest<9,>=8.3; (python_version < '3.10') and extra == 'dev'
Requires-Dist: pytest>=9.0.3; (python_version >= '3.10') and extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff<0.16,>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# grantex

Python SDK for the [Grantex](https://grantex.dev) delegated authorization protocol — OAuth 2.0 for AI agents.

Grantex lets humans authorize AI agents with **verifiable, revocable, audited grants** built on JWT and the OAuth 2.0 model. This SDK provides a complete client for the Grantex API.

[![PyPI](https://img.shields.io/pypi/v/grantex)](https://pypi.org/project/grantex/)
[![Python](https://img.shields.io/pypi/pyversions/grantex)](https://pypi.org/project/grantex/)
[![License](https://img.shields.io/pypi/l/grantex)](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)

> **[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)**

## Install

```bash
pip install grantex
```

## Quick start

```python
from grantex import AuthorizeParams, ExchangeTokenParams, Grantex, VerifyGrantTokenOptions, verify_grant_token

client = Grantex(api_key="YOUR_API_KEY")

# 1. Start the authorization flow
request = client.authorize(AuthorizeParams(
    agent_id="ag_01HXYZ...",
    user_id="usr_01HXYZ...",
    scopes=["files:read", "email:send"],
    audience="https://api.example.com",  # optional; becomes the JWT aud claim
))

# Redirect the user to the consent page — they approve in plain language
print(request.consent_url)

# 2. Exchange the authorization code for a grant token
# (your redirect callback receives the `code` after user approves)
token = client.tokens.exchange(ExchangeTokenParams(code=code, agent_id="ag_01HXYZ..."))
print(token.grant_token)  # RS256-signed JWT
print(token.scopes)       # ('files:read', 'email:send')

# 3. Verify locally using keys retrieved from the issuer's JWKS
grant = verify_grant_token(
    token=token.grant_token,
    options=VerifyGrantTokenOptions(
        jwks_uri="https://api.grantex.dev/.well-known/jwks.json",
    ),
)
print(grant.principal_id)  # 'usr_01HXYZ...'

# 4. Revoke when done
client.tokens.revoke(grant.token_id)
```

## Local JWKS verification

Verify grant-token signatures locally using the issuer's public JWKS. The verifier
retrieves the current JWKS over the network for each call:

```python
from grantex import VerifyGrantTokenOptions, verify_grant_token

verified = verify_grant_token(
    token="eyJhbGciOiJSUzI1NiIs...",
    options=VerifyGrantTokenOptions(
        jwks_uri="https://api.grantex.dev/.well-known/jwks.json",
    ),
)

print(verified.scopes)       # ['files:read', 'email:send']
print(verified.principal_id) # 'usr_01HXYZ...'
print(verified.agent_did)    # 'did:web:...'
```

## PKCE Support

The SDK includes built-in PKCE (Proof Key for Code Exchange) support using the S256 method:

```python
from grantex import AuthorizeParams, ExchangeTokenParams, Grantex, generate_pkce

client = Grantex(api_key="YOUR_API_KEY")

# 1. Generate a PKCE challenge
pkce = generate_pkce()
# pkce.code_verifier        — random 43-char string (keep secret)
# pkce.code_challenge       — SHA-256 hash of verifier (send to server)
# pkce.code_challenge_method — 'S256'

# 2. Pass the challenge when requesting authorization
request = client.authorize(AuthorizeParams(
    agent_id="ag_01HXYZ...",
    user_id="usr_01HXYZ...",
    scopes=["files:read"],
    code_challenge=pkce.code_challenge,
    code_challenge_method=pkce.code_challenge_method,
))

# 3. Exchange the code with the verifier
token = client.tokens.exchange(ExchangeTokenParams(
    code="auth_code_from_redirect",
    agent_id="ag_01HXYZ...",
    code_verifier=pkce.code_verifier,
))
```

## Features

| Feature | Description |
|---|---|
| **Authorization flow** | `client.authorize()` — initiate consent, get grant tokens |
| **Token exchange** | `client.tokens.exchange()` — exchange an authorization code for a grant token |
| **Token management** | `client.tokens.verify()`, `.revoke()` — online verification and revocation |
| **Local verification** | `verify_grant_token()` — retrieves JWKS, then performs the RS256 signature check locally |
| **Agent management** | `client.agents.register()`, `.get()`, `.list()`, `.update()`, `.delete()` |
| **Grant management** | `client.grants.list()`, `.get()`, `.revoke()` |
| **Multi-agent delegation** | `client.grants.delegate()` — scoped sub-grants with cascade revocation |
| **Audit trail** | `client.audit.log()`, `.list()`, `.get()` — tamper-evident hash-chained log |
| **Policy engine** | `client.policies.create()`, `.list()`, `.update()`, `.delete()` |
| **Anomaly detection** | `client.anomalies.list()`, `.detect()` |
| **Compliance** | `client.compliance.get_summary()`, `.export_audit()`, `.export_grants()`, `.evidence_pack()` |
| **Webhooks** | `client.webhooks.create()`, `.list()`, `.delete()` + `verify_webhook_signature()` |
| **Billing** | `client.billing.get_subscription()`, `.create_checkout()`, `.create_portal()` |
| **SCIM 2.0** | `client.scim.create_user()`, `.list_users()`, `.get_user()`, `.update_user()`, `.delete_user()` |
| **OIDC SSO** | `client.sso.create_config()`, `.get_config()`, `.get_login_url()`, `.handle_callback()` |
| **Agent prepaid wallets** | Developer policy, principal wallet/policy/approval, and ES256 DPoP agent clients |
| **Commerce V1/OACP** | `client.commerce.get_profile()`, `.search_catalog()`, `.create_cart()`, `.get_ops_health()` |

## Agent prepaid wallets (0.4+)

```python
from grantex import (
    AgentPrepaidWalletClient,
    PrincipalPrepaidWalletClient,
    generate_dpop_key,
)

principal = PrincipalPrepaidWalletClient(
    base_url="https://grantex.dev",
    session_token=session_token,
)
principal.create_spend_policy({
    "name": "Research group budget",
    "scopeType": "group",
    "scopeId": "research-agents",
    "effect": "limit",
    "maxAmount": "1000000",
    "windowType": "month",
    "onExceed": "require_approval",
})

agent = AgentPrepaidWalletClient(
    access_token=access_token,
    private_key=generate_dpop_key(),  # persist securely across process restarts
    resource_url="https://grantex.dev/v1/prepaid-wallets",
)
result = agent.authorize_payment({
    "amount": "2500",
    "asset": "USDC",
    "network": "grantex:prepaid",
    "recipient": "merchant:data-api",
    "resource": "https://merchant.example/data",
    "scope": "data:read",
    "merchantId": "merchant:data-api",
    "purpose": "research",
    "maxTimeoutSeconds": 120,
    "idempotencyKey": logical_payment_id,
})

if result.get("status") == "approval_required":
    principal.decide_payment_approval(
        result["approvalRequestId"], "approved", "Exact request reviewed"
    )
```

The agent client creates a fresh ES256 DPoP proof for each request and binds it
to the access token, method, and exact URL. An approved retry must preserve the
original wallet, idempotency key, amount, payee, resource, and semantic context.
Grantex governs delegated spend; external issuer/custody, KYC, sanctions,
settlement, dispute, and reconciliation controls remain operator dependencies.

## Commerce V1 / OACP

```python
profile = client.commerce.get_profile(merchant_id="mch_shopify_mgx0n6_22")
products = client.commerce.search_catalog({
    "merchant_id": "mch_shopify_mgx0n6_22",
    "limit": 3,
})
```

## Configuration

```python
from grantex import Grantex

# Explicit API key
client = Grantex(api_key="gx_live_...")

# Or via environment variable
# export GRANTEX_API_KEY=gx_live_...
client = Grantex()

# Custom base URL (self-hosted)
client = Grantex(
    api_key="gx_live_...",
    base_url="https://auth.your-company.com",
)

# Custom timeout (seconds)
client = Grantex(api_key="gx_live_...", timeout=60.0)
```

The client also works as a context manager:

```python
with Grantex(api_key="gx_live_...") as client:
    agents = client.agents.list()
```

## Error handling

```python
from grantex import Grantex, GrantexApiError, GrantexAuthError, GrantexNetworkError

client = Grantex(api_key="gx_live_...")

try:
    client.agents.get("ag_invalid")
except GrantexAuthError:
    # 401 — invalid or expired API key
    pass
except GrantexApiError as e:
    # Any other API error (4xx/5xx)
    print(e.status_code, e.code, e.message)
except GrantexNetworkError:
    # Connection failure, timeout, DNS error
    pass
```

## Requirements

- Python 3.9+
- [httpx](https://www.python-httpx.org/) (sync HTTP client)
- [PyJWT](https://pyjwt.readthedocs.io/) + [cryptography](https://cryptography.io/) (for local token signature verification)

## Links

- [Documentation](https://github.com/mishrasanjeev/grantex)
- [Protocol specification](https://github.com/mishrasanjeev/grantex/blob/main/SPEC.md)
- [TypeScript SDK](https://www.npmjs.com/package/@grantex/sdk)
- [IETF Internet-Draft](https://datatracker.ietf.org/doc/draft-mishra-oauth-agent-grants/)
- [Landing page](https://grantex.dev)

## Grantex Ecosystem

| Package | Description |
|---|---|
| [`@grantex/sdk`](https://www.npmjs.com/package/@grantex/sdk) | TypeScript SDK |
| [`@grantex/langchain`](https://www.npmjs.com/package/@grantex/langchain) | LangChain integration |
| [`@grantex/autogen`](https://www.npmjs.com/package/@grantex/autogen) | AutoGen integration |
| [`@grantex/vercel-ai`](https://www.npmjs.com/package/@grantex/vercel-ai) | Vercel AI SDK integration |
| [`grantex-crewai`](https://pypi.org/project/grantex-crewai/) | CrewAI integration |
| [`grantex-openai-agents`](https://pypi.org/project/grantex-openai-agents/) | OpenAI Agents SDK integration |
| [`grantex-adk`](https://pypi.org/project/grantex-adk/) | Google ADK integration |
| [`@grantex/mcp`](https://www.npmjs.com/package/@grantex/mcp) | MCP server for Claude Desktop / Cursor / Windsurf |
| [`@grantex/cli`](https://www.npmjs.com/package/@grantex/cli) | Command-line tool |

## Scope Enforcement (v0.3.1)

Enforce tool-level permissions on **any connector** — define your own manifests or use the 53 pre-built ones.

```python
from grantex import Grantex, ToolManifest, Permission

grantex = Grantex(api_key="gx_...")

# Define a manifest for any connector — no dependency on Grantex to add support
grantex.load_manifest(ToolManifest(
    connector="my-crm",
    tools={"search": Permission.READ, "create_deal": Permission.WRITE, "delete_account": Permission.DELETE},
))

result = grantex.enforce(grant_token=token, connector="my-crm", tool="delete_account")
# result.allowed = False — "write scope does not permit delete operations"
```

**Features:**
- `enforce()` — verify JWT + check tool permission via manifest, <1ms
- `wrap_tool()` — auto-enforce on LangChain tools
- `GrantexEnforcer` — FastAPI dependency for scope enforcement
- Define custom manifests for any connector: inline, from JSON, or auto-generated via CLI
- 53 pre-built manifests included (Salesforce, HubSpot, Jira, Stripe, SAP, S3, and 47 more)
- Permission hierarchy: `admin > delete > write > read`
- Permissive mode for migration (`enforce_mode="permissive"`)

[Full Guide](https://docs.grantex.dev/guides/scope-enforcement) | [API Reference](https://docs.grantex.dev/sdks/python/enforce)

## License

Apache 2.0

## Ownership

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).
