Skip to main content

Overview

The enforce API verifies that an agent’s grant token includes sufficient scope to call a specific tool on a specific connector. It combines JWT verification with manifest-based permission resolution in a single call.
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:
    print(result.reason)  # "write scope does not cover delete operations on salesforce"

enforce()

Check whether a grant token permits a tool call.
def enforce(
    self,
    grant_token: str,
    connector: str,
    tool: str,
    amount: float | None = None,
) -> EnforceResult

Parameters

ParameterTypeRequiredDescription
grant_tokenstrYesThe JWT grant token issued by Grantex. Decoded and verified inline.
connectorstrYesThe connector name to check against (e.g., "salesforce", "s3"). Must match a loaded manifest.
toolstrYesThe tool name to check (e.g., "delete_contact", "create_lead"). Must be declared in the connector’s manifest.
amountfloat | NoneNoOptional amount for capped scopes. When the token includes a capped scope like tool:stripe:write:*:capped:500, pass the transaction amount to check against the cap.

Response: EnforceResult

FieldTypeDescription
allowedboolTrue if the tool call is permitted by the token’s scopes.
reasonstrHuman-readable reason when allowed is False. Empty string when allowed.
grant_idstrThe grant ID extracted from the JWT grnt (or jti) claim.
agent_didstrThe agent DID extracted from the JWT agt claim.
scopeslist[str]All scopes from the JWT scp claim.
permissionstrThe resolved permission level for this tool from the manifest ("read", "write", "delete", or "admin").
connectorstrThe connector name that was checked.
toolstrThe tool name that was checked.

Example

result = grantex.enforce(
    grant_token="eyJhbGciOiJSUzI1NiIs...",
    connector="salesforce",
    tool="create_lead",
)

if result.allowed:
    print(f"Allowed: {result.tool} on {result.connector}")
    print(f"Grant: {result.grant_id}, Agent: {result.agent_did}")
else:
    print(f"Denied: {result.reason}")

Capped Scopes

When a token includes a capped scope, pass the amount to enforce against the cap:
result = grantex.enforce(
    grant_token=token,
    connector="stripe",
    tool="create_payment_intent",
    amount=750,
)
# If token scope is tool:stripe:write:*:capped:500
# result.allowed = False
# result.reason = "amount 750 exceeds cap of 500 on tool:stripe:write:*:capped:500"

load_manifest()

Load a single tool manifest into the client. Must be called before enforce() for the corresponding connector.
def load_manifest(self, manifest: ToolManifest) -> None

Example

from grantex.manifests.salesforce import manifest as sf

grantex.load_manifest(sf)

load_manifests()

Load multiple tool manifests at once.
def load_manifests(self, manifests: list[ToolManifest]) -> None

Example

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.load_manifests([sf, hs, jira])

ToolManifest

A manifest declares the permission level required for each tool on a connector.
class ToolManifest:
    connector: str
    description: str
    version: str

    def __init__(
        self,
        connector: str,
        tools: dict[str, Permission],
        description: str = "",
        version: str = "1.0.0",
    ) -> None: ...

    @property
    def tool_count(self) -> int: ...

    def get_permission(self, tool_name: str) -> Permission | None: ...
    def add_tool(self, tool_name: str, permission: Permission) -> None: ...

    @classmethod
    def from_file(cls, path: str) -> "ToolManifest": ...

    @classmethod
    def from_dict(cls, data: dict) -> "ToolManifest": ...

Constructor

from grantex import ToolManifest, Permission

manifest = ToolManifest(
    connector="inventory-service",
    description="Internal warehouse inventory API",
    version="1.0.0",
    tools={
        "get_stock_level":   Permission.READ,
        "reserve_inventory": Permission.WRITE,
        "force_stock_reset": Permission.ADMIN,
    },
)

get_permission()

Look up the required permission for a tool. Returns None if the tool is not in the manifest.
perm = manifest.get_permission("reserve_inventory")
# Permission.WRITE

unknown = manifest.get_permission("nonexistent_tool")
# None

add_tool()

Add a tool to an existing manifest. Useful for extending pre-built manifests with custom tools.
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)

print(sf_manifest.tool_count)  # 8 (6 built-in + 2 custom)

from_file()

Create a manifest from a JSON file on disk:
manifest = ToolManifest.from_file("./manifests/inventory-service.json")
grantex.load_manifest(manifest)
The JSON file format:
{
  "connector": "inventory-service",
  "version": "1.0.0",
  "description": "Internal warehouse inventory API",
  "tools": {
    "get_stock_level": "read",
    "reserve_inventory": "write",
    "force_stock_reset": "admin"
  }
}

from_dict()

Create a manifest from a Python dictionary:
manifest = ToolManifest.from_dict({
    "connector": "pricing-engine",
    "tools": {
        "get_price": "read",
        "set_price": "write",
        "reset_all_prices": "admin",
    },
})

Permission

A class representing the four permission levels in the hierarchy.
class Permission:
    READ   = "read"    # Level 0
    WRITE  = "write"   # Level 1
    DELETE = "delete"  # Level 2
    ADMIN  = "admin"   # Level 3
Higher levels subsume all lower levels: admin > delete > write > read.

covers()

Check whether this permission level covers a required permission level:
Permission.WRITE.covers(Permission.READ)    # True  -- write covers read
Permission.WRITE.covers(Permission.DELETE)   # False -- write does not cover delete
Permission.ADMIN.covers(Permission.DELETE)   # True  -- admin covers everything
Permission.READ.covers(Permission.READ)      # True  -- exact match

is_valid()

Check whether a string is a valid permission level:
Permission.is_valid("write")    # True
Permission.is_valid("execute")  # False

wrap_tool()

Wrap a LangChain StructuredTool so that enforcement runs automatically before every invocation.
def wrap_tool(
    self,
    tool: StructuredTool,
    connector: str,
    tool_name: str,
    grant_token: str | Callable[[], str],
) -> StructuredTool

Example

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 chain.
# If the token lacks sufficient scope, the tool raises
# PermissionError instead of executing the underlying function.

FastAPI Integration

Use GrantexAuth from grantex_fastapi as a FastAPI dependency for automatic enforcement on tool execution routes:
from grantex_fastapi import GrantexAuth

enforcer = GrantexAuth(grantex)

@app.post("/api/tools/{connector}/{tool}")
async def execute_tool(
    connector: str,
    tool: str,
    auth: EnforceResult = Depends(enforcer),
):
    # auth.allowed is guaranteed True here.
    # If the token lacked sufficient scope, a 403 was raised automatically.
    print(f"Executing {auth.tool} on {auth.connector} for grant {auth.grant_id}")
    ...