Keeyo KEEYOEquipment register

Form K-99 · Operating manual

User guide

Everything the register can do, in the order you'll meet it.

Registering a key

  1. Scan it. Hit Register key and the scan step opens. Plug the key into the machine you're browsing from and touch it when it blinks. Keeyo reads the model fingerprint (AAGUID), matches it against the live FIDO registry, and pre-fills vendor, model and form factor. Scanning also pairs the key, which unlocks identification and secret notes later. Nothing is written to the key and none of its passkey storage is used.
  2. Or add it manually. The small link below the scan button opens the plain form. Vendors, models, form factors and colors all accept custom entries — anything you type is remembered in your personal catalog (Settings → Catalog) for next time.
  3. Pick the variant if asked. Fingerprints identify a series — a YubiKey 5 NFC and 5C NFC share one — so when it's ambiguous, Keeyo narrows the model list and you pick the plug you're holding.
  4. Give it a name and a color. Name it for where it lives ("Desk drawer backup"), and match the color tag to a real sticker on the physical key. You can upload a photo instead of the schematic drawing.

Logging what's on a key

Open a key's tag and add sign-ins (passkeys and 2FA registrations) and TOTP codes. The service picker searches your existing services, offers one-tap buttons for common ones (GitHub, Google, …) and creates new services inline — with automatic favicons. Tick add another to log a whole key's contents in one sitting.

Registrations can be edited, moved to another key, or removed — deletions come with a 5-second Undo instead of a confirmation dialog.

Services & backup coverage

The Services view (the globe icon in the left rail) is the reverse lookup: every service, and colored squares for each key that covers it. Open a service to see exactly which keys hold it. Two warnings matter:

The same warnings surface as a strip on the dashboard so gaps don't hide.

Every service can carry an icon: upload your own, or search the selfh.st and Dashboard Icons catalogs by name, or pull the site’s favicon through DuckDuckGo. Catalog icons load from jsDelivr in your browser; uploads are stored with the service.

Losing a key

Set the key's status to lost. Its registrations become a revocation checklist — go to each service, remove the key there, and tick it off. The dashboard shows a hazard strip until the checklist is clean. If the key turns up later, the identify feature will recognize it and remind you it was marked lost.

"Which key is this?"

The identify button on the dashboard answers the question the whole app exists for. Plug in the mystery key, touch it, and Keeyo names the exact record — even several keys plugged in at once is fine, because the key you physically touch is the one that answers. Works for paired keys; unpaired ones fall back to a model read with candidate matches.

Secret notes (tap to reveal)

A paired key can hold one secret note — typically its PIN. The note is never shown in the app; revealing it requires tapping that exact physical key, and the server verifies the cryptographic response before releasing it.

On modern keys (anything supporting the WebAuthn PRF extension — YubiKey 5 series included), the note is also end-to-end encrypted: your browser derives the encryption key from the hardware itself, so the server only ever stores ciphertext. Saving asks for one extra tap; revealing doesn't — the same tap proves possession and unlocks the note. The key page labels every note E2E encrypted or server-stored so you always know which mode you're in; re-pairing an old key upgrades it.

⚠Notes on pairings without PRF support are stored unencrypted in the server's database file — the tap protects the API, not the disk. The label tells you; don't store anything you can't afford to have on your own server.

Health check-ins

Backup keys rot in drawers. Whenever you actually use or test a key, press Tested on its page — the dashboard nudges you about any active or backup key that hasn't been confirmed working in six months.

The logbook

Every key keeps an append-only ledger: registered, paired, status changes, services added and removed, revocations, tests, files. It answers "when did I add this?" without you ever writing anything down.

Printing & exports

Protecting Keeyo itself

Settings → Security: add a second factor and logging in requires your password plus that factor.

If every factor is lost, the server owner still has the recovery paths: KEEYO_DISABLE_MFA=1 or scripts/reset-password.js.

Access tokens

Settings → Access tokens creates personal tokens for scripts and integrations. Send one as Authorization: Bearer keeyo_… and the API answers as you, for example GET /api/data for the whole register or GET /api/export for a backup.

Resetting a password with scripts/reset-password.js revokes every token on that account.

Using a token

Every request is the same as the web app's own API — just add the header. Reading the whole register:

curl -H "Authorization: Bearer keeyo_xxxxxxxxxxxx" \
  https://keys.example.com/api/data

That returns one JSON object with your keys, services and registrations. A nightly off-site backup is one cron line (a read-only token is enough):

0 3 * * * curl -sf -H "Authorization: Bearer $KEEYO_TOKEN" \
  https://keys.example.com/api/export > /backups/keeyo-$(date +\%F).json

With a read & write token you can add records. Register a key, then log a passkey on it — services are created inline if the name is new:

curl -H "Authorization: Bearer $KEEYO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Drawer spare", "vendor": "Yubico", "model": "YubiKey 5 NFC", "formFactor": "usb-a", "status": "backup"}' \
  https://keys.example.com/api/keys
# → {"id": 7, ...}

curl -H "Authorization: Bearer $KEEYO_TOKEN" -H "Content-Type: application/json" \
  -d '{"keyId": 7, "kind": "passkey", "service": {"name": "GitHub", "url": "github.com"}}' \
  https://keys.example.com/api/registrations

Same thing from PowerShell:

$headers = @{ Authorization = "Bearer $env:KEEYO_TOKEN" }
Invoke-RestMethod -Uri "https://keys.example.com/api/data" -Headers $headers

Useful endpoints: GET /api/data (everything), GET /api/export (backup JSON), GET /api/keys/ID/events (a key's logbook), POST /api/keys, POST /api/services, POST /api/registrations (with serviceId or an inline service), POST /api/keys/ID/verify (record a health check-in — handy after a scripted test), and PUT/DELETE on any of those by id. Registration kind is passkey, second-factor or totp.

What the errors mean: 401 — the token is wrong, expired or revoked; 403 This access token is read-only — a write with a read-only token; 403 Not available to access tokens — that route needs a real sign-in (account, security and admin settings always do).

Keyboard shortcuts

KeyAction
/Focus the search box
NRegister a new key (dashboard)
EscClose the open dialog