Skip to content

Agent API

An agent asks. A human signs. Not the other way round.

Every assistant that touches your messages today does it with your full standing permission. Nex inverts that: an automated process starts with nothing, has to ask for named permissions with an expiry, and gets exactly what a human decides to hand over.

A new process holds nothing

Before any of the interesting parts, the boring one: an agent that has not been approved cannot do anything at all, and says so in a way that points at the next step.

A fresh process starts with nothing at all.agent terminal
nex send 4f0c7ab2… "this must not be sent"REFUSED: `message.send` requires the scope `message:send` and this process holds no token.Tokens are never written to disk (contract §6.1), so a fresh `nex` invocation starts with nothing.Next:  nex session   then   auth request --scopes message:send --waitA human must approve it before anything is minted.[exit 3]

Request → approve → narrowed grant

The agent asks for read access to every conversation and the ability to send. The human gives it the reading half and keeps the sending half.

1. The agent asks. It gets a request id, not access.agent terminal
nex auth request --scopes conversation:*:read,message:send --ttl 300 --waitapproval requested    request_id    7c41f9d0a2be5163d84e0fb27a95c3ea    user_code     H4KD-27QF    expires in    4m 58s    asking for    ! conversation:*:read             Read the messages in ANY conversation    ! message:send                    Send messages as you    A human must now run:  nex approve 7c41f9d0a2be5163d84e0fb27a95c3ea    and can narrow it:     nex approve 7c41f9d0a2be5163d84e0fb27a95c3ea --scopes conversation:listwaiting for a human… (polling every 2s)
2. A human approves — and grants only half of what was asked for.human terminal
nex approve listrequest  7c41f9d0a2be5163d84e0fb27a95c3ea    from          claude-code    user_code     H4KD-27QF   <- must match what the agent printed    expires in    4m 41s    agent key     3f8a2c19…    asking for    ! conversation:*:read             Read the messages in ANY conversation    ! message:send                    Send messages as younex approve 7c41f9d0a2be5163d84e0fb27a95c3ea --scopes conversation:*:readAPPROVED 7c41f9d0a2be5163d84e0fb27a95c3ea    signed by     9d1b47e0…    valid for     5m 00s    granted    ! conversation:*:read             Read the messages in ANY conversation    withheld (the agent asked, you said no)    ! message:send                    Send messages as you
3. The agent is told exactly what it got — and what it did not.agent terminal
APPROVED    jti           b39d1c60f4a72e58ad0c9b31e7f52d84    expires in    4m 59s    granted    ! conversation:*:read             Read the messages in ANY conversation    withheld by the human    ! message:send                    Send messages as you    token held in memory only; it dies with this process.history 4f0c7ab2…  1753876412031  a71e3d95…  c04b18ff…  ratchet-proof-the-quick-brown-fox

The withheld half is genuinely withheld

This is the moment worth watching. The read succeeds. The send does not — first because the client knows the token cannot cover it, and then, when that check is bypassed, because the daemon refuses it.

4. The withheld scope bites — first in the client, then in the daemon.agent terminal
send 4f0c7ab2… "shipping now"REFUSED locally: `message.send` needs the scope `message:send`, and the tokenthis process holds carries only:    conversation:*:readAsk for a wider grant with `auth request --scopes message:send` — a humanstill has to approve it.(Add --force to send it anyway and see the daemon refuse it itself.)the client check is a courtesy. --force skips it, so the daemon has to say no itself:send 4f0c7ab2… "shipping now" --forceREFUSED by the daemon: token scopes do not cover `message:send` (code -32001)

Revocation bites on the next call

Not at the next login, not when the token expires — on the next call.

5. Revocation lands on the very next call.human terminal, then agent terminal
nex tokens revoke --allrevoked 1 token(s). Every agent must now be re-approved.the agent now repeats the exact read that succeeded a moment ago:history 4f0c7ab2…REFUSED by the daemon: token has been revoked (code -32001)

The permissions an agent can ask for

A grant is a list of these. `!` marks the ones that write, or that cover more than one thing — the CLI marks them the same way when it shows a human what is being asked for.
Scope What it allows
conversation:listList conversations — titles and counts, no message bodies
conversation:<id>:readRead the messages in one named conversation
! conversation:*:readRead the messages in ANY conversation
! conversation:*:writeCreate or modify ANY conversation
! message:sendSend messages as you
media:<id>:fetchFetch media attachments
! admin:*Administer this daemon — devices, keys, settings
! *EVERYTHING — unrestricted access to this daemon

Why this holds up

  • The human can grant less than was asked for

    Approval is not a yes/no button. The approver names the scopes to hand over, and anything the agent asked for that is not on that list is withheld — and shown to both sides as withheld, so nobody discovers it later through a mysterious failure.

  • Tokens are never written to disk

    A minted token lives in the memory of the process that asked for it. A fresh invocation of the CLI holds nothing and is refused immediately. There is no credential file to leak, copy or forget about.

  • Every grant expires

    A token carries a lifetime in seconds — the transcripts below use five minutes — and an inactivity window. An agent that goes quiet and comes back has to ask again.

  • The gate is in the daemon, not the client

    The CLI checks scopes before it makes a call it knows will fail, purely so the error message is useful. Skip that check with --force and the daemon refuses it itself. The client-side check is a courtesy; the server-side one is the security boundary.

  • Holding the token is not enough

    Each call is signed with a key the token is bound to, so a stolen token alone cannot be replayed by another process. Approvals themselves are signed by an enrolled human key that the daemon knows about.

  • Revocation is immediate

    Revoking does not wait for expiry, log the agent out, or take effect next session. The very next call the agent makes is refused, with a distinct error naming revocation specifically.