Passkeys, held in trust

Sign your agent in.
Never hand it a secret.

Agent Passkey holds the passkeys you enrol and signs on your agent's behalf. The agent gets a session — it never gets a credential, because there is no credential to get.

One consent screen. No API keys to copy, no code to paste.

audit log · liveappend-only
connectallowedclaude-code · 2 accounts · 30d

Every signature this service produces, and why it was allowed.

How it works

Set it once. Then stop being the bottleneck.

01

Enrol a passkey

In your own browser, already signed in to the site, you register a passkey the way you always do — or an agent you trust with the enrol scope adds one after you sign in. Either way the private key is generated inside the service and never leaves it.

02

Connect your agent

One command. Your agent opens a consent screen where you pick which accounts it may use and for how long. No key to copy, nothing to paste back.

03

It just signs in

From then on the agent signs in unattended, inside the scope you set. Every signature is written to an append-only log, and one click revokes the lot.

Connecting

One command. One screen.

Agent Passkey is a remote MCP server that speaks OAuth 2.1. Your agent discovers the endpoints, registers itself, and opens a browser. You tick the accounts it may use, choose how long, and hold Allow.

There is no API key, because there is nothing to key. Authority lives in a grant you can read and revoke, not in a string you have to keep secret.

$ claude mcp add --transport http agent-passkey https://app.solospace.dev/mcp

Added MCP server agent-passkey.
Opening browser to authenticate…

✓ Connected. 2 accounts · expires in 7 days

Works with any MCP client that implements the 2025-06-18 authorization spec or later.

Agents: read the skill before your first sign-in.

The dashboard

Everything an agent did, in one table.

Passkeys, agents, approvals and the audit log share one screen and one vocabulary. What you read here is what the service acted on — there is no second copy to drift.

Every passkey, and who made it
Each row is a site, the account it signs in as, the mode your agents get, and whether a person enrolled it in Chrome or an agent added it via the API.
Approvals you can answer anywhere
A pending signature reaches your phone, the browser extension and the dashboard at once. Whichever one you tap, the others see it.
A log the service cannot edit
The same attestation lines as the hero — because they are the same rows. The table refuses UPDATE and DELETE at the database, so the record is what happened.
Works from anywhere

One service. Three doors, one grant behind each.

An agent over MCP, a script over HTTP and a scheduled run all hold the same kind of authority: the accounts you ticked, the scopes you chose, an expiry, an audit line and a revoke button. Nothing one door can do is something another cannot.

MCP

For an agent with a browser.

A remote server that speaks OAuth 2.1. The agent registers itself, you tick the accounts on one consent screen, and from then on it mints a ticket, drops a page script into the site, and the page fetches its own signature.

https://app.solospace.dev/mcpMCP reference →

REST API

For a script, a CI job, a cron.

The same tools as plain HTTP under /v1/*, with an API key minted on the dashboard. Same envelope on every response — state, retryable, terminal, next — so a shell script and an agent reach the same conclusion. Signing in many customers? One key, a subject per customer, a session policy with an end date.

curl -H "Authorization: Bearer apk_key_…" https://app.solospace.dev/v1/connectorsREST API reference →

Automations

For work that happens without you.

Propose a task, activate it once with a consent that names the accounts and a window, and a runner signs in on a schedule, on a webhook, or on demand. Every run is audited; pausing revokes the grant.

trigger: { kind: "schedule", cron: "0 9 * * 1-5" }Automations →
New · the enrol scope

Add passkeys from the API.

A grant that holds enrol can add a passkey to a site — after you have signed in to that site in the browser the agent drives. The service answers the site’s registration ceremony; the key is generated inside it and never leaves. The new passkey starts in approval mode and shows up on your dashboard as “Added via API”.

The scope is off by default. It cannot get a browser past a login, and it cannot turn a second-factor-only site into a passwordless one.

$ curl -X POST $APK/v1/enrol/begin -H "Authorization: Bearer $APK_KEY" \
     -d '{"domain":"github.com","userName":"octocat"}'
{ "state": "ok", "ticket": "9a41…", "shim": "<page script>" }
# evaluate shim on the signed-in “add a passkey” page, click its button
$ curl $APK/v1/enrol/9a41…    → { "status": "created" }
$ curl -X POST $APK/v1/enrol/complete -d '{"ticket":"9a41…","ok":true}'
What holds

The limits, stated as plainly as the claims.

The key never leaves

Each passkey is a P-256 key generated inside the service and envelope-encrypted at rest. It is never sent to your agent, never sent to your browser, and never sent to the site. An agent asks for a signature; it does not ask for a key.

Enrolment is a scope, not a default

By default the agent path serves get() only, and a person registers passkeys in their own browser. A grant that holds the enrol scope may answer create() too — but only on a page where a human has already signed in, because the service holds nothing that could get a browser past a login. Off unless you tick it.

Origin is checked, not asserted

A signature is only produced for an origin sharing a registrable domain with one captured at enrolment. A remote agent can claim any origin it likes; the claim is checked against what a human's browser actually saw.

The log cannot be rewritten

audit_log is append-only in the database itself — a trigger refuses UPDATE, DELETE and TRUNCATE, so not even the application that writes it can go back and change it.

Presence is a claim we take seriously

The UP/UV flags tell a site a human was there. We set them on the strength of a consent you gave, naming the accounts and an expiry — and where you ask for approval per signature, we wait for it. Where you ask for neither, we report UP=0/UV=0 rather than lie.

What this is not

The current deployment seals its master key with the host key, not a TPM — root on that box could recover it offline. What contains that is approval gating, rate limits, the audit log and revocation, not the cryptography. An HSM is the upgrade path and swaps one function.

Stop being the thing your agent waits for.

Enrol a passkey, connect an agent, and go and do something else.