{"record":{"id":"654298a0c49e90cb","repo":"commaai/openpilot","slug":"private-key-is-not-configured","errorCode":null,"errorMessage":"private key is not configured","messagePattern":"private key is not configured","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"openpilot/common/api.py","lineNumber":31,"sourceCode":"\n\nclass Api:\n  def __init__(self, dongle_id):\n    self.dongle_id = dongle_id\n    self.jwt_algorithm, self.private_key, _ = get_key_pair()\n\n  def get(self, *args, **kwargs):\n    return self.request('GET', *args, **kwargs)\n\n  def post(self, *args, **kwargs):\n    return self.request('POST', *args, **kwargs)\n\n  def request(self, method, endpoint, timeout=None, access_token=None, **params):\n    return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)\n\n  def get_token(self, payload_extra=None, expiry_hours=1):\n    if self.private_key is None:\n      raise RuntimeError(\"private key is not configured\")\n    now = datetime.now(UTC).replace(tzinfo=None)\n    payload = {\n      'identity': self.dongle_id,\n      'nbf': now,\n      'iat': now,\n      'exp': now + timedelta(hours=expiry_hours)\n    }\n    if payload_extra is not None:\n      payload.update(payload_extra)\n    token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)\n    if isinstance(token, bytes):\n      token = token.decode('utf8')\n    return token\n\n\ndef api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):\n  headers = {}\n  if access_token is not None:","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/common/api.py#L13-L49","documentation":"Api.get_token() signs a JWT with the device's private key to authenticate to the comma API. The Api constructor loads the key pair via get_key_pair(), which scans PERSIST/comma/id_rsa(.pub) and id_ecdsa(.pub). If neither pair exists on disk, private_key stays None and get_token raises this RuntimeError instead of attempting to sign.","triggerScenarios":"Calling Api(dongle_id).get_token(...) on a device (or dev machine) where Paths.persist_root()/comma/ does not contain both id_rsa and id_rsa.pub, or both id_ecdsa and id_ecdsa.pub. This happens on fresh installations before key generation, on non-device PCs without a persisted /comma directory, or if the persist partition was wiped.","commonSituations":"Running openpilot code off-device (CI, laptops) without copying the persist partition; a factory reset or re-flash that cleared /persist/comma; a partially provisioned device where only the .pub file exists (get_key_pair requires both files).","solutions":["Generate an RSA or ECDSA key pair in the persist directory: mkdir -p <persist>/comma && ssh-keygen -t ecdsa -f <persist>/comma/id_ecdsa (no passphrase), so both id_ecdsa and id_ecdsa.pub exist","If keys exist but under a different Paths.persist_root(), verify Paths.persist_root() resolves where you expect and move/copy the comma/ directory there","Guard calls: only invoke get_token() after checking api.get_key_pair() returns a non-None key, and skip/queue API-authenticated work otherwise","On a real device, let the provisioning flow (e.g. setup/registration) create the keys before any Api usage"],"exampleFix":"// before\napi = Api(dongle_id)\ntoken = api.get_token()  # RuntimeError: private key is not configured\n\n# after\nfrom openpilot.common.api import Api, get_key_pair\n\nalgorithm, private_key, _ = get_key_pair()\nif private_key is None:\n  print('device not provisioned with keys; skipping API token')\nelse:\n  token = Api(dongle_id).get_token()","handlingStrategy":"validation","validationCode":"from openpilot.common.api import get_key_pair\n\nalgorithm, private_key, public_key = get_key_pair()\nif private_key is None:\n  # device not provisioned; skip or trigger provisioning\n  raise SystemExit('no comma key pair in persist; provision the device first')","typeGuard":"def has_private_key() -> bool:\n  from openpilot.common.api import get_key_pair\n  return get_key_pair()[1] is not None","tryCatchPattern":"try:\n  token = api.get_token()\nexcept RuntimeError as e:\n  if 'private key is not configured' in str(e):\n    # provision keys or degrade gracefully\n    ...\n  raise","preventionTips":["Check get_key_pair() returns a key before constructing authenticated API flows","On new devices, run registration/provisioning before any Api usage","Keep id_rsa(.pub) or id_ecdsa(.pub) pairs intact in PERSIST/comma — never delete just one of the pair"],"tags":["authentication","jwt","provisioning","device-state","python"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}