ha-spacexai-auth

September 17, 2026 · View on GitHub

Shared SpaceXAI / xAI Grok OAuth helpers for Home Assistant integrations (device code, PKCE S256, refresh, TokenSet).

This is Phase A only: a pure Python library. There is no Home Assistant domain, Config Flow UI, TTS, Jev router, or Application Credentials in this repo. Those stay in each integration.

Install

pip install ha-spacexai-auth

Until this is on PyPI, integrations can depend on a git pin:

ha-spacexai-auth @ git+https://github.com/luxus/ha-spacexai-auth.git@main

Home Assistant manifest.json:

{
  "requirements": ["ha-spacexai-auth==0.1.0"]
}

Requires Python ≥ 3.11. No Home Assistant dependency. HTTP is session-agnostic: pass any aiohttp-like object with .post(...) (Home Assistant’s async_get_clientsession(hass) works).

How jev / TTS will import

from ha_spacexai_auth import (
    CLIENT_ID,
    SCOPES,
    TOKEN_URL,
    DeviceAuthorization,
    PkcePair,
    SpaceXaiAuthError,
    SpaceXaiAuthExpired,
    SpaceXaiEntitlementError,
    TokenSet,
    authorization_headers,
    ensure_fresh,
    generate_pkce,
    poll_device_token,   # alias of poll_token
    poll_token,
    refresh_access_token,
    request_device_code,  # alias of start_device_auth
    start_device_auth,
    token_data_updates,
)

# Config Flow lives in the integration, not here:
session = async_get_clientsession(hass)
device = await start_device_auth(session)
# Show device.user_code + device.verification_uri_complete to the user.
tokens = await poll_token(session, device)
hass.config_entries.async_update_entry(entry, data=tokens.to_entry_data())

# API calls:
headers = authorization_headers(tokens.access_token)

# Refresh if expires_at - 60s <= now. If expires_at is missing/None, tokens
# are returned unchanged (no refresh).
tokens = await ensure_fresh(session, tokens)
hass.config_entries.async_update_entry(
    entry, data={**entry.data, **token_data_updates(tokens)}
)

Package import path: ha_spacexai_auth.

Public API

SymbolRole
generate_pkceRFC 7636 S256 PkcePair
start_device_auth / request_device_codeRFC 8628 device-code start
poll_token / poll_device_tokenPoll until token, slow_down, or expiry
refresh_access_tokengrant_type=refresh_token; keep old RT if omitted
ensure_freshRefresh when expires_at - skew_seconds <= now (default skew 60s). Missing/None expires_at → return tokens as-is
TokenSetaccess_token, refresh_token, expires_at, token_type, scope
TokenSet.to_entry_data / from_entry_dataHA config-entry roundtrip
token_data_updatesToken keys only (no auth_method / api_key)
authorization_headersAuthorization: Bearer …
DeviceAuthorization, PkcePairDevice-flow types
SpaceXaiAuthError, SpaceXaiAuthExpired, SpaceXaiEntitlementErrorTyped errors (error code optional)
ConstantsISSUER, CLIENT_ID, URLs, SCOPES, DEVICE_GRANT_TYPE, REFERRER, …

Modules

ModuleContents
ha_spacexai_auth.constIssuer, discovery, device/token/revoke URLs, public client, scopes
ha_spacexai_auth.device_flowstart_device_auth / poll_token (RFC 8628 + PKCE S256)
ha_spacexai_auth.refreshrefresh_access_token, ensure_fresh; persist rotated refresh token
ha_spacexai_auth.storeTokenSet, to_entry_data / from_entry_data
ha_spacexai_auth.headersBearer Authorization
ha_spacexai_auth.errorsSpaceXaiAuthError, SpaceXaiAuthExpired, SpaceXaiEntitlementError

Config entry contract

TokenSet.to_entry_data() / from_entry_data() use these keys:

KeyNotes
access_tokenrequired
refresh_tokenoptional (None if the IdP did not issue one)
expires_atUnix timestamp (float)
token_typedefault Bearer
scopespace-delimited; optional
auth_methodoauth or api_key (on the entry, not on TokenSet)
api_keyoptional; integrations keep this, not the library core

Pinned OAuth constants

Verified against xai-org/grok-build crates/codegen/xai-grok-login/src/config.rs and discovery at https://auth.x.ai/.well-known/openid-configuration.

ConstantValue
Issuerhttps://auth.x.ai
Client IDb1a00492-073a-47ea-816f-4c329264a828 (public, no secret)
Devicehttps://auth.x.ai/oauth2/device/code
Tokenhttps://auth.x.ai/oauth2/token
Revokehttps://auth.x.ai/oauth2/revoke
Granturn:ietf:params:oauth:grant-type:device_code
Referrergrok-build
Scopesopenid profile email offline_access grok-cli:access api:access conversations:read conversations:write

Helm/Vera pins the eight-scope set above (same as luxus/pi-xai). grok-build default_oauth2_scopes() also currently lists workspaces:read and workspaces:write; those are not requested here.

Phase B (out of scope)

Shared Home Assistant config-entry helpers / a reusable Config Flow live in each integration (jev, TTS, …), not in this package.

License

MIT