Skip to main content

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:

  • iss is https://auth.openai.com
  • aud contains the client id the code was exchanged with
  • exp is in the future
  • nonce equals the nonce of that authorization attempt
  • the callback state equals 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, Store for the host id, the HA http client, config-flow and reauth steps.
  • Application branding: pass agent_name_hint (defaults to chatgpt-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)

Source distribution for chatgpt-plan-auth 1.0.0
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)

Table of built distributions (wheels) for chatgpt-plan-auth 1.0.0
File Interpreter ABI Platform Actions
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page