Who spent what
Per-request cost attribution down to a single virtual key, in the currency each vendor actually invoices you in.
TORHAUS.AI sits between your applications and the model vendors you buy from, and answers the four questions that turn urgent the moment more than one team shares an API key: who spent what, what was sent, who may call which model, and where it went.
# The only change your developers make.
from openai import OpenAI
client = OpenAI(
base_url="https://gateway.example.com/v1",
api_key="tor_...", # a virtual key, not a vendor key
)
client.chat.completions.create(
model="gateway-balanced", # a routing alias, not a model name
messages=[{"role": "user", "content": "Hello"}],
)
# Charged to a team. Checked against a budget.
# Screened by your guardrails. Written to the audit log.
→ 200 OK x-request-id: 01JD8P4W2Q…
The first team gets access. Then the second. Six months later one key is in eleven repositories, the invoice is a single line item, nobody can say which prompt cost what, and legal wants to know what has been sent to whom. A torii is the gate you pass through deliberately. That is the whole idea.
Per-request cost attribution down to a single virtual key, in the currency each vendor actually invoices you in.
An audit row for every proxied request, with an opt-in copy of the prompt and the response for the teams that need it.
Keys scoped to models, operations, rate limits, budgets and an expiry date. Issued in seconds, revoked in seconds.
Explicit routing with failover, and a record of every target that was tried before one answered.
TORHAUS.AI runs as one binary on infrastructure you control, in a region you chose, talking only to the model endpoints you configured. It has no home to phone. Where your data lives follows from where you install it and which providers you point it at, which is the only kind of sovereignty a piece of software can honestly offer you.
One instance belongs to one company and is administered through a single bootstrap account whose password the console forces you to rotate before anything else opens.
HTTPS_PROXY.GATEWAY_FX_SOURCE=none switches it off and a static rate takes over, which is what an air-gapped install runs.'self' Content-Security-Policy that never permits an inline script.Proportions illustrative. The figures are whatever your own traffic measures, per request.
Every stage of a request is timed separately and written to the audit row in microseconds, including the one that matters: total minus the upstream call, which is everything TORHAUS.AI itself spent. The request detail page draws it as a stacked bar, so an outlier stage is obvious rather than argued about.
Every proxied request writes an audit row: who called, which key and team it belonged to, which alias resolved to which target, tokens, exact cost, every guardrail verdict, and where the microseconds went. Prompt and response bodies are captured only where you switch that on, per team or per virtual key.
X-Forwarded-For is ignored entirely until you say how many reverse proxies really sit in front. Believing that header by default would let any caller write whatever address it liked into your audit log.Illustrative row. Field names and units are the ones the gateway actually writes.
A guardrail is a type plus a JSON configuration, bound additively to global, organization, team or key scope. Rules import and export as YAML or JSON, so a policy set moves between a staging gateway and a production one as a file, not as an afternoon of clicking.
fail_closed and a check that errors, an unreachable webhook say, blocks the request instead of letting it through.The request never reaches the provider. The client gets an OpenAI-shaped error it already knows how to handle.
The match is rewritten before the request leaves, and before anything is written down.
The request passes and the verdict lands on the audit row. This is how you measure a rule before you enforce it.
Guardrails read the prompt text your users write. Whatever your policy needs beyond that, a house pattern or an internal project code, is a regex rule you add yourself.
Prices and billing currency are set per provider endpoint, so a vendor invoicing in EUR and one invoicing in USD are each accounted natively, with nothing converted at write time.
Spend caps on an organization, a team or a single virtual key, per day, week, month or lifetime.
402, not 429. A 429 makes SDK retry logic back off and try again against a condition that will never clear on its own.Organizations, teams under them, and any number of virtual keys per team, each with a cost centre you can hand to accounting.
Lower numbers are tried first. Targets sharing a number split traffic by weight. A struck-through target is one whose circuit breaker is open.
A model alias maps to targets grouped into priority tiers and weighted within a tier. When a provider degrades, its circuit breaker opens, the balancer skips it, and traffic moves to the next tier, and the audit row shows exactly which targets were tried on the way. Recovery is a single probe, not a stampede.
Point any OpenAI SDK at the gateway, give it a virtual key, and ask for an alias instead of a model name. That is the whole integration.
Names are listed to say what TORHAUS.AI connects to. None of these companies endorses or is affiliated with this product, and all trademarks belong to their owners.
Every route starts with the same conversation, because the right answer depends on what your DPO, your Betriebsrat and your platform team each need to be able to say.
We run TORHAUS.AI for you on infrastructure in the European Union, and you get the console on day one instead of a deployment project.
The binary, in your account, in your region, under your own Kubernetes or Compose, with your own Postgres and Valkey.
Your own hardware, your own network, and a gateway that will happily never see the internet.
No, and be careful with anyone who says their software does. The gateway does not record or check the geography of any destination. Residency is a consequence of where you deploy it and which endpoints you configure, not something a binary can assert.
What it does give you is the control that makes residency yours to decide: it runs wherever you put it, it calls nothing on its own initiative, and every upstream it talks to is one you entered by hand. On the EU-hosted plan, we run it in the European Union and tell you exactly which model providers your prompts reach.
It is a Postgres table, not a blockchain. There is no hash chain and no write-once storage, so anyone with database access can change it. Treat it the way you treat your other production data, and control access to it the same way.
What it is: a complete, per-request record of identity, routing, tokens, cost, guardrail verdicts and latency, written asynchronously so it never slows a request. If the writer backs up under load it drops rows rather than blocking the proxy, and it counts every drop so you can alert on it, or you can configure it to block instead and trade latency for completeness.
Provider credentials are sealed with AES-256-GCM and bound to their own database row, so a credential copied elsewhere in the database simply fails to decrypt. Virtual keys are stored only as digests and shown exactly once.
Captured prompts are not separately encrypted by the gateway: they live in your database, under whatever encryption at rest you already run there. Capture is off until you enable it for a named team or key, and redaction runs before anything is written. Transport is yours to terminate: run it behind your reverse proxy, set the production flag, and require SSL on any database that is not on localhost.
No. A fresh gateway enforces nothing until you bind your first rule, and that is deliberate: a redactor nobody chose is a redactor nobody trusts.
Once bound, the PII evaluator finds and rewrites email addresses, phone numbers, card numbers, IBANs, IP addresses, US social security numbers and API keys in the prompt text, with checksum validation so it survives real traffic. It reads the prose your users write; anything specific to your business is a regex rule you add. On a streamed response, text already sent to the client is recorded as a verdict but cannot be recalled.
Not yet. An instance is administered through a single bootstrap account, and every administrator account has the same full access, including the ability to read stored prompts where a team has capture enabled. That is worth knowing before you enable capture broadly.
What you can separate today is the network. The -admin-port flag puts the console on its own socket, after which the data plane serves only /v1 and answers admin paths with a pointed 404. Expose the data plane to your developers and keep the console on an internal network or behind a VPN.
On a laptop, minutes: bring up Postgres and Valkey, initialise the database, start the gateway, and the console prints a one-time bootstrap password it then forces you to rotate. From there it is one provider endpoint with its price list, one model alias, one organization, one team, one key.
For production, add the reverse proxy, real datastore credentials with TLS, and the split admin port. The Compose file in the repository is a development stack and should never be mistaken for a hardened deployment.
It depends on which of the three routes you take and how many teams sit behind it, so we quote rather than publish a table. Book the demo and you will have a number in the same conversation.
Thirty minutes, your questions, and a live gateway rather than slides. Bring the person who will ask what happens to the prompts. That is the part we like doing.
Built and supported from Rosenheim, Germany