Summary
Synvya/server is being refactored so restaurant identities use Keycast team keys via bunker URLs today. That is the correct secure boundary because restaurant private keys remain in Keycast, but it means the server must communicate with Keycast over NIP-46 relay events for team-owned restaurant keys.
Keycast already supports two authorization models today:
- Personal/OAuth authorizations:
oauth_authorizations + personal_keys, usable via POST /api/nostr with UCAN bearer tokens.
- Team authorizations:
authorizations + stored_keys, usable via NIP-46 bunker URLs handled by the signer daemon.
The missing piece is an HTTP RPC path for team authorizations, analogous to the existing OAuth/personal-key HTTP RPC path.
Problem
POST /api/nostr currently only resolves bearer tokens into signing sessions for:
- OAuth tokens with
bunker_pubkey that load from oauth_authorizations
- preloaded-user server-signed UCANs
It does not support team authorizations backed by stored_keys, even though the signer daemon already supports those keys for bunker/NIP-46 usage.
Goal
Allow first-party services such as Synvya/server to securely request signing/decryption on behalf of a team-owned restaurant key via HTTP, without the private key ever leaving Keycast.
Requirements
- Keep restaurant/team private keys inside Keycast only.
- Preserve per-authorization policy enforcement.
- Do not introduce a shared super-token that can sign as arbitrary team keys without an explicit authorization boundary.
- Keep support for the existing NIP-46 bunker path; this issue is about adding an HTTP path, not replacing bunker support.
Suggested design
-
Add a first-party endpoint to mint a short-lived UCAN for a specific team authorization.
Example shape:
POST /api/server/authorizations/:id/token
or similar first-party/admin-scoped endpoint
-
The endpoint should:
- authenticate the caller as a trusted first-party service
- verify the caller is allowed to use the target team authorization
- load the
authorizations row
- mint a short-lived server-signed UCAN whose audience is the restaurant/team signing pubkey
- include facts sufficient for
/api/nostr to resolve the authorization, likely including:
tenant_id
redirect_origin (for traceability / app identity)
bunker_pubkey
- optionally
authorization_id
-
Extend POST /api/nostr handler loading so a bearer token with bunker_pubkey can resolve against either:
oauth_authorizations + personal_keys
authorizations + stored_keys
-
Reuse the same permission enforcement model already used by the signer daemon for team authorizations.
Implementation notes
Relevant files today:
api/src/api/http/nostr_rpc.rs only loads HTTP handlers from oauth_authorizations or preloaded users.
signer/src/signer_daemon.rs already supports loading both OAuth and regular team authorizations.
api/src/api/http/teams.rs already creates team authorizations and returns bunker URLs.
A reasonable implementation path would be:
- factor the common team-authorization loading/signing logic so HTTP RPC and the signer daemon can share it
- add the first-party token issuance endpoint
- extend HTTP RPC
get_handler() to resolve team authorizations
Acceptance criteria
- A team-owned stored key can be authorized for a first-party service without exposing the private key.
- The first-party service can call
POST /api/nostr for get_public_key, sign_event, nip44_encrypt, and nip44_decrypt using a short-lived UCAN tied to that team authorization.
- Policy restrictions from the team authorization are enforced.
- Existing OAuth HTTP RPC behavior remains unchanged.
- Existing NIP-46 bunker behavior for team authorizations remains unchanged.
Why this matters
This would let Synvya/server keep the correct security model while using a simpler and lower-latency HTTP path for restaurant identities. Until this exists, Synvya/server will use the existing bunker/NIP-46 path for team restaurant keys.
Summary
Synvya/server is being refactored so restaurant identities use Keycast team keys via bunker URLs today. That is the correct secure boundary because restaurant private keys remain in Keycast, but it means the server must communicate with Keycast over NIP-46 relay events for team-owned restaurant keys.
Keycast already supports two authorization models today:
oauth_authorizations+personal_keys, usable viaPOST /api/nostrwith UCAN bearer tokens.authorizations+stored_keys, usable via NIP-46 bunker URLs handled by the signer daemon.The missing piece is an HTTP RPC path for team authorizations, analogous to the existing OAuth/personal-key HTTP RPC path.
Problem
POST /api/nostrcurrently only resolves bearer tokens into signing sessions for:bunker_pubkeythat load fromoauth_authorizationsIt does not support team
authorizationsbacked bystored_keys, even though the signer daemon already supports those keys for bunker/NIP-46 usage.Goal
Allow first-party services such as
Synvya/serverto securely request signing/decryption on behalf of a team-owned restaurant key via HTTP, without the private key ever leaving Keycast.Requirements
Suggested design
Add a first-party endpoint to mint a short-lived UCAN for a specific team authorization.
Example shape:
POST /api/server/authorizations/:id/tokenor similar first-party/admin-scoped endpoint
The endpoint should:
authorizationsrow/api/nostrto resolve the authorization, likely including:tenant_idredirect_origin(for traceability / app identity)bunker_pubkeyauthorization_idExtend
POST /api/nostrhandler loading so a bearer token withbunker_pubkeycan resolve against either:oauth_authorizations+personal_keysauthorizations+stored_keysReuse the same permission enforcement model already used by the signer daemon for team authorizations.
Implementation notes
Relevant files today:
api/src/api/http/nostr_rpc.rsonly loads HTTP handlers fromoauth_authorizationsor preloaded users.signer/src/signer_daemon.rsalready supports loading both OAuth and regular team authorizations.api/src/api/http/teams.rsalready creates team authorizations and returns bunker URLs.A reasonable implementation path would be:
get_handler()to resolve team authorizationsAcceptance criteria
POST /api/nostrforget_public_key,sign_event,nip44_encrypt, andnip44_decryptusing a short-lived UCAN tied to that team authorization.Why this matters
This would let
Synvya/serverkeep the correct security model while using a simpler and lower-latency HTTP path for restaurant identities. Until this exists,Synvya/serverwill use the existing bunker/NIP-46 path for team restaurant keys.