offrec
Official Python client for offrec: zero-retention inference for frontier models. AI, off the record.
- Frontier models, one key. Claude, Gemini, Gemma, DeepSeek, Kimi, Qwen and GLM
through
https://api.offrec.ai/v1, priced per model. - Zero retention. offrec never stores or logs your prompts or the answers. The models run on managed providers under zero-retention terms.
- A receipt for every call. Signed with Ed25519 and checkable against a public key.
Docs: docs.offrec.ai · Console:
console.offrec.ai · R package: install.packages("pnotp")
Formerly
pnotp.pip install offrecreplacespip install pnotp, with the same API: changeimport pnotptoimport offrec. Nothing else has to change:pnotp_sk_keys,PNOTP_API_KEY/PNOTP_BASE_URLandexcept PnotpErrorkeep working, andpnotp0.5.0 on PyPI is an alias of this package.
Install
pip install offrec
Requires Python 3.9 or later. The only hard dependency is httpx.
Authenticate
Mint an account key at console.offrec.ai/api-keys
(it starts with offrec_sk_ and is shown once) and add credits on the console's
Billing page. A new account starts with none; a call on an empty balance raises
offrec.InsufficientCreditsError (402).
export OFFREC_API_KEY="offrec_sk_..."
import offrec
px = offrec.Client() # reads OFFREC_API_KEY
# or: px = offrec.Client(api_key="offrec_sk_...")
Your first call
out = px.chat("Summarize this clause in one sentence: ...", model="gemini-3.1-flash-lite")
out["completion"] # the answer
out["usage"] # {"input_tokens": ..., "output_tokens": ...}
out["receipt"]["zero_retention"] # True
out["receipt"]["served_by"] # who ran the call, and who governs retention there
Change model to use any model on the list. A few starting points:
model |
Use it for | $ in / out per 1M tokens |
|---|---|---|
gemini-3.1-flash-lite |
The default: fast, reads images, tool calling | $0.25 / $1.50 |
claude-haiku-5-5 |
The lowest price on the list; 1M context ($0.50 / $2.50 over 100K tokens of prompt) | $0.10 / $0.50 |
gemma-4-26b |
Fast and multimodal at a low price | $0.15 / $0.60 |
deepseek-v4.1-flash |
Agents and 1M-token context at a low price | $0.30 / $1.20 |
claude-sonnet-5-5 |
Coding, agents and knowledge work, 1M context | $2.00 / $10.00 |
claude-opus-5-5 |
Frontier coding and long-horizon agent work | $4.00 / $20.00 |
The live list, with every price and context window, is public:
GET https://api.offrec.ai/v1/llm/pricing. Gemini and Gemma run on Google Cloud
Vertex AI, Claude on Amazon Bedrock, and DeepSeek, Kimi, Qwen and GLM on
Fireworks AI.
Tools, images and documents
out = px.chat("Book a 3pm slot with Dr. Reyes", tools=[{"type": "function", "function": {...}}])
out.get("tool_calls") # [{"id", "name", "arguments": {...}}]
px.chat("What is in this diagram?", image="diagram.png") # on a model with vision
px.chat("Summarize this contract", file="contract.pdf") # parsed to text
A model sees images natively when its GET /v1/llm/pricing entry says
vision: true. Audio is not enabled today (a clean 422).
The receipt
Every call returns a receipt next to the answer: the retention basis, the model, the tokens, the cost and who served it. It carries no prompt and no answer. offrec keeps no copy of the call, so store the receipts you need.
The receipt is signed with Ed25519. The public key is at
GET https://api.offrec.ai/v1/llm/attestation, and the verification recipe is on
docs.offrec.ai/receipts.
Already on the OpenAI SDK?
The API speaks OpenAI's Chat Completions. Change two lines and keep your code:
from openai import OpenAI
client = OpenAI(base_url="https://api.offrec.ai/v1", api_key="offrec_sk_...")
resp = client.chat.completions.create(model="claude-sonnet-5-5", messages=[...], tools=[...])
resp.receipt # the signed receipt
Streaming, tool_choice, the Vercel AI SDK and LangChain work the same way. See
docs.offrec.ai/migrating.
Laya: typed decisions
Laya answers typed questions (choice, score, yes/no) about a ticket, an email or a conversation with calibrated probabilities, and writes no text.
result = px.decide(
"Checkout has been down for an hour and we're losing sales. Fix it or we cancel.",
{
"queue": {"type": "choice", "instructions": "Which team owns this ticket?",
"criteria": {"infrastructure": "outages, downtime, errors",
"billing": "refunds, invoices, charges"}},
"churn_risk": {"type": "noul", "instructions": "Does the customer threaten to cancel?"},
},
)
result["answers"]["queue"]["choice"] # "infrastructure"
Guide: docs.offrec.ai/laya.
Hosted agents
A hosted agent runs a tool-calling loop with a zero-retention model as its brain. A data-compliant run keeps no question, no intermediate reasoning and no loop state: only metered metadata and the encrypted final answer, held for 24 hours.
a = px.agents.create("Advisor")
px.agents.publish(a["id"], model="gemini-3.1-flash-lite", tools=["programs_db"])
run = px.agents.run(a["id"], "I love AI and maths. Which programs fit?", wait=True)
print(run.final_answer())
SDK agents (bring your own brain)
offrec.Agent is a bounded, observable tool-calling loop that runs in your
process. The brain can be your own provider key (any OpenAI-style
/chat/completions with tool calling) or a text model you deployed on offrec. With
your own key, tokens go to that provider, not through offrec's zero-retention layer.
import offrec
agent = offrec.Agent("gemini:gemini-2.5-pro", # reads GEMINI_API_KEY
system="You are a careful research assistant.")
@agent.tool
def search_pubmed(query: str, max_results: int = 5) -> list[dict]:
"Search PubMed and return the top hits." # docstring -> tool description
return my_client.search(query, k=max_results) # JSON Schema is derived from the signature
run = agent.run("Recent trials on pediatric pneumonia from chest X-rays?")
print(run.output, run.stop_reason) # final answer + "stop" | "max_steps"
for event in agent.stream("..."): # live UI events
... # TextDelta / ToolCallStarted / ToolCallFinished / StepBoundary / Done
The loop is bounded: max_steps, per-tool tool_timeout, max_tool_retries (a
schema-invalid or timed-out call is re-asked without spending the step budget),
and on_tool_error ("return" feeds the error back to the brain, or "raise").
Pass response_format=MyDataclass for a structured final answer.
Neural networks (an extra)
Training and deploying your own models is a side feature that still works end to end:
project = px.projects.create(name="Chest X-ray", task_type="binary_image_classification")
model = px.models.create(
project["id"], name="cnn-v1",
architecture=offrec.architectures.image_classifier(num_classes=2),
)
# dataset = a .zip of class-named folders (image) or a .csv (tabular)
job = px.train(model.id, dataset="train.zip", epochs=5)
job.wait(on_progress=lambda j: print(j.status)) # job.pause() / resume() / cancel()
dep = px.deployments.upload(project["id"], name="prod", checkpoint="model.pt", compliant=True)
print(px.predict(dep["deployment"]["id"], "xray.jpg"))
CPU training is free; GPU training bills $1.00 per hour on an NVIDIA L4, per second
of compute. compliant=True makes an endpoint data-compliant: the input is deleted
before the response returns, error text stays out of the stats log, and each
response carries a compliance block. External consumers can call an endpoint with
a per-deployment pnp_... key (px.deployments.create_key(...)). Guide:
docs.offrec.ai/neural-networks.
Help and Toby
The SDK documents itself, offline, with no key and no network:
offrec.help() # overview + the list of topics
offrec.help("quickstart") # a topic page
offrec.help.search("stream a chat") # search topics + the live API reference
offrec.help(offrec.Client.chat) # any symbol's real signature + docstring
offrec.help.errors # the exception catalog
Toby, the hosted assistant, answers in plain English (needs your key and a positive balance; it falls back to local doc search otherwise, so it never raises):
print(offrec.help.toby("Which model should I use for long contracts?"))
Surface
| Area | Calls |
|---|---|
| LLM | chat(text_or_messages, model=, tools=, image=, file=, ...) returns {completion, usage, model, receipt} |
| Laya | decide(state, questions, model="laya", min_confidence=) |
| Hosted agents | agents.create/get/list/publish(model="gemini-3.1-flash-lite", tools=, ...)/run(..., wait=) · run.final_answer/steps/progress |
| SDK agents | Agent(model, tools=, system=, ...) · agent.run/stream · @agent.tool / @offrec.tool · offrec.endpoints.* · offrec.model |
| Credits | credits.balance() |
| API keys | api_keys.create/list/revoke |
| Projects and models | projects.list/create/get · models.create/list/get/add_version/versions |
| Training | train(...) returns a TrainingJob · job.wait/progress/pause/resume/cancel · jobs.list/get |
| Deployments | deployments.upload/list/get/predict/stats/pause/resume/delete · create_key/list_keys/revoke_key |
| Help | help() / help("topic") / help(symbol) · help.search/topics/errors · help.toby(...) · help.as_tool() |
Every error subclasses offrec.OffrecError (also importable as PnotpError).
API errors raise offrec.APIError subclasses: AuthError (401/403), NotFoundError
(404), InsufficientCreditsError (402). Agent-loop failures raise
offrec.AgentError subclasses: MaxStepsExceeded, ToolError, ModelEndpointError.
The legacy pnotp_api package (a standalone PathoVision wrapper, unrelated to the
client above) no longer ships with this package. It stays in pnotp 0.5.0:
pip install "pnotp[server]".
Metadata
Release files for offrec 0.5.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 |
|---|---|---|---|---|
| offrec-0.5.0.tar.gz | 107.6 kB | With provenance | Details for offrec-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| offrec-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for offrec-0.5.0-py3-none-any.whl |
Total release size: 184.3 kB
Release files / offrec-0.5.0.tar.gz
| Download URL | offrec-0.5.0.tar.gz |
|---|---|
| Size | 107.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9a3eede4212c6a78ae45e77545f0ab39bdc1f8bedb44f2e4badec3248fd53025
|
|
BLAKE2b-256 checksum How to use checksums |
c1008eb28a47e4ff412b0dd81fb8aa0053d289c23e273e9bf438d3042f9e17f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 9, 2026.
Transparency logRelease files / offrec-0.5.0-py3-none-any.whl
| Download URL | offrec-0.5.0-py3-none-any.whl |
|---|---|
| Size | 76.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
993a7986c0d8389cddd9909a4fde7755b23bf556cba668890dd717979954e48d
|
|
BLAKE2b-256 checksum How to use checksums |
9b9728a82720fc22e80498d5b960128dcea12a9704520ce33f2d6214a1e08125
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 9, 2026.
Transparency logSecurity for offrec 0.5.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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for offrec-0.5.0.tar.gz
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 Nov 9, 2026.
Transparency logA 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.