diff --git a/cmd/tesla-control/commands.go b/cmd/tesla-control/commands.go
index 59dabd26..5b802abb 100644
--- a/cmd/tesla-control/commands.go
+++ b/cmd/tesla-control/commands.go
@@ -13,6 +13,7 @@ import (
 	"github.com/teslamotors/vehicle-command/pkg/account"
 	"github.com/teslamotors/vehicle-command/pkg/cli"
 	"github.com/teslamotors/vehicle-command/pkg/protocol"
+	"github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/keys"
 	"github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/vcsec"
 	"github.com/teslamotors/vehicle-command/pkg/vehicle"
 )
@@ -229,12 +230,12 @@ var commands = map[string]*Command{
 		requiresFleetAPI: false,
 		args: []Argument{
 			Argument{name: "PUBLIC_KEY", help: "file containing public key (or corresponding private key)"},
-			Argument{name: "ROLE", help: "One of: owner, driver"},
+			Argument{name: "ROLE", help: "One of: owner, driver, fm (fleet manager), vehicle_monitor, charging_manager"},
 			Argument{name: "FORM_FACTOR", help: "One of: nfc_card, ios_device, android_device, cloud_key"},
 		},
 		handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error {
-			role := strings.ToUpper(args["ROLE"])
-			if role != "OWNER" && role != "DRIVER" {
+			role, ok := keys.Role_value["ROLE_"+strings.ToUpper(args["ROLE"])]
+			if !ok {
 				return fmt.Errorf("%w: invalid ROLE", ErrCommandLineArgs)
 			}
 			formFactor, ok := vcsec.KeyFormFactor_value["KEY_FORM_FACTOR_"+strings.ToUpper(args["FORM_FACTOR"])]
@@ -245,7 +246,7 @@ var commands = map[string]*Command{
 			if err != nil {
 				return fmt.Errorf("invalid public key: %s", err)
 			}
-			return car.AddKey(ctx, publicKey, role == "OWNER", vcsec.KeyFormFactor(formFactor))
+			return car.AddKeyWithRole(ctx, publicKey, keys.Role(role), vcsec.KeyFormFactor(formFactor))
 		},
 	},
 	"add-key-request": &Command{
@@ -254,12 +255,12 @@ var commands = map[string]*Command{
 		requiresFleetAPI: false,
 		args: []Argument{
 			Argument{name: "PUBLIC_KEY", help: "file containing public key (or corresponding private key)"},
-			Argument{name: "ROLE", help: "One of: owner, driver"},
+			Argument{name: "ROLE", help: "One of: owner, driver, fm (fleet manager), vehicle_monitor, charging_manager"},
 			Argument{name: "FORM_FACTOR", help: "One of: nfc_card, ios_device, android_device, cloud_key"},
 		},
 		handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error {
-			role := strings.ToUpper(args["ROLE"])
-			if role != "OWNER" && role != "DRIVER" {
+			role, ok := keys.Role_value["ROLE_"+strings.ToUpper(args["ROLE"])]
+			if !ok {
 				return fmt.Errorf("%w: invalid ROLE", ErrCommandLineArgs)
 			}
 			formFactor, ok := vcsec.KeyFormFactor_value["KEY_FORM_FACTOR_"+strings.ToUpper(args["FORM_FACTOR"])]
@@ -270,7 +271,7 @@ var commands = map[string]*Command{
 			if err != nil {
 				return fmt.Errorf("invalid public key: %s", err)
 			}
-			if err := car.SendAddKeyRequest(ctx, publicKey, role == "OWNER", vcsec.KeyFormFactor(formFactor)); err != nil {
+			if err := car.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role(role), vcsec.KeyFormFactor(formFactor)); err != nil {
 				return err
 			}
 			fmt.Printf("Sent add-key request to %s. Confirm by tapping NFC card on center console.\n", car.VIN())
diff --git a/pkg/vehicle/security.go b/pkg/vehicle/security.go
index e27d53ed..9a2b9239 100644
--- a/pkg/vehicle/security.go
+++ b/pkg/vehicle/security.go
@@ -9,6 +9,7 @@ import (
 	"github.com/teslamotors/vehicle-command/pkg/connector"
 	"github.com/teslamotors/vehicle-command/pkg/protocol"
 	carserver "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/carserver"
+	"github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/keys"
 	"github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/vcsec"
 )
 
@@ -178,10 +179,21 @@ func (v *Vehicle) TriggerHomelink(ctx context.Context, latitude float32, longitu
 // AddKey adds a public key to the vehicle's whitelist. If isOwner is true, the new key can
 // authorize changes to vehicle access controls, such as adding/removing other keys.
 func (v *Vehicle) AddKey(ctx context.Context, publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) error {
+	if isOwner {
+		return v.AddKeyWithRole(ctx, publicKey, keys.Role_ROLE_OWNER, formFactor)
+	}
+	return v.AddKeyWithRole(ctx, publicKey, keys.Role_ROLE_DRIVER, formFactor)
+}
+
+// AddKeyWithRole adds a public key to the vehicle's whitelist. See [Protocol Specification] for
+// more information on roles.
+//
+// [Protocol Specification]: https://github.com/teslamotors/vehicle-command/blob/main/pkg/protocol/protocol.md#roles
+func (v *Vehicle) AddKeyWithRole(ctx context.Context, publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) error {
 	if publicKey.Curve() != ecdh.P256() {
 		return protocol.ErrInvalidPublicKey
 	}
-	payload := addKeyPayload(publicKey, isOwner, formFactor)
+	payload := addKeyPayload(publicKey, role, formFactor)
 	encodedPayload, err := proto.Marshal(payload)
 	if err != nil {
 		return err
@@ -276,13 +288,24 @@ func (v *Vehicle) Unlock(ctx context.Context) error {
 // attempting to call v.SessionInfo with the domain argument set to
 // [universal.Domain_DOMAIN_INFOTAINMENT].
 func (v *Vehicle) SendAddKeyRequest(ctx context.Context, publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) error {
+	if isOwner {
+		return v.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role_ROLE_OWNER, formFactor)
+	}
+	return v.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role_ROLE_DRIVER, formFactor)
+}
+
+// SendAddKeyRequestWithRole behaves like [SendAddKeyRequest] except the new key's role can be
+// specified explicitly. See [Protocol Specification] for more information on roles.
+//
+// [Protocol Specification]: https://github.com/teslamotors/vehicle-command/blob/main/pkg/protocol/protocol.md#roles
+func (v *Vehicle) SendAddKeyRequestWithRole(ctx context.Context, publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) error {
 	if publicKey.Curve() != ecdh.P256() {
 		return protocol.ErrInvalidPublicKey
 	}
 	if _, ok := v.conn.(connector.FleetAPIConnector); ok {
 		return protocol.ErrRequiresBLE
 	}
-	encodedPayload, err := proto.Marshal(addKeyPayload(publicKey, isOwner, formFactor))
+	encodedPayload, err := proto.Marshal(addKeyPayload(publicKey, role, formFactor))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/vehicle/vcsec.go b/pkg/vehicle/vcsec.go
index f07434e6..e48c05cf 100644
--- a/pkg/vehicle/vcsec.go
+++ b/pkg/vehicle/vcsec.go
@@ -123,13 +123,7 @@ func (v *Vehicle) executeWhitelistOperation(ctx context.Context, payload []byte)
 	return err
 }
 
-func addKeyPayload(publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) *vcsec.UnsignedMessage {
-	var role keys.Role
-	if isOwner {
-		role = keys.Role_ROLE_OWNER
-	} else {
-		role = keys.Role_ROLE_DRIVER
-	}
+func addKeyPayload(publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) *vcsec.UnsignedMessage {
 	return &vcsec.UnsignedMessage{
 		SubMessage: &vcsec.UnsignedMessage_WhitelistOperation{
 			WhitelistOperation: &vcsec.WhitelistOperation{