Your agent authenticates with an API key. The external service knows who is calling. But it has no idea what the agent is actually allowed to do.
This is the gap between authentication and authorization in agent-to-service communication. Auth tokens prove identity. They do not prove permission. When agent A calls external service B, B can verify that A is a legitimate caller, but it cannot verify that A has been authorized to read customer data, trigger a payment, or access a specific endpoint.
The problem with calling home
The traditional fix is for service B to call back to A's organization and ask "is this agent allowed to do this?" That works when both services are inside the same network. It falls apart when agents interact with third-party APIs, partner services, or any system outside the org boundary. The callback adds latency, creates a runtime dependency, and requires the external service to know how to reach your authorization system in the first place.
Most teams skip this entirely. They give agents broad API keys and hope the agent only does what it should. That is not a security model. That is wishful thinking.
Scope tokens
A scope token is a signed, portable proof of what an agent is authorized to do. You create it before the agent makes an outgoing request. It lists the specific actions the agent is permitted to perform, has a time-to-live, and is signed by Asqav as an independent third party.
The token travels with the request. The receiving service verifies the signature using Asqav's public key, checks the action list and expiry, and makes its access decision. No callback to your org. No shared secret. No runtime dependency.
import asqav
asqav.init(api_key="sk_...")
agent = asqav.Agent.create("api-caller")
# Create scope token before calling external API
token = agent.create_scope_token(
actions=["data:read:customer", "api:call:external"],
ttl=3600
)
# Attach to outgoing request
import requests
requests.get("https://partner-api.com/data", headers=token.to_header())
The token gets attached as an X-Agent-Scope header. On the partner side, verification is one call.
# Partner side - verify without calling your org
received = asqav.verify_scope_token(request.headers["X-Agent-Scope"])
if received and "data:read:customer" in received.actions:
# Verified: agent is authorized for this scope
pass
What makes this different from OAuth scopes
OAuth scopes are declared during the authorization flow and embedded in the access token. The resource server trusts the authorization server that issued the token. Scope tokens work differently. They are issued per-request (or per-session), signed by an independent third party, and verifiable by anyone with the public key. The receiving service does not need a pre-existing trust relationship with your authorization server.
This matters for agent-to-agent communication. When your agent calls a partner's agent, there is no shared OAuth provider. Scope tokens give both sides a common verification mechanism without requiring either to adopt the other's auth infrastructure.
Practical constraints
Scope tokens are intentionally short-lived. The ttl parameter sets the expiry in seconds. A token created with ttl=3600 is valid for one hour. After that, the receiving service rejects it. This limits the blast radius if a token leaks.
Actions follow a hierarchical format: resource:operation:target. The format is flexible enough to model most permission structures but specific enough to verify programmatically.
Getting started
pip install asqav
Full docs at asqav.com/docs. Source on GitHub. If something does not work the way you expect, open an issue.
Top comments (0)