Skip to main content

Use the Keymanager API

The Keymanager API lets you change a running validator client without restarting it: add or remove keys, set a fee recipient, gas limit, or graffiti per key, and sign voluntary exits. It's a standard API that every consensus client implements, so staking tools built for it work with Prysm. This page uses curl. The full specification is at ethereum.github.io/keymanager-APIs.

Operating system

Before you begin​

  • A running validator client with a wallet. See Import keys.

Enable the API​

  1. Add --rpc to your validator client command and restart it:

    --rpc

    The API listens on http://127.0.0.1:7500. Change the port with --http-port. It's advised not to change the address with --http-host: the API is plain HTTP and controls your keys. Prysm creates the token file auth-token in your wallet directory (--wallet-dir) if it doesn't exist. To put it elsewhere, set --keymanager-token-file.

  2. Check the log for the token path and the API address:

    INFO rpc: Validator Client auth token for gRPC and REST authentication set at <TOKEN_FILE>
    INFO httprest: Starting HTTP server address=<ADDRESS>:<PORT>
  3. Load the token into your shell. Replace <TOKEN_FILE>, <ADDRESS> and <PORT> with the values from the log:

    TOKEN=$(cat <TOKEN_FILE>)
    API=http://<ADDRESS>:<PORT>
The token controls your keys

Anyone with the token can remove your keys or sign exits for them. Keep the file readable only by the validator's user, and never expose the API port to other machines.

Endpoints​

Every request needs the Authorization: Bearer $TOKEN header. <VALIDATOR_PUBKEY> is the validator's BLS public key, with 0x.

PathGETPOSTDELETE
/eth/v1/keystoresList local keysImport keysRemove keys
/eth/v1/remotekeysList remote signer keysAdd keysRemove keys
/eth/v1/validator/<VALIDATOR_PUBKEY>/feerecipientReadSetReset to default
/eth/v1/validator/<VALIDATOR_PUBKEY>/gas_limitReadSetReset to default
/eth/v1/validator/<VALIDATOR_PUBKEY>/graffitiReadSetReset to default
/eth/v1/validator/<VALIDATOR_PUBKEY>/builder_configReadReplaceReset to default
/eth/v1/validator/<VALIDATOR_PUBKEY>/voluntary_exitSign an exit

Add keys​

Send the keystore file and its password.

For a new key, one that has never signed:

jq -n \
--rawfile ks <PATH_TO>/keystore-m_12381_3600_0_0_0-<TIMESTAMP>.json \
--arg pw "$(cat <KEYSTORE_PASSWORD_FILE>)" \
'{keystores: [$ks], passwords: [$pw]}' \
| curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @- \
$API/eth/v1/keystores | jq

jq builds the request body, and curl sends it. The body looks like this:

{
"keystores": [
"{\"crypto\": {...}, \"pubkey\": \"a1b2...\", \"path\": \"m/12381/3600/0/0/0\", \"uuid\": \"...\", \"version\": 4}"
],
"passwords": [
"<KEYSTORE_PASSWORD>"
]
}

The API expects the keystore as a string holding the file contents, not as a JSON object: that's what --rawfile does. To check the body before sending it, run the jq part alone.

For a key that has already signed anywhere, also send its slashing protection history (see Move your slashing protection history):

jq -n \
--rawfile ks <PATH_TO>/keystore-m_12381_3600_0_0_0-<TIMESTAMP>.json \
--arg pw "$(cat <KEYSTORE_PASSWORD_FILE>)" \
--rawfile sp <PATH_TO>/slashing_protection.json \
'{keystores: [$ks], passwords: [$pw], slashing_protection: $sp}' \
| curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @- \
$API/eth/v1/keystores | jq

The body now also has the slashing protection history, as a string too:

{
"keystores": [
"{\"crypto\": {...}, \"pubkey\": \"a1b2...\", \"path\": \"m/12381/3600/0/0/0\", \"uuid\": \"...\", \"version\": 4}"
],
"passwords": [
"<KEYSTORE_PASSWORD>"
],
"slashing_protection": "{\"metadata\": {...}, \"data\": [...]}"
}

The response gives each key a status: imported, duplicate (already there) or error with a message. The validator starts duties for imported keys without a restart; with doppelganger protection on, it first waits at least two epochs.

List the keys to check:

curl -s -H "Authorization: Bearer $TOKEN" $API/eth/v1/keystores | jq

Sign a voluntary exit​

An exit can't be undone

Once the network processes it, the validator stops for good. You can't start it again with the same key.

