Skip to main content

Why Scope Enforcement Matters

AI agents call real APIs — Salesforce, Jira, Stripe, S3 — using shared service account credentials. The credentials themselves do not restrict what an agent can do. Without enforcement, an agent authorized to read contacts can also delete contacts if the underlying credential allows it. Grantex scope enforcement closes this gap: every tool call is checked against an explicit permission manifest before execution, ensuring agents can only perform the actions their grant token authorizes.

Quick Start

Add scope enforcement to your agent in five lines:
from grantex import Grantex
from grantex.manifests.salesforce import manifest as sf

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

result = grantex.enforce(grant_token=token, connector="salesforce", tool="delete_contact")
if not result.allowed:
    raise PermissionError(result.reason)
That is all you need. The enforce() call decodes the JWT, resolves the tool’s required permission from the manifest, and checks whether the token’s scopes cover it.

Loading Manifests

Grantex ships 54 pre-built manifests covering finance, HR, marketing, ops, and comms connectors. Load them before calling enforce().

Pre-Built Manifests

from grantex import Grantex
from grantex.manifests.salesforce import manifest as sf
from grantex.manifests.hubspot import manifest as hs
from grantex.manifests.jira import manifest as jira

grantex = Grantex(api_key="gx_...")
grantex.load_manifests([sf, hs, jira])

Custom Manifests

For internal APIs or connectors Grantex does not ship, define your own manifest inline, from a JSON file, or by extending a pre-built manifest. Inline definition:
from grantex import ToolManifest, Permission

grantex.load_manifest(ToolManifest(
    connector="inventory-service",
    description="Internal warehouse inventory API",
    tools={
        "get_stock_level":   Permission.READ,
        "reserve_inventory": Permission.WRITE,
        "force_stock_reset": Permission.ADMIN,
    },
))
From a JSON file:
grantex.load_manifest(ToolManifest.from_file("./manifests/inventory-service.json"))
Extend a pre-built manifest:
from grantex.manifests.salesforce import manifest as sf_manifest

sf_manifest.add_tool("bulk_delete_all", Permission.ADMIN)
sf_manifest.add_tool("export_all_contacts", Permission.READ)

grantex.load_manifest(sf_manifest)

LangChain Integration

Wrap any LangChain tool with wrap_tool() to enforce scope checks automatically on every invocation:
protected_tool = grantex.wrap_tool(
    my_langchain_tool,
    connector="salesforce",
    tool="create_lead",
    grant_token=lambda: current_state["grant_token"],
)

# Use protected_tool in your LangChain agent — enforcement happens
# automatically before every call. If the token lacks write scope,
# the tool raises PermissionError instead of executing.

Express / FastAPI Middleware

Add one-line enforcement to your tool execution endpoints:
import { Grantex } from '@grantex/sdk';
import { salesforceManifest } from '@grantex/sdk/manifests/salesforce';

const grantex = new Grantex({ apiKey: 'gx_...' });
grantex.loadManifest(salesforceManifest);

app.use('/api/tools/*', grantex.enforceMiddleware({
  extractToken: (req) => req.headers.authorization?.replace('Bearer ', ''),
  extractConnector: (req) => req.params.connector,
  extractTool: (req) => req.params.tool,
}));

CLI Workflow

The CLI provides a complete manifest workflow: list available manifests, validate your agent’s tools against them, and dry-run enforcement.
# Browse all 54 pre-built manifests
grantex manifest list

# Filter by category
grantex manifest list --category finance

# Show tools in a specific manifest
grantex manifest show salesforce

# Validate that your agent's tools all have manifest entries
grantex manifest validate --agent-tools create_lead,query,delete_contact --connector salesforce

# Dry-run enforcement against a real token
grantex enforce test --token "eyJ..." --connector salesforce --tool delete_contact
See CLI manifest reference and CLI enforce reference for full details.

Permission Hierarchy

Grantex uses a four-level permission hierarchy. Higher levels subsume all lower levels:
LevelPermissionAllowsExample Tools
0readQuery, list, get, search, fetch, downloadget_contact, list_invoices, search_emails
1writeRead + create, update, send, uploadcreate_lead, send_email, update_issue
2deleteRead + write + delete, remove, void, terminatedelete_contact, void_envelope, reject_application
3adminEverythingrun_payroll, run_period_close, force_stock_reset
Coverage rules:
Granted ScopeREAD ToolsWRITE ToolsDELETE ToolsADMIN Tools
tool:X:readAllowedDeniedDeniedDenied
tool:X:writeAllowedAllowedDeniedDenied
tool:X:deleteAllowedAllowedAllowedDenied
tool:X:adminAllowedAllowedAllowedAllowed

Scope Format

Tool enforcement scopes use the format:
tool:{connector}:{permission}:{resource}[:capped:{N}]
Examples:
ScopeMeaning
tool:salesforce:write:*Write access to all Salesforce tools
tool:s3:read:*Read-only access to S3 tools
tool:stripe:write:*:capped:500Write access to Stripe tools, capped at $500 per operation
tool:jira:admin:*Full admin access to all Jira tools

Complete Example

A full agent with scope enforcement from token exchange to tool execution:
import os
from grantex import Grantex, ExchangeTokenParams
from grantex.manifests.salesforce import manifest as sf
from grantex.manifests.gmail import manifest as gmail

# 1. Initialize client and load manifests
grantex = Grantex(api_key=os.environ["GRANTEX_API_KEY"])
grantex.load_manifests([sf, gmail])

# 2. Exchange authorization code for a grant token
token_response = grantex.tokens.exchange(ExchangeTokenParams(
    code="auth_code_from_callback",
    agent_id="agt_sales_rep",
))
grant_token = token_response.grant_token
# Token scopes: ["tool:salesforce:write:*", "tool:gmail:read:*"]

# 3. Before every tool call, enforce scope
def call_tool(connector: str, tool: str, **kwargs):
    result = grantex.enforce(
        grant_token=grant_token,
        connector=connector,
        tool=tool,
    )
    if not result.allowed:
        raise PermissionError(f"Agent denied: {result.reason}")

    # Proceed with the actual tool execution
    return execute_connector_tool(connector, tool, **kwargs)

# 4. These calls succeed
call_tool("salesforce", "create_lead", name="Acme Corp")     # write -> write: allowed
call_tool("salesforce", "query", soql="SELECT Id FROM Lead") # write -> read: allowed
call_tool("gmail", "search_emails", query="from:acme")       # read -> read: allowed

# 5. These calls are denied
call_tool("salesforce", "delete_contact", id="003xx")        # write -> delete: DENIED
call_tool("gmail", "send_email", to="ceo@acme.com")          # read -> write: DENIED