Python SDK
nopii-sdk is the server half of a protected app for Python: client sessions for your frontend, proxy settings for the official OpenAI and Anthropic clients, and server-side tokenization. Sync and async.
Preview
Install
pip install nopii-sdkimport os
from nopii_sdk import NoPII
nopii = NoPII(secret_key=os.environ["NOPII_SECRET_KEY"])Install nopii-sdk, import nopii_sdk
nopii on PyPI is an unrelated project. pip install nopii installs someone else's code.Python 3.10 or later. One instance is safe to share across threads and requests.
Mint client sessions
Your frontend asks your server for a session. Bind it to your own user id, so that user, and only that user, can reveal their earlier messages.
# FastAPI
@app.post("/api/nopii-session")
def nopii_session(user: User = Depends(current_user)):
session = nopii.client_sessions.create(end_user_id=user.id, ttl_seconds=900)
return {"token": session.token, "expiresAt": session.expires_at.isoformat()}Forward messages to the LLM
The browser sends your route tokens. Forward them in tokens-only mode, passing the request headers so the browser's NoPII session carries through.
from openai import OpenAI
from anthropic import Anthropic
@app.post("/api/chat")
async def chat(request: Request):
body = await request.json() # already tokenized in the browser
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
**nopii.openai_options(request_headers=request.headers, mode="tokens-only"),
)
return client.chat.completions.create(model="gpt-4o", messages=body["messages"])
# For Anthropic, inside the same kind of handler:
# claude = Anthropic(
# api_key=os.environ["ANTHROPIC_API_KEY"],
# **nopii.anthropic_options(request_headers=request.headers, mode="tokens-only"),
# )The reply comes back tokenized, and the browser SDK reveals it for the user. Use openai_options for OpenAI and anthropic_options for Anthropic: the Anthropic client adds /v1/messages itself, so one shared base URL cannot serve both.
Sessions deeper in your code
client = OpenAI(api_key=..., **nopii.openai_options())
with nopii.session(session_id, mode="tokens-only"):
summarize(client, thread) # every proxy call in here carries the sessionThe session lives in a context variable, so each thread and each asyncio task keeps its own.
Tokenize on the server
result = nopii.tokenize(["Contract signed by Wei Chen"], end_user_id=customer.id)
result.texts[0] # "Contract signed by [NAME: ...]"
found = nopii.detect(["Is there PII in here?"]) # not tokenized, not metered
revealed = nopii.detokenize(["8f2kQ1xZ"], session_id=session_id)
revealed.tokens["8f2kQ1xZ"].status # "revealed", "forbidden", "purged", or "not_found"Reveal grants
Let a support agent read a customer's conversation for a limited time. Every reveal under the grant is logged.
grant = nopii.reveal_grants.create(
subject_end_user_id=ticket.customer_id,
grantee_end_user_id=agent.id,
ttl_seconds=900,
reason=f"ticket {ticket.id}",
)
# The agent's browser passes grant.id when revealing.
nopii.reveal_grants.revoke(grant.id)Async
from openai import AsyncOpenAI
from nopii_sdk import AsyncNoPII
nopii = AsyncNoPII(secret_key=os.environ["NOPII_SECRET_KEY"])
session = await nopii.client_sessions.create(end_user_id=user.id)
client = AsyncOpenAI(api_key=..., **nopii.openai_options(request_headers=request.headers, mode="tokens-only"))Errors
| Exception | When |
|---|---|
NoPIIUnavailableError | NoPII could not be reached or had a server error. Nothing was tokenized. |
NoPIIAuthError | The key was rejected, or lacks the scope for the call. |
NoPIIRequestError | The request was invalid, for example too many texts. |
All three derive from NoPIIError.
HTTP libraries
Current openai and anthropic releases are built on httpx2, and anthropic rejects an httpx client. The SDK builds its proxy client on httpx2 when it is installed. For older client versions, pass http_library="httpx" to NoPII.
Related
- Browser SDK: the client half of Tier 2
- Keys and reveal policy: sessions, grants, and the reveal log
- Client API reference: the endpoints behind these methods