chatgpt-plan-auth
Sign in with ChatGPT (ChatGPT plan usage) for locally hosted, open-source apps.
Implements the SIWC OAuth flow documented at
https://developers.openai.com/siwc/token-sharing-open-source/: register a
dynamic client, authorize with PKCE, exchange the code for tokens, keep them
refreshed and rotated, read the plan's model catalog, and shape Responses API
requests for the plan usage route. The access token is used as the bearer token
against https://api.openai.com/v1 instead of an API key.
The library is a straight extraction of Home Assistant core's
openai_conversation/chatgpt_auth.py (the reviewer's point: deep knowledge of
an external system belongs in a library, not in core). It carries no Home
Assistant imports, no hass, no config-entry or storage knowledge, and no
crypto dependency.
pip install chatgpt-plan-auth # protocol only: httpx
pip install chatgpt-plan-auth[openai] # + the OpenAI SDK client wrapper
What lives where
| Module | Contents |
|---|---|
chatgpt_plan_auth.const |
endpoints, dynamic_agent_client, scopes, the ext_agent_host_id scheme, rejected params/tools lists, model suggestions |
chatgpt_plan_auth.auth |
authorization URL + PKCE/state/nonce, callback parsing, token requests, code exchange, ID token claim validation, credentials, token lifecycle |
chatgpt_plan_auth.catalog |
plan model catalog fetch/parse and the suggestion fallback |
chatgpt_plan_auth.requests |
shape_request_for_subscription, subscription_error_message |
chatgpt_plan_auth.client |
httpx auth flow and openai.AsyncOpenAI subclass (extra: openai) |
Everything is caller-driven: no Home Assistant, no global session, no storage.
import httpx
from chatgpt_plan_auth import (
ChatGPTCredentials, ChatGPTAuth, async_exchange_code, create_authorization_request,
create_ext_agent_host_id, parse_callback_url,
)
host_id = create_ext_agent_host_id() # generate once, persist it yourself
request = create_authorization_request(client_id=None, ext_agent_host_id=host_id)
# send the user to request.url, then read the loopback address bar (any machine):
params = parse_callback_url(callback_url)
async with httpx.AsyncClient() as client:
credentials = await async_exchange_code(
client, params["code"], request=request,
callback_client_id=params.get("client_id"), callback_state=params.get("state"),
)
# Later, per request:
auth = ChatGPTAuth(credentials, client=client, on_update=save) # `save` persists rotation
token = await auth.async_get_access_token()
Errors: ChatGPTPlanAuthFatalError means the stored refresh token can never be
used again (start your re-authentication flow); any other
ChatGPTPlanAuthError is transient. With the openai extra,
ChatGPTSubscriptionClient(auth, http_client=...) attaches the token per
request and retries once on a 401.
Deliberate simplification: no ID token signature verification
/.well-known/jwks.json is never fetched, RSA keys are never parsed and no JWT
or crypto dependency is carried — the JWKS helpers and their cache from core are
gone.
Rationale: the ID token arrives directly from the provider's TLS-authenticated token endpoint in the same response as the code exchange. That is the trust model Home Assistant's own OAuth2 flow uses, and it is exactly the dependency-heavy part the core reviewer objected to. A signature check adds nothing there: anyone able to forge the token response could equally forge the signature.
What is still verified on every code exchange:
issishttps://auth.openai.comaudcontains the client id the code was exchanged withexpis in the futurenonceequals the nonce of that authorization attempt- the callback
stateequals the state of that authorization attempt
Token endpoint errors (invalid_grant, invalid_client, token_expired, ...)
still surface as fatal errors and never invalidate good credentials.
If you need signature verification — or you cannot trust your network path to
the token endpoint — verify the ID token against the JWKS in your application
before calling validate_id_token.
Out of scope (stays in the consuming application)
- Home Assistant: config-entry inspection/storage,
Storefor the host id, the HA http client, config-flow and reauth steps. - Application branding: pass
agent_name_hint(defaults tochatgpt-plan-auth). - Persisting credentials: the library returns plain mappings
(
ChatGPTCredentials.as_entry_data()), nothing is written to disk.
Tests
uv run --with pytest-asyncio --with openai python -m pytest tests -q
All network access is a fake httpx.MockTransport; no test touches the network
and no test needs key material.
Metadata
Release files for chatgpt-plan-auth 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| chatgpt_plan_auth-1.0.0.tar.gz | 21.8 kB | Without provenance | Details for chatgpt_plan_auth-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| chatgpt_plan_auth-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for chatgpt_plan_auth-1.0.0-py3-none-any.whl |
Total release size: 40.5 kB
Release files / chatgpt_plan_auth-1.0.0.tar.gz
| Download URL | chatgpt_plan_auth-1.0.0.tar.gz |
|---|---|
| Size | 21.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b1f2030fc2d36235388437fbd0a8443eb597957e506db6f00fffd42e3d362c1
|
|
BLAKE2b-256 checksum How to use checksums |
3d0e81682a6296ef183fa7525af1dab4dee6d1dab341cc33194db2b472cdb432
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / chatgpt_plan_auth-1.0.0-py3-none-any.whl
| Download URL | chatgpt_plan_auth-1.0.0-py3-none-any.whl |
|---|---|
| Size | 18.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4c86b8a6ab9214da29f99b76c91f9772ae86333cd82fffb5f53d3f95ee0e557b
|
|
BLAKE2b-256 checksum How to use checksums |
80e5854554fe0d2af032b286fe8dc4afc641d082d33679209c63702d89a94add
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for chatgpt-plan-auth 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.