Skip to content

HTTP API

Docs path: Reference / HTTP API

All JSON endpoints use bearer-token authentication where noted. Signing routes use the token in signer_token_file; admin routes use admin_token_file.

The machine-readable API source of truth is the committed openapi/openkms.v1.json spec. CI regenerates it from Rust code and fails when it drifts.

Direct artifact links:

MethodPathAuthPurpose
GET/healthnoneHealth check and HSM probe state.
GET/keysnoneList configured keys and runtime enabled state.
GET/policysigner bearerList effective signing policies and live usage counters.
GET/policy/{label}signer bearerRead one key’s effective policy before signing.
POST/sign/solanasigner bearerSign a Solana VersionedMessage.
POST/sign/cosmossigner bearerSign a Cosmos SDK SignDoc.
GET/admin/policyadmin bearerList policies with baseline and overlay metadata.
GET/admin/keys/{label}/policyadmin bearerRead one key policy with admin metadata.
PATCH/admin/keys/{label}/policyadmin bearerPersist a partial per-key policy overlay.
DELETE/admin/keys/{label}/policyadmin bearerClear a policy overlay and return to config baseline.
POST/admin/keys/{label}/enableadmin bearerEnable a configured key.
POST/admin/keys/{label}/disableadmin bearerDisable a configured key.
GET/metricsnonePrometheus text exposition.

Signer agents should read policy before requesting signatures:

Terminal window
curl -sS \
-H "Authorization: Bearer $(cat signer.token)" \
http://pi.local:9443/policy/solana-hot-0

Responses include key identity, the effective policy, and runtime counters:

{
"label": "solana-hot-0",
"chain": "solana",
"effective_enabled": true,
"policy": {
"enabled": true,
"max_signs_per_minute": 30,
"max_signs_per_day": 5000,
"per_tx_cap_lamports": "5000000000",
"allowed_programs": [{ "id": "11111111111111111111111111111111" }]
},
"runtime": {
"enabled_override": null,
"daily_spend": [{ "token": "native", "spent": "0", "cap": null }],
"sign_counts": {
"per_minute": { "limit": 30, "used": 0, "window_secs": 60 }
}
}
}

The runtime sign counters are accepted policy evaluations inside each window. They help agents self-throttle, but the policy engine remains authoritative.

Admin policy mutation stores a partial overlay under state_dir; it does not rewrite config.toml. The effective policy is the config baseline plus the persisted overlay. Non-null fields in a PATCH replace the corresponding baseline policy field. Use DELETE /admin/keys/{label}/policy to clear the whole overlay.

Terminal window
curl -sS -X PATCH \
-H "Authorization: Bearer $(cat admin.token)" \
-H "Content-Type: application/json" \
http://pi.local:9443/admin/keys/solana-hot-0/policy \
-d '{ "max_signs_per_minute": 5, "per_tx_cap_lamports": "1000000" }'
{
"label": "solana-hot-0",
"expected_chain_id": "mainnet-beta",
"message_b64": "<base64 VersionedMessage>",
"address_lookup_tables": [
{ "key": "<ALT pubkey>", "addresses": ["<base58>", "..."] }
]
}

Response:

{ "signature_b64": "<base64 64-byte ed25519>" }
{
"label": "cosmos-hub-0",
"sign_doc_b64": "<base64 proto-encoded SignDoc>",
"expected_chain_id": "cosmoshub-4"
}

Response:

{ "signature_b64": "<base64 64-byte compact low-s ECDSA>" }

JSON API errors use this shape:

{ "error": "human-readable reason" }

Policy denials return 403 or 429 depending on the denial reason. Decode errors return 400, unknown labels return 404, and HSM/internal failures return 500.