useoutlet
Let your users plug in the AI they already pay for.
Outlet connects a user's existing AI account (Anthropic, OpenAI, Google, or any OpenAI-compatible provider) to your app through capped, revocable App keys, one per app. Never their raw credentials. Your app calls the provider directly with the official SDK. Outlet is never in the data path.
Status: direct mode works today (validated bring-your-own-key, no server). Vault mode (capped, revocable App keys) is open. Register your app at useoutlet.dev/register. The same session in both modes. Protocol docs at useoutlet.dev. Feedback welcome.
This is the Python SDK, the server side of Outlet. It starts a Vault connection, takes the session back, and refreshes, checks and revokes it. Standard library only. Python 3.10 and up. The Connect your AI button, the CLI and the MCP docs server live in the npm package, @useoutlet/sdk.
Why
- Users already pay for AI. They shouldn't pay again inside every app.
- Developers shouldn't store customer API keys (breach target, compliance).
- Raw keys are all-or-nothing. Outlet keys are per-app, capped, revocable.
Install
pip install useoutlet
Quickstart
pip install useoutlet
Start a Vault connection from your server. Vault needs a registered app ID and app secret. Register your app to get them. The app secret stays on your server.
import os
from useoutlet import Outlet
outlet = Outlet(app_id="app_yourapp", app_secret=os.environ["OUTLET_APP_SECRET"])
# 1. Start a Vault connection request. Send the user to its grant URL.
request = outlet.connect(providers=["openai"], requested_cap_usd=10)
print("Approve the Vault connection at", request.grant_url)
# 2. Wait for the approval. The session holds the Vault App key.
session = outlet.wait(request.id)
print("Connected:", session.grant_id)
# 3. Call the provider like you already do, with its official SDK.
# ai = OpenAI(api_key=session.keys["openai"])
Save it as quickstart.py and run it.
python quickstart.py
session.keys["openai"] holds the Vault App key. Store session.grant_id,
not the key. Then call the provider like you already do, with its official
SDK.
When a connection ends
A connection can end while your app is running: the user's Vault connection
is paused at its cap, revoked or disconnected, or its refresh token is gone.
The SDK turns each into one typed error, ConnectionEndedError, with
reason (capped, revoked or expired) and grant_id. status(),
refresh() and wait() raise it.
from useoutlet import ConnectionEndedError
try:
info = outlet.status(session.grant_id)
except ConnectionEndedError as e:
print(e.reason) # "capped", "revoked" or "expired"
For a capped connection, the user raises the cap on useoutlet.dev and
refresh() returns the key. For a revoked or expired one, ask the user to
connect again.
Usage today (direct mode)
Your user pastes their own API key; the SDK validates it locally (catches provider mix-ups, refuses admin keys) and hands back a session. No network, nothing sent to Outlet. A local format check.
from useoutlet import direct
session = direct({"openai": user_pasted_key})
from openai import OpenAI
ai = OpenAI(api_key=session.keys["openai"])
Works with any OpenAI-compatible provider. Pass the key under that provider and point the OpenAI SDK at its base URL:
session = direct({"groq": user_pasted_key})
ai = OpenAI(api_key=session.keys["groq"], base_url="https://api.groq.com/openai/v1")
OpenAI, Anthropic, and Google get strict key-format checks (mix-ups caught, admin keys refused); other providers are accepted with the same admin-key safety check, since their key formats vary.
Vault mode (the same session)
Same session shape. The pasted key becomes an App key: provisioned inside the user's own account for your app alone, capped, and revocable.
# later: re-fetch keys (session.grant_id is the thing you persist)
fresh = outlet.refresh(session.grant_id)
# check spend / status
info = outlet.status(session.grant_id)
print(f"{info.spend_usd} of {info.cap_usd} used")
# the app's side of revoke
outlet.revoke(session.grant_id)
No app secret? Public clients use PKCE
An app without an app secret registers as a public client instead: no secret, just a registered return address. Grant creation is secured by PKCE (protocol docs at useoutlet.dev), so the flow spans the return address in two calls:
outlet = Outlet(app_id="app_yourapp") # no secret: a public client
# on your [ Connect your AI ] route: generates PKCE, send the user to the grant URL
request = outlet.connect(providers=["anthropic"], redirect_uri="https://yourapp.com/outlet/return")
# on the route at the return address: verifies state, exchanges the code
session = outlet.exchange(code, state)
ai = Anthropic(api_key=session.keys["anthropic"])
# session.refresh_token (not an app secret) authorizes later calls. The client holds it.
fresh = outlet.refresh(session.grant_id)
# fresh.refresh_token replaces the one you sent; the old one now gets 401. Store the new one.
The provider key arrives only on the back-channel exchange, never in the
redirect URL. The refresh_token belongs to this one grant.
Outlet registers http://localhost/outlet/return for every app. It matches
any port.
The client holds each grant's refresh token in memory. From another process,
pass refresh_token= to refresh(), status() or revoke() from your own
store, and pass the GrantRequest you kept to exchange(). AsyncOutlet
has the same methods for asyncio apps.
Example: examples/python/, a FastAPI app with the connect route and the return address.
Several providers
Create a separate connection for each provider your app uses. A text connection can sit alongside connections for video and voice. If the same provider serves several models, those models can use that provider’s connection.
What your app never sees (vault mode)
- The user's root API key or account credentials
- Other apps' keys or spend
- Anything after revocation. A revoked grant stops working
The user's own key, in your app, by their choice. Validated, never an admin credential. The same API you keep when you upgrade to vault mode.
What Outlet never does
No proxying. No token markup. No model routing. No prompt storage. Free for end users, forever. Direct mode is free. The vault is the paid product for developers. MIT licensed.
Docs: useoutlet.dev · Source · Contact: hello@useoutlet.dev
Metadata
Release files for useoutlet 0.1.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 | |
|---|---|---|---|
| useoutlet-0.1.0.tar.gz | 32.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| useoutlet-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.2 kB
Release files / useoutlet-0.1.0.tar.gz
| Download URL | useoutlet-0.1.0.tar.gz |
|---|---|
| Size | 32.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70c42ac5b71ed6e6d1d6db7dd5daab4a5c3e9b8a1ebda545bb84faa205dd31f9
|
|
BLAKE2b-256 checksum How to use checksums |
4863b4f1fa5a8036254e4a013b3222b3457a0316cffae044e12b18c32fa4856e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / useoutlet-0.1.0-py3-none-any.whl
| Download URL | useoutlet-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c2de7cfbaa15c7d7c2f3da32b3849e3839ea65762977cb231f54dd103ec38ccb
|
|
BLAKE2b-256 checksum How to use checksums |
58c85c4b08b7de274d70d878ec11db99da2088884ec8e88a951b31ac380838df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log