This is about agents acting through your contract. If you’re looking for how you, the tenant developer, authenticate to manage your own deployment, see Set Up Development Environment instead — that’s a different session.This page assumes the agent already has a DID. Creating that identity in the first place — minting the agent and publishing its agent card — is Agent Onboarding, which happens before anything on this page.That identity also needs its own test credits before any metered call on this page will work — an agent DID’s balance is separate from its tenant’s and starts at zero. Get it a key from the same claim page you used for your own; it comes with credits attached.
1. An agent authenticates like any other T3N session
An agent has its own identity — its own key pair and its own DID — separate from the tenant that owns the contract it’s calling. It authenticates the same way any T3N client does: handshake, then authenticate, then read its own DID back from the session. There’s nothing tenant-specific to configure here.2. Being authenticated is not being authorized
Authenticating proves the agent’s identity. It does not grant it permission to call anything. Before an agent can invoke a contract function — especially one that makes an outbound HTTP call — the user who owns the data (the “data owner,” not the agent, and not you as the tenant developer) has to explicitly grant that agent access:updateMemberDelegation. It reads the current policy, merges your grant by (grantee, contract_id), and writes the whole document back, so every other grant survives:
Why “member delegation”. The name is for the delegator: a member (the data owner) delegates a scoped slice of their own authority to a grantee. The grantee is a member too — identified by a DID, whether that’s an AI agent (as above) or the member’s own DID for a direct call (a self-grant). The same
member-delegation-update / member-delegation-get contract functions (SDK: updateMemberDelegation / getMemberDelegation) cover both. This page was previously titled “Agent Auth”.userClient here is the data owner’s own authenticated session — built the same way as t3n/agentClient above, just with the user’s own key. See Invoke your contract for the full construction and where TENANT_CONTRACT/contractVersion come from.
A grant is scoped three ways at once: which contract, which functions on it, and which external hosts it may reach. An agent with no matching grant can still call the contract — the call just fails at the point it tries to reach the network, with host/http.egress_denied.
What a grant can say
Every field is snake_case on the wire, exactly as written here — camelCase keys (agentDid, scriptName, validFromSecs) are rejected rather than silently
dropped, so a mistyped time-box can’t be lost.
Org delegation — the second edge
Everything above is the member edge: a data owner delegating their own authority. There is a second, independent edge — org delegation — where an organisation admin grants authority over the org’s contracts, written with the SDK’sOrgDataClient.setDelegation rather than updateMemberDelegation.
A call on the delegated path needs both edges to permit it. They are
enforced separately rather than as one combined check, so a missing org grant
and a missing member grant fail in different places. An org grant also carries
less: only functions and scopes — the qualifiers version_req,
read_scopes, allowed_hosts and window are rejected on that edge, so an org
grant cannot carry an expiry or a host allowlist. checkDelegation() reports
which edge is missing for a given action.
Under the Hood: why is it structured this way?
Under the Hood: why is it structured this way?
Splitting authentication from authorization means a compromised or misbehaving agent key doesn’t automatically mean compromised data access — the blast radius of a leaked agent key is exactly whatever contracts, functions, and hosts a user has explicitly granted it, nothing more. It also means a user can revoke an agent’s access without the agent’s key changing at all — they just stop re-issuing the grant.
3. Stateless invocation with invoke()
Everything above assumes a session:handshake() then authenticate(), held open across calls. An org-owned agent has a second, stateless option — invoke() posts once to POST /api/invoke and authenticates by an opaque API key instead, with no handshake, no Session-Id, no session state at all:
X-T3N-Api-Key header (t3n_key_<key-id>.<secret>) — the same key printed once when you minted the agent, see Register an Organization-owned Agent. The node derives the acting agent DID from the key per call and runs the same execution pipeline the session execute path runs, returning the contract’s decoded result directly (no JSON-RPC envelope to unwrap).
Use invoke() for a one-shot call from somewhere a long-lived session doesn’t fit — a webhook handler, a serverless function, a cron job — and the session-based flow above (T3nClient.handshake()/authenticate()) for anything that makes several calls and can hold a session open. pii_did on the request is the delegated-call target — set it to act on behalf of another org member; omit it for a self call.
Direct (self) calls work the same way
If a user is invoking their own contract directly rather than through a separate agent, the same grant mechanism applies — they just grant to their own DID (a self-grant) instead of an agent’s.Full working example
See Invoke your contract for this in context, including the search/book contract call itself. For why the outbound call fails without a grant even when the contract code is correct, see Outbound HTTP calls are authorized by the user, not the contract.Older community code sometimes references a standalone delegation-credential API — functions for building and signing a per-call delegation credential (an “envelope”). That flow was removed when T3N moved to envelope-free authorisation, and it is not part of the current SDK (confirmed against
testnet-v1.0.9, @terminal3/t3n-sdk 5.2.0). Authority is now the standing on-chain member delegation shown above (intersected with any org-granted authority), so member-delegation-update — not a per-call credential — is the write surface.