SolidHooksSolidHooks/ Docs

Agent Auth

Give an AI agent its own delegated identity on SolidHooks instead of a shared API key, and drive the platform through capability calls.

Agent Auth lets an agent hold its own identity on SolidHooks. The agent generates its own keypair, registers it, and a human approves exactly which capabilities it gets. SolidHooks implements the Agent Auth protocol through Better Auth.

If you only need a script to call the REST API, an organisation API key is simpler. See API keys and the AI agent setup guide, which covers the key-based route. Use Agent Auth when you want a per-agent identity that a human can see and revoke on its own.

Why a delegated identity

A shared API key is the same credential for every caller, so you cannot tell agents apart or switch one off without breaking the rest. An agent identity differs in three ways:

  • It is yours, not shared. Each agent has its own keypair and its own grants.
  • The human sees what they are granting. The approval screen lists the capabilities the agent asked for.
  • Either side can revoke it. Revoking one agent does not touch anything else.

Only delegated mode exists. There is no autonomous or service-account mode, and the only approval method is device_authorization. CIBA is not offered.

Discovery

Start with the discovery document. It is unauthenticated, so you can read it before you hold any credential.

curl $API/.well-known/agent-configuration
{
  "version": "1.0-draft",
  "provider_name": "SolidHooks",
  "issuer": "https://api.solidhooks.com/auth",
  "default_location": "https://api.solidhooks.com/auth/capability/execute",
  "algorithms": ["Ed25519"],
  "modes": ["delegated"],
  "approval_methods": ["device_authorization"],
  "endpoints": { "register": "...", "capabilities": "...", "execute": "...", "status": "...", "revoke": "..." }
}
FieldMeaning
issuerThe auth origin. Use it as the JWT aud.
default_locationWhere capability calls go.
algorithmsAuthoritative. Only Ed25519 is accepted.
modesdelegated only.
approval_methodsdevice_authorization only.
endpointsAbsolute URL of every agent endpoint, so you hardcode nothing.

GET /auth/capability/list returns the capabilities on offer. It is also unauthenticated. Most capabilities mirror the /v1 REST surface one for one.

The flow

Generate keypairs

Generate one Ed25519 keypair for the host and one for the agent.

Register

Call POST /auth/agent/register with Authorization: Bearer <host JWT>. The host JWT is self-signed with typ: "host+jwt" and carries host_public_key and agent_public_key as inline JWK claims, which is how an unknown host registers itself.

{
  "name": "release-bot",
  "mode": "delegated",
  "capabilities": ["account.get_memberships", "account.select_project", "sources.list"]
}

Ask only for what you will use. The human sees this list on the approval screen. Include account.get_memberships and account.select_project, because you need both below.

The response has status: "pending" and an approval block containing verification_uri_complete. Show that URL to the human and stop. They open it, review the request and approve it. Nothing lets you approve on their behalf.

Poll for approval

Call GET /auth/agent/status with an agent JWT until status is active.

Read memberships

Execute account.get_memberships first. It takes no arguments and has no REST equivalent. It returns the organisations your approver belongs to, each with its projects:

[{ "organizationId": "org_...", "name": "Acme Inc", "slug": "acme-inc", "role": "owner",
   "projects": [{ "id": "proj_...", "name": "Default Project", "slug": "default" }] }]

Your token says who approved you, not which organisation or project to act in. If there is more than one project, ask the human which one to use rather than guessing.

Select a project

Execute account.select_project with {"projectId": "proj_..."}. Until you do, every other capability call is refused with 403 FORBIDDEN.

{ "organizationId": "org_...", "organizationName": "Acme Inc", "projectId": "proj_...", "projectName": "Default Project" }

The choice is stored on the agent, so you do not resend it. Call it again at any time to switch projects. You can only select a project in an organisation your approver belongs to, and an id that does not exist returns the same error as one you cannot reach.

Execute capabilities

Call POST /auth/capability/execute for everything else, signing a fresh agent JWT per call:

{ "capability": "sources.list", "arguments": {} }

Every /v1 operation is available as a capability of the same name, such as sources.list or stats.dashboard.

Why /v1 rejects agent tokens

/v1 accepts API keys only. An agent token sent there gets a 401 whatever it was approved for. This is deliberate. Capability execution is where your approved grants are enforced, and it runs the same procedure with the same permission checks as /v1. A token accepted at both doors would only be as narrow as the weaker one.

X-Project-Id belongs to the API-key surface and does nothing for an agent. Your project comes from account.select_project.

Signing an agent JWT

These rules each fail as a bare 401 with no message naming the cause.

RuleDetail
subThe agent id.
issThe host id. Putting the agent id here fails a host cross-check.
audThe auth origin, which is the issuer from discovery. It may instead be a capability's location template, but only when the JWT names exactly one capability, and never the concrete request URL. The issuer always works.
htm / htuDo not set them. Verification re-issues the token internally, so a request-bound token never matches.
jtiFresh on every call. It burns on first use.
AgeTokens are short-lived, 60 seconds at most.
typagent+jwt, or host+jwt for host calls.

A 401 does not tell you which rule you broke

Check these six in order before assuming the keypair or the approval is wrong.

Lifetimes and revocation

An agent session slides on use for one hour, with a 24 hour ceiling from activation. Past that, the agent must call POST /auth/agent/reactivate. Reactivation clears its grants and returns it to needing human approval, so an idle agent decays to no access.

A human can revoke an agent at any time from the dashboard under Settings → Agents. It takes effect on the next call.

Next steps