commaai/openpilot · error · UnauthorizedError

Unauthorized. Authenticate with openpilot/tools/lib/auth.py

Error message

Unauthorized. Authenticate with openpilot/tools/lib/auth.py

What it means

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.

Source

Thrown at openpilot/tools/lib/api.py:23

# TODO: this should be merged into common.api

class CommaApi:
  def __init__(self, token=None):
    self.session = requests.Session()
    self.session.headers['User-agent'] = 'OpenpilotTools'
    if token:
      self.session.headers['Authorization'] = 'JWT ' + token

    retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
    self.session.mount('https://', HTTPAdapter(max_retries=retries))

  def request(self, method, endpoint, **kwargs):
    with self.session.request(method, API_HOST + '/' + endpoint, **kwargs) as resp:
      resp_json = resp.json()
      if isinstance(resp_json, dict) and resp_json.get('error'):
        if resp.status_code in [401, 403]:
          raise UnauthorizedError('Unauthorized. Authenticate with openpilot/tools/lib/auth.py')

        e = APIError(str(resp.status_code) + ":" + resp_json.get('description', str(resp_json['error'])))
        e.status_code = resp.status_code
        raise e
      return resp_json

  def get(self, endpoint, **kwargs):
    return self.request('GET', endpoint, **kwargs)

  def post(self, endpoint, **kwargs):
    return self.request('POST', endpoint, **kwargs)

class APIError(Exception):
  pass

class UnauthorizedError(Exception):
  pass

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Authenticate: python openpilot/tools/lib/auth.py (then retry the original tool)
  2. If already 'logged in', refresh the token by re-running auth.py - expired JWTs are the most common cause
  3. For CI, provision the token via environment/config so CommaApi picks it up instead of passing none
  4. Confirm you are accessing your own routes/devices; 403 on foreign resources is expected even with a valid token

Example fix

# before
api = CommaApi()  # no token -> 401

# after (shell)
# python openpilot/tools/lib/auth.py  # complete browser login first
from openpilot.tools.lib.auth_config import get_token
api = CommaApi(token=get_token())
Defensive patterns

Strategy: try-catch

Validate before calling

from openpilot.tools.lib.auth_config import get_token
token = get_token()
assert token, 'not authenticated - run: python openpilot/tools/lib/auth.py'

Try / catch

from openpilot.tools.lib.api import UnauthorizedError, APIError
try:
    data = api.get('v1/route/...')
except UnauthorizedError:
    raise SystemExit('token missing/expired - run python tools/lib/auth.py and retry')
except APIError as e:
    raise SystemExit(f'api error {getattr(e, "status_code", "?")}: {e}')

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/6a87b6b8e545a208. Report an issue: GitHub.