> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inviolet.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Call the Inviolet gateway from Python over REST — mint mandates and guard tool calls with httpx.

<Note>
  Inviolet does not ship a first-party Python package. From Python you call the
  gateway's REST API directly — the same endpoints the Node client wraps. The
  examples below use [`httpx`](https://www.python-httpx.org/), but any HTTP
  client works.
</Note>

## Setup

```bash theme={"dark"}
pip install httpx
```

```python theme={"dark"}
import os, httpx

GATEWAY = os.environ["INVIOLET_GATEWAY_URL"]
HEADERS = {"authorization": f"Bearer {os.environ['INVIOLET_API_KEY']}"}
```

## Mint a mandate

Wraps `POST /v1/mandate/dispense`. The requested scope must be a subset of the
[intent card's](/concepts/intent-cards) allowed scope.

```python theme={"dark"}
mandate = httpx.post(f"{GATEWAY}/v1/mandate/dispense", headers=HEADERS, json={
    "actor": "alice@example.com",
    "intent_id": "intents:customer_support_lookup",
    "requested_scope": {
        "operations": ["postgres.select"],
        "resources": ["contacts.first_name", "contacts.last_name"],
    },
    "audience": "postgres-prod",
    # Optional: forward to a credential broker
    # "broker_dispense": {"kind": "vault", "vault_role": "inviolet-support"},
}).json()
# → { "credential": "<jwt>", "cred_id": ..., "expires_at": ..., "ttl_seconds": ... }
```

## Guard a tool call

Attach the mandate JWT as the `X-Inviolet-Mandate` header and post the action to
the decision engine.

```python theme={"dark"}
resp = httpx.post(
    f"{GATEWAY}/v1/mcp-proxy/call",
    headers={**HEADERS, "x-inviolet-mandate": mandate["credential"]},
    json={
        "action": {
            "surface": "sdk",
            "operation": "postgres.select",
            "arguments": {"table": "contacts", "id": 42},
        },
        "audience": "postgres-prod",
    },
)
verdict = resp.json()

if not verdict["kind"].startswith("allow"):
    # kind == "approval" -> queued for a human; "step_up" -> IdP challenge;
    # "deny_with_reroute" -> fall back; else hard deny.
    raise RuntimeError(verdict.get("message", "Denied by Inviolet"))
```

## Verifying mandates yourself

The gateway publishes its public keys at `/v1/mandate/jwks`. Verify the RS256
signature locally with any JWT library (e.g. `pyjwt` + `PyJWKClient`):

```python theme={"dark"}
import jwt
from jwt import PyJWKClient

jwks = PyJWKClient(f"{GATEWAY}/v1/mandate/jwks")
signing_key = jwks.get_signing_key_from_jwt(mandate["credential"])
claims = jwt.decode(
    mandate["credential"], signing_key.key,
    algorithms=["RS256"], audience="postgres-prod",
)
print(claims["inviolet_intent_id"])
```

## Read next

* **[REST reference](/sdks/rest)** — every endpoint, field, and response shape
* **[Mandates](/concepts/mandates)** — the JWT format and lifecycle
* **[API Reference](/api-reference/http-api)** — the full HTTP surface
