vault

Mint a Handoff Key

kygit-handoff (design D2/D3/D10). Mints a single-use bearer Handoff Key that bridges to a principal envelope: the recipient becomes an org member at the minter's role (or a narrower requested role) the moment the key is claimed. The kgh1_<handoff_id||master_secret> key is assembled entirely CLIENT-SIDE — handoff_id is client-generated (it salts the HKDF derivation of auth_secret/wrap_key) and REQUIRED in the body; the gateway never sees the assembled key, only the resulting auth_hash and sealed_envelope. Auth: principalAuth (control-plane session, SIWX wallet, or admin key) at handoff.mint (developer+, no grant key — a grant key is never a member, and a handoff confers membership). Never lifecycle-gated. Bounds (hard-coded): 20 open handoffs per vault, 100 mints per org per UTC day, TTL 60-86400s (default 3600s). vault-multi-writer rev 47: a claimed handoff now ALSO confers write authority on the vault (the blast-radius warning names it: "...becomes an owner of this org and a writer of this vault, permanently..."), so minting requires writer_admission_grant and the minter must currently be an active, non-gateway-blocked writer — refused 403 HANDOFF_MINT_REQUIRES_WRITER otherwise.

post/vaults/v1/{vault_id}/handoffs

Request

  • Base URL: https://api.run402.com
  • URL: https://api.run402.com/vaults/v1/{vault_id}/handoffs
  • Auth: one of:
    • HTTP bearer
    • API key in header SIGN-IN-WITH-X
    • API key in header x-admin-key

Path parameters

vault_idstring required

The vault id (src_<32 hex>); the wire field on the response is repo_id.

Headers

SIGN-IN-WITH-Xstring required

SIWX auth header — base64-encoded JSON payload containing a signed CAIP-122 (EIP-4361) message. See https://docs.x402.org/extensions/sign-in-with-x.

Request body

handoff_idstring uuid required

Client-generated (any UUID version). Salts the HKDF derivation of auth_secret/wrap_key, so the client must mint it before deriving the key. Used as the row's own primary key — a collision refuses 409 HANDOFF_ID_CONFLICT rather than ever overwriting a row.

role'owner' | 'admin' | 'developer' | 'billing' | 'viewer'

Requested role. The stored minted_role is min(requested, the minter's own fresh role) — a handoff never grants MORE than the minter's own role. Defaults to the minter's role when omitted.

expires_in_secondsinteger

TTL in seconds. Default 3600 (1 hour).

sealed_envelopestring byte required

Base64 ciphertext, opaque to the gateway: {v, kind:"handoff", repo_id, epoch, k_e, checkpoint, note_schema} sealed under wrap_key with the vault's frame AEAD backend. Stored verbatim; never decrypted by the gateway.

envelope_kindstring required

Client-declared envelope format tag, opaque to the gateway.

auth_hashstring required

64 lowercase hex chars (32 bytes) — SHA-256("kygit/handoff/auth-hash/v1" || auth_secret). The gateway stores only this hash, never auth_secret itself.

writer_admission_grantobject required

vault-multi-writer rev 47, REQUIRED (no observe mode). The minter's own signed grant proving THEY are currently an active vault writer and binding this mint: {format, object_kind, suite, repo_id, handoff_id, auth_hash, checkpoint_generation, checkpoint_head_sha256, grantor_writer_key_id, handoff_admission_pubkey, minted_role, claim_not_after, created_at, signature}. Verified synchronously server-side under the minter's OWN signing_pubkey (published via POST /agent/v1/whoami/encryption-key, above) — every field cross-bound against this mint request's own resolved values. Stored verbatim + its sha256; echoed back byte-for-byte from the matching claim response.

Response

Handoff minted. The Handoff Key itself is assembled and printed exactly once, client-side (the gateway never sees it).

handoff_idstring uuid required
kind'handoff' required
role'owner' | 'admin' | 'developer' | 'billing' | 'viewer' required

The stored minted_role — never wider than the minter's own fresh role at mint time.

expires_atstring date-time required
repo_idstring required
org_idstring uuid required
project_idstring nullable required
warningstring required

Verbatim (design D10, amended vault-multi-writer rev 47) blast-radius sentence: "Whoever claims this key first becomes a <role> of this org and a writer of this vault, permanently. The key works once and expires at <expires_at>." ("an owner" / "an admin" — the article follows the role.) Show this to whoever runs the mint — it names the authority they are about to hand over, now including on-chain WRITE authority on this vault, not just org membership.

Example response

{
  "next_actions": [
    {
      "method": "POST",
      "path": "/tiers/v1/hobby",
      "auth": "x402"
    }
  ]
}

Changes

No recorded changes to this operation across all 1 revision of this API.