{"record":{"id":"6a87b6b8e545a208","repo":"commaai/openpilot","slug":"unauthorized-authenticate-with-openpilot-tools-li","errorCode":null,"errorMessage":"Unauthorized. Authenticate with openpilot/tools/lib/auth.py","messagePattern":"Unauthorized\\. Authenticate with openpilot/tools/lib/auth\\.py","errorType":"exception","errorClass":"UnauthorizedError","httpStatus":401,"severity":"error","filePath":"openpilot/tools/lib/api.py","lineNumber":23,"sourceCode":"\n# TODO: this should be merged into common.api\n\nclass CommaApi:\n  def __init__(self, token=None):\n    self.session = requests.Session()\n    self.session.headers['User-agent'] = 'OpenpilotTools'\n    if token:\n      self.session.headers['Authorization'] = 'JWT ' + token\n\n    retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])\n    self.session.mount('https://', HTTPAdapter(max_retries=retries))\n\n  def request(self, method, endpoint, **kwargs):\n    with self.session.request(method, API_HOST + '/' + endpoint, **kwargs) as resp:\n      resp_json = resp.json()\n      if isinstance(resp_json, dict) and resp_json.get('error'):\n        if resp.status_code in [401, 403]:\n          raise UnauthorizedError('Unauthorized. Authenticate with openpilot/tools/lib/auth.py')\n\n        e = APIError(str(resp.status_code) + \":\" + resp_json.get('description', str(resp_json['error'])))\n        e.status_code = resp.status_code\n        raise e\n      return resp_json\n\n  def get(self, endpoint, **kwargs):\n    return self.request('GET', endpoint, **kwargs)\n\n  def post(self, endpoint, **kwargs):\n    return self.request('POST', endpoint, **kwargs)\n\nclass APIError(Exception):\n  pass\n\nclass UnauthorizedError(Exception):\n  pass\n","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/tools/lib/api.py#L5-L41","documentation":"CommaApi.request() inspects every JSON response: if the body contains an 'error' key and the HTTP status is 401 or 403, it raises UnauthorizedError pointing at the auth helper. The JWT token in the Authorization header is missing, expired, or not accepted for this endpoint. Other error statuses raise generic APIError instead.","triggerScenarios":"Any CommaApi().get()/post() call when ~/.comma/home/... auth token is absent or expired; token valid but the resource belongs to another user's dongle; tools/lib API key not set in the environment.","commonSituations":"First use of comma tools on a machine without logging in; token older than its expiry after weeks of no use; multiple accounts / CI without credentials.","solutions":["Authenticate: python openpilot/tools/lib/auth.py (then retry the original tool)","If already 'logged in', refresh the token by re-running auth.py - expired JWTs are the most common cause","For CI, provision the token via environment/config so CommaApi picks it up instead of passing none","Confirm you are accessing your own routes/devices; 403 on foreign resources is expected even with a valid token"],"exampleFix":"# before\napi = CommaApi()  # no token -> 401\n\n# after (shell)\n# python openpilot/tools/lib/auth.py  # complete browser login first\nfrom openpilot.tools.lib.auth_config import get_token\napi = CommaApi(token=get_token())","handlingStrategy":"try-catch","validationCode":"from openpilot.tools.lib.auth_config import get_token\ntoken = get_token()\nassert token, 'not authenticated - run: python openpilot/tools/lib/auth.py'","typeGuard":null,"tryCatchPattern":"from openpilot.tools.lib.api import UnauthorizedError, APIError\ntry:\n    data = api.get('v1/route/...')\nexcept UnauthorizedError:\n    raise SystemExit('token missing/expired - run python tools/lib/auth.py and retry')\nexcept APIError as e:\n    raise SystemExit(f'api error {getattr(e, \"status_code\", \"?\")}: {e}')","preventionTips":["Check for a stored token before long-running jobs and fail fast instead of mid-run","Catch UnauthorizedError specifically (it subclasses from APIError family) and prompt re-auth rather than retrying"],"tags":["api","authentication","http-401","openpilot"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}