Querying an Agent¶
This guide explains how to get x402 access tokens and make authenticated requests to AI agents.
Overview¶
To query an AI agent, subscribers need to:
- Have an active subscription to a plan associated with the agent
- Generate an x402 access token
- Include the token in requests to the agent
Get X402 Access Token¶
The x402 access token authorizes requests to agents and enables credit verification/settlement.
from payments_py import Payments, PaymentOptions
payments = Payments.get_instance(
PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox")
)
# Generate access token (basic — no delegation config needed for credit/fiat plans)
result = payments.x402.get_x402_access_token(
plan_id="your-plan-id",
agent_id="agent-id" # Optional but recommended
)
access_token = result['accessToken']
print(f"Access Token: {access_token[:50]}...")
print(f"Token version: {result.get('tokenVersion')}") # 2 or 3, absent if the mint returned no token
Token Generation Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
plan_id |
str |
Yes | The payment plan ID |
agent_id |
str |
No | Target agent ID (recommended) |
token_options |
X402TokenOptions |
No | Scheme, delegation, resource binding and requested token version |
X402TokenOptions fields:
| Field | Type | Description |
|---|---|---|
scheme |
str |
x402 scheme (defaults to nvm:erc4337) |
network |
str |
Network identifier (auto-derived from the scheme if omitted) |
delegation_config |
DelegationConfig |
Delegation to mint against |
resource |
str | X402Resource |
The protected resource the token is minted for. Signed on v3 |
http_verb |
str |
HTTP verb of that resource (e.g. "POST"). Signed on v3 |
token_version |
int |
Access-token version to request (2 or 3). Never read it back from here — see below |
The result is a dict with:
| Key | Type | Description |
|---|---|---|
accessToken |
str |
The access token to send in payment-signature |
tokenVersion |
int |
2 or 3, detected from the returned token, not from what was requested |
Token Generation with Delegation¶
For erc4337 (crypto) plans, token generation uses delegations. The supported
flow is create-first: create the delegation once, then reference it by
delegation_id on every token request. Delegations are plan-agnostic by
default, so a single delegation can back token requests across many plans.
from payments_py.x402 import X402TokenOptions, DelegationConfig, CreateDelegationPayload
# Step 1 — create the delegation once. `currency` is required.
delegation = payments.delegation.create_delegation(
CreateDelegationPayload(
provider="erc4337",
spending_limit_cents=10000, # $100
duration_secs=604800, # 1 week
currency="usdc",
)
)
# Step 2 — request a token referencing the delegation id (reusable across plans).
result = payments.x402.get_x402_access_token(
plan_id="your-plan-id",
agent_id="agent-id",
token_options=X402TokenOptions(
delegation_config=DelegationConfig(
delegation_id=delegation.delegation_id
)
)
)
Deprecated: passing
spending_limit_cents/duration_secs(or a payment method) directly toget_x402_access_token— inline "create-on-the-fly" — still works but emits aDeprecationWarningand will be removed in a future release. Create the delegation first as shown above.
Single-Use Tokens (v3)¶
By default the backend mints a v2 token: a reusable bearer credential you can keep for the lifetime of your client. Opt into a v3 token to get one that is bound to a single seller endpoint and is consumed by its first settle:
result = payments.x402.get_x402_access_token(
plan_id="your-plan-id",
agent_id="agent-id",
token_options=X402TokenOptions(
delegation_config=DelegationConfig(delegation_id=delegation.delegation_id),
resource="https://seller.example/api/v1/tasks",
http_verb="POST",
token_version=3,
),
)
if result.get("tokenVersion") == 3:
... # do NOT reuse: mint a fresh token for the next paid request
Requesting v3 is not a guarantee of getting v3 — a backend that predates v3
support drops the field silently and returns v2. Always read
result.get("tokenVersion") (or is_single_use_access_token(access_token)), never the
value you passed in. Full details, including the BCK.X402.0059
"already used" error, are in
x402 Payment Protocol.
Make Requests to Agents¶
Using the x402 Payment Header¶
Include the access token in the payment-signature header (per x402 v2 HTTP transport spec):
import requests
# Get access token
token_result = payments.x402.get_x402_access_token(
plan_id=plan_id,
agent_id=agent_id
)
access_token = token_result['accessToken']
# Make request to the agent
response = requests.post(
f"https://agent-api.example.com/agents/{agent_id}/tasks",
headers={
"payment-signature": access_token,
"Content-Type": "application/json"
},
json={
"task": "Analyze this data",
"data": {"key": "value"}
}
)
if response.status_code == 200:
result = response.json()
print(f"Agent response: {result}")
elif response.status_code == 402:
print("Payment required - check plan balance")
else:
print(f"Error: {response.status_code}")
Decode Access Token¶
You can decode the token to inspect its contents:
from payments_py.x402.token import decode_access_token
decoded = decode_access_token(access_token)
if decoded:
payload = decoded.get('payload', {})
authorization = payload.get('authorization', {})
print(f"Subscriber: {authorization.get('from')}")
print(f"Plan ID: {authorization.get('planId')}")
print(f"Agent ID: {authorization.get('agentId')}")
# v3 tokens additionally carry the signed binding and a one-time nonce.
print(f"Resource URL: {authorization.get('resourceUrl')}")
print(f"HTTP verb: {authorization.get('httpVerb')}")
print(f"Nonce: {authorization.get('nonce')}")
The nonce is what distinguishes the two versions — but use the helpers rather
than reading it yourself:
from payments_py.x402 import detect_access_token_version, is_single_use_access_token
detect_access_token_version(access_token) # 2 or 3
is_single_use_access_token(access_token) # True / False
Complete Example¶
from payments_py import Payments, PaymentOptions
from payments_py.common.types import PlanMetadata, AgentMetadata, AgentAPIAttributes
from payments_py.plans import get_free_price_config, get_fixed_credits_config
import requests
# Initialize as builder
builder = Payments.get_instance(
PaymentOptions(nvm_api_key="nvm:builder-key", environment="sandbox")
)
# Initialize as subscriber
subscriber = Payments.get_instance(
PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox")
)
# 1. Builder creates plan and agent
plan_result = builder.plans.register_credits_plan(
plan_metadata=PlanMetadata(name="API Access Plan"),
price_config=get_free_price_config(), # Free for demo
credits_config=get_fixed_credits_config(100)
)
plan_id = plan_result['planId']
agent_result = builder.agents.register_agent(
agent_metadata=AgentMetadata(name="Demo Agent"),
# AgentAPIAttributes() with no args registers a minimal agent — your
# Payments library middleware handles per-route gating. See
# docs/api/04-agents.md for opt-in Additional Security.
agent_api=AgentAPIAttributes(),
payment_plans=[plan_id]
)
agent_id = agent_result['agentId']
# 2. Subscriber orders the plan
subscriber.plans.order_plan(plan_id)
# 3. Subscriber generates access token
token_result = subscriber.x402.get_x402_access_token(
plan_id=plan_id,
agent_id=agent_id
)
access_token = token_result['accessToken']
# 4. Subscriber makes request to agent
response = requests.post(
"https://api.example.com/tasks",
headers={
"payment-signature": access_token,
"Content-Type": "application/json"
},
json={"prompt": "Hello, agent!"}
)
print(f"Status: {response.status_code}")
print(f"Response: {response.json()}")
# 5. Check updated balance
balance = subscriber.plans.get_plan_balance(plan_id)
print(f"Remaining credits: {balance.balance}")
Error Handling¶
402 Payment Required¶
response = requests.post(agent_endpoint, headers=headers, json=payload)
if response.status_code == 402:
error = response.json()
print(f"Payment required: {error}")
# Check balance
balance = payments.plans.get_plan_balance(plan_id)
if balance.balance <= 0:
print("No credits remaining - order more credits")
elif not balance.is_subscriber:
print("Not subscribed - order the plan first")
Token Expired or Invalid¶
try:
response = requests.post(agent_endpoint, headers=headers, json=payload)
response.raise_for_status()
except requests.HTTPError as e:
if e.response.status_code == 401:
print("Token expired or invalid - generate a new token")
# Regenerate token
new_token = payments.x402.get_x402_access_token(plan_id, agent_id, token_options=token_options)
Request Flow Diagram¶
sequenceDiagram
participant Subscriber
participant Nevermined
participant Agent
Subscriber->>Nevermined: get_x402_access_token()
Nevermined-->>Subscriber: accessToken
Subscriber->>Agent: POST /tasks<br/>payment-signature: token
Agent->>Nevermined: verify_permissions()
Nevermined-->>Agent: {isValid: true}
Agent->>Nevermined: settle_permissions()
Nevermined-->>Agent: {success: true}
Agent-->>Subscriber: Response
Next Steps¶
- Request Validation - How agents validate incoming requests
- x402 Protocol - Deep dive into x402 verification and settlement