The validator client signs the exit and returns it. It does not send it to the network:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/voluntary_exit \
| jq '.data' > exit.json

The exit is valid from the epoch it names: by default, the current one. To hand someone an exit they can't use before a given epoch, add ?epoch=<EPOCH>. That doesn't schedule the exit: the network rejects it until that epoch.

Then submit it to your beacon node:

curl -s -X POST \
-H "Content-Type: application/json" \
-d @exit.json \
http://127.0.0.1:3500/eth/v1/beacon/pool/voluntary_exits

An empty 200 response means the beacon node accepted it and shares it with the network. See Exit a validator.

Remove keys​

Removing a key returns its slashing protection history. Save it in the same step:

curl -s -X DELETE \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"pubkeys":["<VALIDATOR_PUBKEY>"]}' \
$API/eth/v1/keystores \
| tee delete-response.json | jq '.data'
jq -r '.slashing_protection' delete-response.json > slashing_protection.json

The status is deleted, not_active, not_found, or error. Import slashing_protection.json wherever the key runs next.

caution

Removing a key doesn't exit the validator. It stays active and loses balance until something signs for it again. To stop for good, exit the validator.

Add remote signer keys​

With a remote signer (--validators-external-signer-url), the keys live in the signer, and the validator only needs their public keys. Use /eth/v1/remotekeys instead of /eth/v1/keystores. See Use a remote signer.

Without a remote signer, GET returns an empty list and POST and DELETE fail. Adding keys also needs --validators-external-signer-key-file, where Prysm saves them; without it, each key returns a status error.

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"remote_keys":[{"pubkey":"<VALIDATOR_PUBKEY>"}]}' \
$API/eth/v1/remotekeys | jq

Prysm always uses the signer URL from --validators-external-signer-url. A url field in the request is ignored, with a warning in the log.

List the keys to check:

curl -s -H "Authorization: Bearer $TOKEN" $API/eth/v1/remotekeys | jq

Remove remote signer keys​

You can remove keys only from the key file. Keys from --validators-external-signer-public-keys, a list, or a URL showing readonly: true, and removing one returns error: it would come back on the next reload. If such a key is also in the key file, Prysm removes it from the file, and the key keeps validating.

curl -s -X DELETE \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"pubkeys":["<VALIDATOR_PUBKEY>"]}' \
$API/eth/v1/remotekeys | jq
caution

Removing a key doesn't exit the validator. It stays active and loses balance until something signs for it again. To stop for good, exit the validator.

Set per-key settings​

The fee recipient, gas limit, and graffiti endpoints work the same way: POST sets the value for one key, GET reads it and, DELETE resets it, so the key uses the default again.

Set the fee recipient:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ethaddress":"<FEE_RECIPIENT_ADDRESS>"}' \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/feerecipient

Set the gas limit:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"gas_limit":"<GAS_LIMIT>"}' \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/gas_limit

Set the graffiti:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"graffiti":"<GRAFFITI>"}' \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/graffiti

Graffiti can be at most 32 bytes; longer graffiti is rejected with 400. Success returns an empty 202, and DELETE returns an empty 204.

Read a value back; here, the fee recipient:

curl -s -H "Authorization: Bearer $TOKEN" \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/feerecipient | jq

To print the fee recipient of every key at once:

prysmctl validator proposer-settings \
--token=$TOKEN \
--validator-host=$API

It prints one line per key:

Validator: <PUBKEY>. Fee-recipient: <ADDRESS>
A settings file replaces API changes

Prysm keeps these values across restarts, unless you start the validator with a proposer settings file that has a proposer_config section: at the next restart, the file replaces them. Manage per-key settings in one place. See Set your fee recipient.

Set the builder config of a key​

Read the config a key uses, with defaults filled in:

curl -s -H "Authorization: Bearer $TOKEN" \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/builder_config | jq

The response has min_bid, builder_boost_factor, max_execution_payment, and a builders list, with the same fields as a version 2 proposer settings file; see Use builders after Gloas (ePBS). auth_data and builder_pubkeys are 0x hex. You can edit it and send it back with POST, which replaces the key's whole builder config:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @builder-config.json \
$API/eth/v1/validator/<VALIDATOR_PUBKEY>/builder_config

DELETE removes the key's builder config, so it follows the validator's defaults again. That's not the same as sending "builders": [], which allows no builder at all.

Check that it works​

curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $TOKEN" \
$API/eth/v1/keystores

200 means the API is up and your token works. After adding or removing keys, list them again.