Skip to main content

outlet: use the AI power you already pay for

PyPI version zero dependencies type hints included MIT license

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)

Source distribution for useoutlet 0.1.0
File Size Uploaded
useoutlet-0.1.0.tar.gz 32.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for useoutlet 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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