Building an agent#
An agent needs three things: its own key, the person's approval for each site, and a way to sign every request it makes. The Python SDK does all three.
The agent's key#
python -m authyouragent keygen --name "Job-search assistant"
Output (shortened):
{
"agent_key": {
"name": "Job-search assistant",
"jwk": {"kty": "EC", "crv": "P-256", "x": "…", "y": "…"},
"pubkey_pem": "-----BEGIN PUBLIC KEY-----…",
"privkey_pem": "-----BEGIN PRIVATE KEY-----…"
}
}
privkey_pemstays on the agent's machine. Anyone with it can act as the agent, within what the person approved. Store it the way you store any secret.jwkgoes to the person, who pastes it into Your agents → New agent in the app. The app returns the agent id (ag_…).
Auth Your Agent never sees the private key. You can also generate the key yourself: any EC P-256 key pair works; register the public key as a JWK or PEM.
Create the client#
from authyouragent import AgentClient
agent = AgentClient(
base_url="https://authyouragent.com",
agent_id="ag_1a2b3c…",
privkey_pem=PRIVATE_KEY_PEM,
poll_interval=1.5, # seconds between checks while waiting for approval
timeout=60, # HTTP timeout, seconds
)
One client can talk to many sites. It keeps a separate pass for each site, because a pass is only valid for the site it was issued for.
Ask for access#
agent.ensure_grant("jobs.example.com", scopes=["list", "apply"])
siteis the site's id, normally its hostname. The site documents it.scopesare the permissions you want, as the site names them: 1 to 20 strings of lowercase letters, digits and_ : . -.
The person receives the request on their phone. ensure_grant:
- returns
Trueas soon as they approve - returns
Trueimmediately if access is already approved - raises
AgentErrorif they deny, if the request expires (5 minutes), or if they revoked the agent - with
wait=False, returnsNoneright after sending the request, without waiting
Only one request per site can be waiting at a time.
If the agent restarts within an hour of the approval, ensure_grant picks the access up again without asking the person twice.
Make requests#
r = agent.request("GET", "https://jobs.example.com/api/jobs", params={"remote": 1})
request(method, url, stepup_action=None, site=None, **kwargs) accepts the same keyword arguments as httpx (json=, params=, headers= and so on) and returns an httpx.Response. For every call it:
site is the site id you were granted. It defaults to the URL's host; pass it when you reach the site at another address, as in the walkthrough.
- picks the pass for the URL's host, renewing it if it expires within 30 seconds
- adds
Authorization: Bearer <pass>and a freshDPoPproof for this method and URL
The site id is taken from the URL's hostname, so the hostname you call must be the site id you asked access for.
Sensitive actions#
r = agent.request("POST", "https://jobs.example.com/api/jobs/j1/apply",
json={"cover_letter": "…"}, stepup_action="apply")
If the site replies 403 with stepup_required, the SDK:
- asks the person's phone to approve
applyon that site - waits for the answer (up to about five and a half minutes)
- retries the request once, with the one-time approval in
X-AuthYourAgent-Stepup
If the person denies, or doesn't answer in time, you get the original 403 response back. Without stepup_action, the SDK never asks for a step-up and returns the 403 as is.
A step-up approval can be used once, within 60 seconds of being granted.
Errors#
Everything the SDK raises is an AgentError, with a message you can show or log:
| Message starts with | Meaning |
|---|---|
approval denied by user, approval expired by user |
The person said no, or didn't answer within 5 minutes. |
agent revoked by owner |
The person blocked this agent. Stop and tell your user. |
no grant — call ensure_grant() first |
No approved access for this site, or it was revoked. |
cloud unreachable |
Network problem reaching Auth Your Agent. Safe to retry. |
Responses from the site itself (for example a 404) are returned, not raised.
JavaScript and TypeScript#
The JavaScript SDK has the same shape: new AgentClient({agentId, privateKeyPem}), await agent.ensureGrant(site, scopes, {userInfo}) and await agent.fetch(url, {method, stepupAction}), which returns a standard Response. It can also make the key: await keygen() returns {privateKeyPem, jwk}. Errors are AgentError with a code of denied, expired, timeout, revoked or no_grant.
When the agent is stuck at a login#
If a site has no Auth Your Agent support and your agent reaches a login, CAPTCHA or 2FA page, it can ask its owner to take over the browser from their phone. MCP agents need no code: add the authyouragent-mcp server (pip install "authyouragent[mcp]"). See Take over.
Other languages#
The SDKs are a thin layer over the HTTP API: an agent JWT signed with the agent key proves the agent's identity to Auth Your Agent, and DPoP proofs (RFC 9449) sign each request to the site. Any language with ES256 support can do the same.