Skip to main content
Auth is invisible to the agent. The agent describes intent; Gecko injects credentials at call time. The entire engine/adapter seam is one function:
class AuthSession(Protocol):
    def auth_headers(self) -> dict[str, str]: ...
Any object with auth_headers() -> dict[str, str] is a valid session. A paywalled API returns its tokens; a public API returns an empty dict. This is the design that keeps the engine API-agnostic — adding an API is data (the spec) plus, at most, this one adapter.

Sessions you get out of the box

Session

A two-token authenticated session (JWT + API token). Returned by the access handshake.

NoAuthSession

For public, no-auth APIs — auth_headers() returns an empty dict.

stub_session

A non-live session for recorded demos: auth headers are present but not real.

How auth gating works

When a session returns an empty auth-header dict, Gecko treats it as “this session can’t satisfy auth-gated operations” and hides those operations from the agent — it could only mis-call them. A session with auth surfaces everything, unchanged. If an auth-gated tool is forced anyway, prepare() raises a typed error rather than firing a request that can’t succeed.
from surfcall.access import public_session
from surfcall.client import AgentApiClient

client = AgentApiClient(spec, session=public_session())
client.list_tools()          # auth-gated operations are not in this list
client.prepare("some_auth_only_op", {})   # raises CallError — correctly refused

The two-token handshake (worked example)

The live reference integration (the TxODDS World Cup API) uses a flow the agent never has to learn. Gecko encodes it so the agent only ever describes intent:
guest JWT  →  on-chain subscribe (txSig)  →  sign(txSig:leagues:jwt)  →  activate  →  apiToken
producing a two-token session:
Authorization: Bearer <session JWT>     (httpAuth)
X-Api-Token:   <long-lived apiToken>    (apiKeyAuth)
Transport and signer are injected, so the whole flow is unit-testable with no network and no keys:
from surfcall.access import establish_session

session = establish_session(base_url, tx_sig, leagues, signer=my_signer)
session.auth_headers()
# {"Authorization": "Bearer ...", "X-Api-Token": "..."}

The on-chain step

The subscribe transaction itself is a wallet-signing, network-specific step that lives outside the access layer (in scripts/). The access layer takes the resulting txSig plus a signer and finishes the session.
Mainnet boundary. The subscribe transaction is founder-run only. The tooling simulates by default (no spend) and hands over the exact command; a human broadcasts it. Gecko does not sign or broadcast mainnet transactions on its own.

What Gecko never does with credentials

  • It never exposes auth headers in the agent-facing tool definitions.
  • It never logs tokens or keys — secrets are redacted before any error is raised.
  • It never stores secrets; they are read from the session/environment at call time.
See Architecture for how this fits the control-plane model.