<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>O2 Identity Blog</title>
        <link>https://o2id.dev/blog</link>
        <description>O2 Identity Blog</description>
        <lastBuildDate>Wed, 22 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Authenticating AI Agents with O2ID]]></title>
            <link>https://o2id.dev/blog/authenticating-ai-agents-with-o2id</link>
            <guid>https://o2id.dev/blog/authenticating-ai-agents-with-o2id</guid>
            <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Every AI agent eventually needs to answer two very different questions:]]></description>
            <content:encoded><![CDATA[<p>Every AI agent eventually needs to answer two very different questions:
<em>"who am I?"</em> and <em>"who am I acting for, right now?"</em> O2ID gives an
<a class="" href="https://o2id.dev/docs/managing-agents">AI agent</a> its own identity — distinct from both a
human <code>User</code> and an OAuth <code>Application</code> — and two separate flows for those
two questions. This post walks through both, using a small agent as a
running example, and finishes by using the resulting token to call an
external API.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-example-ada-a-support-agent">The example: Ada, a support agent<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#the-example-ada-a-support-agent" class="hash-link" aria-label="Direct link to The example: Ada, a support agent" title="Direct link to The example: Ada, a support agent" translate="no">​</a></h2>
<p><strong>Ada</strong> is a support agent for a SaaS product that runs its customer
identity on O2ID. It does two kinds of work:</p>
<ol>
<li class="">Overnight, unattended, it works through the backlog of open support
tickets — reading free-form customer messages, judging severity and
sentiment, merging obvious duplicates, drafting suggested replies for
the team to review in the morning. None of this touches any one
customer's account or authority; it's Ada reading and organizing its
own ticket queue, as itself.</li>
<li class="">During the day, a customer chatting live with Ada might ask it to do
something to <em>their own account</em> — "cancel my subscription," "why was
I charged twice." For that, Ada has to act <em>as that specific customer</em>,
so the action is attributable to the person who actually asked for it,
not to a bot with broad access to every account.</li>
</ol>
<p>Those are exactly the two flows O2ID supports: <strong>own token</strong> and <strong>acting
on behalf of a human</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-0-register-the-agent-and-its-key">Step 0: Register the agent and its key<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#step-0-register-the-agent-and-its-key" class="hash-link" aria-label="Direct link to Step 0: Register the agent and its key" title="Direct link to Step 0: Register the agent and its key" translate="no">​</a></h2>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">AGENT_JSON</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">./o2idctl agents create </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--name</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"Ada"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--scope</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> users:read</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">AGENT_INTERNAL_ID</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">printf</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'%s'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$AGENT_JSON</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> jq </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> .id</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">AGENT_ID</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">printf</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'%s'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$AGENT_JSON</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> jq </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> .agentId</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><br></div></code></pre></div></div>
<p>Every agent has two IDs, and mixing them up is the single easiest mistake
to make here: <code>AGENT_INTERNAL_ID</code> (the plain <code>id</code> field) is for <em>managing</em>
the agent — <code>agents get</code>/<code>update</code>/<code>delete</code>, and the mandate endpoints
below. <code>AGENT_ID</code> (the <code>agent_</code>-prefixed <code>agentId</code> field) is the agent's
<em>public</em> identity — it only ever shows up inside an OAuth request, as a
<code>client_id</code> or a JWT <code>iss</code>/<code>sub</code> claim. Passing one where the other is
expected fails with <code>agent not found</code>, since they're looked up in
completely different ways.</p>
<p>Ada runs unattended overnight, so it authenticates with an asymmetric key
instead of a shared secret (see
<a class="" href="https://o2id.dev/docs/managing-agents#registering-a-public-key-for-unattended-ai-agents">Registering a public key</a>).
Generate a keypair and derive the JWK O2ID expects:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> base64</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> cryptography</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hazmat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">primitives</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">asymmetric </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> rsa</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> cryptography</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hazmat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">primitives </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> serialization</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">b64url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> byte_len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> base64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">urlsafe_b64encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to_bytes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">byte_len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"big"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">rstrip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">b"="</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">decode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">private_key </span><span class="token operator">=</span><span class="token plain"> rsa</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">generate_private_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">public_exponent</span><span class="token operator">=</span><span class="token number">65537</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> key_size</span><span class="token operator">=</span><span class="token number">2048</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">numbers </span><span class="token operator">=</span><span class="token plain"> private_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">public_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">public_numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">open</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ada-private.pem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"wb"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">private_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">private_bytes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        encoding</span><span class="token operator">=</span><span class="token plain">serialization</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Encoding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">PEM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">format</span><span class="token operator">=</span><span class="token plain">serialization</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">PrivateFormat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">PKCS8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        encryption_algorithm</span><span class="token operator">=</span><span class="token plain">serialization</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">NoEncryption</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">open</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ada-public.jwk.json"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"w"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dump</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"kty"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"RSA"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"n"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> b64url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"e"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> b64url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">./o2idctl agents update </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$AGENT_INTERNAL_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> --public-key ./ada-public.jwk.json</span><br></div></code></pre></div></div>
<p>O2ID only ever stores the public half. The private key stays with the
agent process and is used to sign a short-lived assertion on every token
request.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flow-1-getting-its-own-token-no-human-involved">Flow 1: getting its own token (no human involved)<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#flow-1-getting-its-own-token-no-human-involved" class="hash-link" aria-label="Direct link to Flow 1: getting its own token (no human involved)" title="Direct link to Flow 1: getting its own token (no human involved)" translate="no">​</a></h2>
<p>For the overnight ticket-triage run, there's no human session to
borrow — Ada authenticates as itself via the
<a class="" href="https://o2id.dev/docs/client-credentials-grant">Client Credentials Grant</a>,
proving its identity with a signed <code>private_key_jwt</code> instead of a client
secret:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> time</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> jwt  </span><span class="token comment" style="color:rgb(98, 114, 164)"># PyJWT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> requests</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">O2ID_URL </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://your-o2id-host"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">open</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ada-private.pem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"rb"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    private_key_pem </span><span class="token operator">=</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">assertion </span><span class="token operator">=</span><span class="token plain"> jwt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"iss"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> AGENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"sub"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> AGENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># RFC 7523 requires iss == sub</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"aud"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">O2ID_URL</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">/oauth2/token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"exp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">120</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    private_key_pem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    algorithm</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"RS256"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">resp </span><span class="token operator">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">O2ID_URL</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">/oauth2/token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"grant_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"client_credentials"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"client_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> AGENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"client_assertion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> assertion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"client_assertion_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"scope"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"users:read"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">agent_token </span><span class="token operator">=</span><span class="token plain"> resp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p><code>agent_token</code>'s <code>sub</code> claim is Ada's own <code>agentId</code>, scoped only to what
the agent itself was granted (<code>users:read</code> here) — there's no mandate, no
<code>act</code> claim, and no human role scope in this path at all. Ada uses it to
cross-reference which O2ID customer record each ticket belongs to and
check account standing while triaging — read-only work done as itself,
against no one customer's authority in particular.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="checking-that-claim-inspecting-the-token">Checking that claim: inspecting the token<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#checking-that-claim-inspecting-the-token" class="hash-link" aria-label="Direct link to Checking that claim: inspecting the token" title="Direct link to Checking that claim: inspecting the token" translate="no">​</a></h3>
<p><code>agent_token</code> isn't a JWT — it's an opaque random string, so there's
nothing to decode locally the way you'd decode a JWT on jwt.io. To
actually see the <code>sub</code> claim (or anything else) it carries, use
<a class="" href="https://o2id.dev/docs/oauth-oidc#token-introspection">token introspection</a>
(<code>POST /oauth2/introspect</code>, RFC 7662). It requires the <code>tokens:introspect</code>
scope — an operator/admin credential, not something the agent itself needs:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-X</span><span class="token plain"> POST </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$O2ID_URL</span><span class="token string" style="color:rgb(255, 121, 198)">/oauth2/introspect"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ADMIN_TOKEN</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/x-www-form-urlencoded"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --data-urlencode </span><span class="token string" style="color:rgb(255, 121, 198)">"token=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$agent_token</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"active"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"scope"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"users:read"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"client_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"agent_e241a1c4447bdc04d755a707dd0dec76"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"sub"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"agent_e241a1c4447bdc04d755a707dd0dec76"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"token_type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Bearer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"exp"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1735689600</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>That's it — <code>sub</code> is Ada's own <code>agentId</code>, exactly as described above, and
there's no <code>act</code> field at all. Introspecting an unknown, expired, or
malformed token never errors — it's always a <code>200</code> with just
<code>{"active": false}</code>, so this same call is also the right way to check
<em>"is this token still good?"</em> before using it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flow-2-acting-on-behalf-of-a-human">Flow 2: acting on behalf of a human<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#flow-2-acting-on-behalf-of-a-human" class="hash-link" aria-label="Direct link to Flow 2: acting on behalf of a human" title="Direct link to Flow 2: acting on behalf of a human" translate="no">​</a></h2>
<p>Maya, a customer, is live in a chat with Ada right now, asking it to
cancel her subscription. Because Maya is present and is authorizing
exactly this one action, Ada's backend doesn't need any mandate set up in
advance — it creates one on the spot, scoped tightly and expiring in a
few minutes, and redeems it immediately, in the same round trip:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-X</span><span class="token plain"> POST </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$API_URL</span><span class="token string" style="color:rgb(255, 121, 198)">/agents/</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$AGENT_INTERNAL_ID</span><span class="token string" style="color:rgb(255, 121, 198)">/mandates"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$MAYA_TOKEN</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"scope": ["users:read"], "expiresAt": "2026-07-22T09:15:00Z"}'</span><br></div></code></pre></div></div>
<p>A mandate created this way is no different from one set up days ahead —
same record, same lookup at redemption time — only the timing differs.
(This requires Maya's own role to include the <code>mandates:manage</code> scope;
since a mandate can only ever be created for the caller themself, that's
safe to grant to every ordinary customer, not just administrators.)</p>
<p>With the mandate in place — even by a few seconds — Ada redeems it
through <a class="" href="https://o2id.dev/docs/token-exchange#actor-token-delegation">Token Exchange</a>.
Maya's token is the <code>subject_token</code>; the agent's <em>own</em> token from Flow 1
is the <code>actor_token</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">resp </span><span class="token operator">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">O2ID_URL</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">/oauth2/token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"grant_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:grant-type:token-exchange"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"client_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> FRONT_DOOR_CLIENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"subject_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> maya_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"subject_token_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:token-type:access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"actor_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"actor_token_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:token-type:access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"scope"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"users:read"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">delegated_token </span><span class="token operator">=</span><span class="token plain"> resp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>There's no <code>mandate_id</code> to pass — O2ID looks the mandate up implicitly by
the (agent, human) pair named by <code>actor_token</code> and <code>subject_token</code>. The
minted token's subject <strong>stays Maya</strong>; Ada is recorded separately in an
internal <code>act</code> claim. Every lookup this token makes is auditable back to
Maya, and the moment her role is revoked, this exchange (and any future
one) stops working — no separate cleanup needed.</p>
<p>The granted scope is the intersection, checked fresh at exchange time, of:
the front-door app's <code>allowedScopes</code>, Ada's own <code>allowedScopes</code>, the
mandate's <code>scope</code>, and Maya's <strong>current</strong> role scopes.</p>
<p>Introspecting <code>delegated_token</code> the same way as before now shows the
difference: <code>sub</code> is Maya, and an <code>act</code> field appears naming Ada —</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"active"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"scope"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"users:read"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"sub"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"9f8e7d6c5b4a39281726354433221100"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"act"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"sub"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"agent_e241a1c4447bdc04d755a707dd0dec76"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>— which is the whole point of RFC 8693 §4.1's actor claim: the audit trail
always shows who's really behind an action, not just whoever's holding the
token.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="using-the-token-to-call-an-external-api">Using the token to call an external API<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#using-the-token-to-call-an-external-api" class="hash-link" aria-label="Direct link to Using the token to call an external API" title="Direct link to Using the token to call an external API" translate="no">​</a></h2>
<p>With <code>delegated_token</code> in hand, Ada calls the external billing API to
actually process the cancellation. It passes <code>resource</code> on the exchange
so the intended target is recorded on the token:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">resp </span><span class="token operator">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">O2ID_URL</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">/oauth2/token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"grant_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:grant-type:token-exchange"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"client_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> FRONT_DOOR_CLIENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"subject_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> maya_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"subject_token_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:token-type:access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"actor_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"actor_token_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"urn:ietf:params:oauth:token-type:access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"scope"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"users:read"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"resource"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://billing.example.com"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">delegated_token </span><span class="token operator">=</span><span class="token plain"> resp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"access_token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"https://billing.example.com/v1/subscriptions/cancel"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    headers</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"Bearer </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">delegated_token</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    json</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"reason"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"customer_requested"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Worth being upfront about: today <code>resource</code> is
<a class="" href="https://o2id.dev/docs/token-exchange#resource--audience">recording-only</a> — O2ID validates
it's a well-formed URI and stamps it on the token, but there's no
resource-server registry yet, so <code>billing.example.com</code> can't independently
verify the token was minted for it. Real cross-service verification is
follow-up work; for now, treat it as an audit trail, not an enforcement
boundary.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="own-token-vs-acting-on-behalf-of-a-human">Own token vs. acting on behalf of a human<a href="https://o2id.dev/blog/authenticating-ai-agents-with-o2id#own-token-vs-acting-on-behalf-of-a-human" class="hash-link" aria-label="Direct link to Own token vs. acting on behalf of a human" title="Direct link to Own token vs. acting on behalf of a human" translate="no">​</a></h2>
<table><thead><tr><th></th><th>Own token</th><th>Acting on behalf of a human</th></tr></thead><tbody><tr><td>Grant</td><td><code>client_credentials</code> + <code>private_key_jwt</code></td><td><code>token-exchange</code> + <code>actor_token</code></td></tr><tr><td>Subject of the minted token</td><td>The agent itself</td><td>The human — unchanged throughout</td></tr><tr><td>What you need</td><td>The agent's own registered key</td><td>A mandate, the human's live token, <em>and</em> the agent's own token</td></tr><tr><td>Scope ceiling</td><td>The agent's <code>allowedScopes</code></td><td>Agent <code>allowedScopes</code> ∩ mandate <code>scope</code> ∩ human's <strong>current</strong> role scopes ∩ app <code>allowedScopes</code></td></tr><tr><td>Use it when</td><td>No human is present (background jobs, cron)</td><td>A specific human is asking for something, present or not — the mandate can be created on the spot or looked up from one made in advance</td></tr></tbody></table>
<p>Both flows end in the same place — an O2ID access token an agent can
present to any API — but they answer different questions about whose
authority that token carries. Getting that distinction right up front is
what makes the audit trail (and the ability to instantly cut an agent off
by revoking a role or a mandate) actually hold up.</p>
<p>For the full mechanics, see <a class="" href="https://o2id.dev/docs/managing-agents">Managing AI Agents</a>,
<a class="" href="https://o2id.dev/docs/token-exchange">Token Exchange</a>, and the
<a class="" href="https://o2id.dev/docs/client-credentials-grant">Client Credentials Grant</a>.</p>]]></content:encoded>
            <category>ai-agents</category>
            <category>oauth</category>
            <category>token-exchange</category>
        </item>
    </channel>
</rss>