# Migrating from OpenAI Enterprise (/docs/migrating-from-openai-enterprise)



If you're on an OpenAI Enterprise contract, the question isn't
whether to replace it. SSO/SAML, ZDR, dedicated capacity, Projects
with per-project API keys, the Admin API, evals, fine-tuning,
prompt caching, structured outputs — those are the vendor's slot
and they keep doing what they're good at. The question is what
sits **underneath** the OpenAI call inside your own infrastructure,
and that's where most enterprise customers hit one of three walls
after the first 1-3 features ship.

The walls are: (1) finance can't allocate cost to a meaningful axis
**within a Project** because the Usage API reports per-project,
period — your end customers in a SaaS product, or your employees,
teams, and cost centers when the Project is an internal deployment,
all get flattened into one Project total; (2) compliance can't show
a tamper-evident audit row in **your own database** because the only
durable audit trail lives at OpenAI; (3) eval/regression can't
replay byte-identically because OpenAI doesn't guarantee determinism
across model snapshots. Pleach is an npm dependency and a Postgres
table. It runs inside your existing infrastructure, doesn't add a
vendor, and closes each of those three walls without touching what
your OpenAI contract already covers.

A note on shape: this page reads naturally for the multi-tenant SaaS
case (Acme Corp is your customer). The same row covers internal-use
deployments — set `tenantId` to the employee, team, or cost-center
identifier you want to chargeback or audit against. The Usage API
still rolls up to the Project; Pleach rolls up to whichever axis
your finance and audit teams actually report on.

## The architecture [#the-architecture]

```
┌─────────────────────────────────────────────────────────────┐
│  OpenAI Enterprise (your existing contract)                 │
│  - SSO / SAML, SCIM                                         │
│  - Zero Data Retention                                      │
│  - Projects + per-project API keys                          │
│  - Admin API, Usage API                                     │
│  - Scale tier / dedicated capacity                          │
│  - Evals, fine-tuning, prompt caching                       │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  Your API route / serverless function                 │  │
│  │                                                       │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  Pleach SessionRuntime (npm dep + your DB)      │  │  │
│  │  │  - AuditableCall row → your Postgres            │  │  │
│  │  │  - tenantId stamped per end customer            │  │  │
│  │  │  - Family-lock at session start                 │  │  │
│  │  │  - Replay fingerprint per turn                  │  │  │
│  │  │  - Subagent rollup to parent turnId             │  │  │
│  │  │                                                 │  │  │
│  │  │  ┌───────────────────────────────────────────┐  │  │  │
│  │  │  │  AiSdkProvider({                          │  │  │  │
│  │  │  │    model: openai("gpt-4o", {              │  │  │  │
│  │  │  │      apiKey: PROJECT_API_KEY              │  │  │  │
│  │  │  │    })                                     │  │  │  │
│  │  │  │  })                                       │  │  │  │
│  │  │  └───────────────────────────────────────────┘  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
```

OpenAI's Usage API and run history are the **vendor** trail —
what the project spent, which key fired, what the model returned.
Pleach's `AuditableCall` ledger is the **downstream** trail — which
of your end customers caused the call, what tools the agent
invoked, which subagents spawned, attributed to the turn the user
typed, in a row keyed `(sessionId, turnId, stageId,
seqWithinTurn)` that your finance and compliance teams can query
directly in your own DB.

The two ladders compose. OpenAI tells you what the Project spent;
Pleach tells you which of *your* customers within that Project
spent it. Neither side is doing the other's job badly.

## The three walls [#the-three-walls]

### 1. Per-end-customer cost attribution within a Project [#1-per-end-customer-cost-attribution-within-a-project]

**What OpenAI Enterprise gives you.** Projects partition usage and
keys. The Usage API and Admin API report token spend per project,
per API key, per model, per minute. Per-project budgets and rate
limits enforce ceilings.

**The gap.** Inside a single Project, the Usage API has no notion
of *your* end customer. If your SaaS app serves a thousand
tenants out of one Project (the common shape — Projects are
expensive to provision and operationally scoped to your team, not
your customers), the OpenAI dashboard tells you the Project spent
$8,400 last month. It cannot tell you that Acme Corp was $3,200
of it.

**What Pleach closes.** `runtime.tenant` stamps `tenantId` on
every `AuditableCall` row at write time, before the stream
finishes. The row also carries `turnId`, `toolName`,
`subagentDepth`, and `tokenUsage`. The per-end-customer rollup
falls out of a single SQL query against your Postgres:

```sql
SELECT
  tenant_id,
  SUM((token_usage->>'totalTokens')::bigint)            AS tokens,
  SUM((token_usage->>'inputTokens')::bigint)            AS input,
  SUM((token_usage->>'outputTokens')::bigint)           AS output,
  COUNT(DISTINCT turn_id)                               AS turns
FROM harness_auditable_calls
WHERE created_at >= date_trunc('month', now())
  AND model_id LIKE 'gpt-4o%'
GROUP BY tenant_id
ORDER BY tokens DESC;
```

Reconcile that against the Project's Usage API total at month
close — the sums match because Pleach is recording the same calls
your OpenAI key is making, just with the tenant attribution
attached.

### 2. Tamper-evident audit row in your own DB [#2-tamper-evident-audit-row-in-your-own-db]

**What OpenAI Enterprise gives you.** ZDR ensures OpenAI does not
retain prompt or completion data for training. The Admin API
exposes audit logs of organizational events (key creation, role
changes, project membership). Your security questionnaire likely
covers SOC 2, ISO 27001, and the vendor's penetration testing.

**The gap.** ZDR is about what OpenAI doesn't keep. It doesn't
give you a row in your own database that says "on 2026-06-07 at
14:23:11, session `sess_abc` turn `turn_42` invoked tool
`compound_lookup` for tenant `acme-corp`, model `gpt-4o`, spent
2,193 tokens, fingerprint `f3a9...`." That row is what a
downstream auditor — your customer's compliance team, an SEC
inquiry, a FedRAMP review six months from now — asks for. The
vendor contract doesn't produce it because the vendor doesn't
know about the decisions *your* product made.

**What Pleach closes.** Every LLM call writes an `AuditableCall`
row before the stream completes. The row is hash-chained:
`prev_hash` references the previous row's `row_hash` keyed by
session, so any backfill or mutation breaks the chain
detectably. The schema lives in your Postgres, under your
backup and retention policy, joinable against your customer and
billing tables.

### 3. Replay determinism for evals and regression [#3-replay-determinism-for-evals-and-regression]

**What OpenAI Enterprise gives you.** The Evals product runs a
graded test set against a model snapshot and reports pass rate.
Fine-tuning produces a frozen snapshot you can pin a Project to.
Dedicated capacity reduces latency variance.

**The gap.** OpenAI does not guarantee that the same prompt
against the same snapshot returns the same stream. Sampling
parameters, model serving variance, prompt-cache hit/miss state,
and silent rolling updates within a snapshot family can all
shift output. When your eval suite regresses, you can't replay
the offending production turn byte-identically to bisect what
changed.

**What Pleach closes.** The `Fingerprint` primitive records
`(messages, tools, model, params, family, transport)` per turn.
Replay against the same fingerprint reproduces the
`StreamEvent` sequence the production turn emitted, against the
same family-locked transport. Drift between two snapshots
surfaces as a fingerprint-keyed delta in the replay output —
you catch the regression in your CI eval suite before the
customer does.

## The code shape [#the-code-shape]

```typescript
import {
  createPleachRuntime,
  AiSdkProvider,
  type StreamEvent,
} from "@pleach/core";
import { SupabaseAdapter } from "@pleach/core/sessions";
import { openai, createOpenAI } from "@ai-sdk/openai";

// Per-project API keys keep working. You read the same env you
// already read; OpenAI's Usage API still rolls up against this
// key inside the Project.
const openaiClient = createOpenAI({
  apiKey: process.env.OPENAI_PROJECT_API_KEY,
});

// createPleachRuntime is the zero-config bootstrap. The tenantId
// here is YOUR end customer, not OpenAI's Project — that's how
// the two attribution ladders compose.
const runtime = createPleachRuntime({
  tenantId: endCustomerId,         // e.g. "acme-corp"
  userId:   endUserId,
  storage:  new SupabaseAdapter({ client: supabase }),
  provider: new AiSdkProvider({
    model: openaiClient("gpt-4o"),
  }),
});

// Family-lock fires here. The session pins:
//   - tokenizer (OpenAI cl100k)
//   - prompt-cache key (so OpenAI's automatic prefix caching
//     stays stable per session — Pleach doesn't interfere)
//   - tool-call dialect (OpenAI JSON Schema)
//   - refusal pattern (OpenAI's shape)
// If you run multiple snapshots (gpt-4o, gpt-4o-mini, o1) the
// session-start lock prevents a transient provider error from
// silently falling back to a different snapshot mid-conversation.
const session = await runtime.sessions.create({
  provider: { type: "openai" },
  model:    { id: "gpt-4o" },
});

// executeMessage writes the AuditableCall row at call time, with
// tenantId = endCustomerId already stamped by runtime.tenant.
const events: StreamEvent[] = [];
for await (const evt of runtime.executeMessage(session.id, prompt)) {
  events.push(evt);
}
```

Three things this pattern gives you that the OpenAI Enterprise
contract alone doesn't:

1. **End-customer cost rollup falls out of one SQL query** against
   `harness_auditable_calls` keyed by `tenant_id`. Reconcile it
   against the Project's Usage API total at month close — the
   sums match because Pleach records the same calls your
   per-project key is making, with tenant attribution attached.
2. **The audit row lives in your DB before the response returns**,
   hash-chained `prev_hash` → `row_hash`, joinable to your
   customer, billing, and compliance tables. A downstream auditor
   asking "show me every LLM call that touched tenant Acme last
   quarter" answers from your Postgres without an OpenAI dashboard
   export.
3. **Replay is byte-identical against the recorded fingerprint.**
   Your CI eval suite replays last week's production turns against
   the current snapshot and surfaces drift before the customer
   does. When OpenAI rolls a silent update inside a snapshot family,
   the fingerprint-keyed delta is your early warning.

## What stays the same [#what-stays-the-same]

Pleach is downstream of the LLM call. None of the following change:

* **SSO/SAML and SCIM provisioning** through your IdP — Pleach
  doesn't touch identity.
* **Zero Data Retention** on prompts and completions — the
  vendor contract governs what OpenAI stores; Pleach governs
  what *you* store.
* **Projects and per-project API keys** — Pleach reads the same
  key and stamps your downstream `tenantId` on the audit row.
* **Scale tier / dedicated capacity** — capacity allocation is
  vendor-side; Pleach doesn't see it.
* **Fine-tuned snapshots** — pin the model in
  `AiSdkProvider({ model: openaiClient("ft:gpt-4o:acme:...") })`
  exactly as before.
* **Automatic prompt caching** — OpenAI's server-side prefix
  cache is keyed by your prompt shape, which Pleach doesn't
  rewrite. Cache hit rates are preserved.
* **Structured outputs** (JSON Schema response format) and
  function/tool calling — these are transport-side features that
  pass through `AiSdkProvider`.
* **Batch API** for offline jobs — Pleach captures per-batch
  audit rows around the batch submission and result fetch.
* **Evals product** — keep running it for graded test sets;
  Pleach's replay determinism is the orthogonal CI-side primitive.
* **Vector stores** and file search — Pleach doesn't replace
  these; they remain the vendor's slot.
* **Admin API** — organizational audit logs and key management
  are unchanged.

## What changes [#what-changes]

| Before Pleach                                               | With Pleach                                                                                              |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Project-level usage in OpenAI dashboard                     | Per-end-customer rollup via `SELECT ... GROUP BY tenant_id` in your Postgres                             |
| Audit trail at OpenAI (per ZDR contract)                    | Hash-chained `AuditableCall` row in your DB, joinable to billing/compliance tables                       |
| Eval runs against current snapshot only                     | Replay any recorded turn byte-identically against its fingerprint                                        |
| Multi-snapshot deployments rely on the SDK not falling back | Family-locked at session start — provider error surfaces as an explicit halt, not silent snapshot switch |

## The Assistants API caveat [#the-assistants-api-caveat]

If you built on the Assistants API, thread state lives at OpenAI.
Pleach can still capture per-call `AuditableCall` rows around each
`runs.create` or `runs.createAndPoll`, with `tenantId` and
`tokenUsage` attributed, and the hash chain still holds. What
doesn't transfer is the conversation primitive — the durable record
of "what did this conversation contain" lives at OpenAI, not in
your DB, and replay determinism is weaker because the next-call
inputs depend on what OpenAI stored. For new code, prefer Chat
Completions or the Responses API with Pleach owning the session.
The [Pleach + OpenAI SDK page](/docs/with-openai-sdk) has the
longer treatment and the per-shape recommendation.

## No new vendor [#no-new-vendor]

Pleach is `npm install @pleach/core` and a table in the Postgres
you already run. There is no Pleach-hosted service, no new ZDR
contract to negotiate, no new security questionnaire, no new
procurement cycle, no new SOC 2 boundary. The npm package ships
under FSL-1.1-Apache-2.0 — source-available, usable in production,
auto-transitions to permissive Apache 2.0 two years after first
stable publish. See [Versioning](/docs/versioning) for the
license-posture detail your legal team will want. The
database table lives under your existing backup, retention, and
encryption-at-rest policies. The only new surface is a row schema
your DBA already knows how to operate.

This is the property most enterprise customers care about most.
The OpenAI contract is the expensive one to get through
procurement; adding a second vendor underneath it would
neutralize the point. Pleach is structured so that doesn't happen.

## Where each is load-bearing [#where-each-is-load-bearing]

| Concern                                            | OpenAI Enterprise's slot                       | Pleach's slot                                        |
| -------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------- |
| Sending tokens to the model                        | yes                                            | none — delegates to provider                         |
| ZDR on prompts/completions                         | vendor contract                                | none — Pleach governs YOUR DB only                   |
| SSO / SAML / SCIM                                  | yes                                            | none                                                 |
| Per-project API keys, budgets, rate limits         | yes                                            | none — Pleach reads the same key                     |
| Project-level usage reporting                      | Usage API                                      | none                                                 |
| Per-end-customer cost rollup inside a Project      | none                                           | `tenant_id` on every `AuditableCall` row             |
| Tamper-evident downstream audit row in YOUR DB     | none                                           | `prev_hash` + `row_hash` hash chain                  |
| Family + transport lock across snapshots           | none                                           | `runtime.sessions.create()` freezes at session start |
| Replay-deterministic LLM stream                    | none — same prompt may return different stream | `Fingerprint` replays byte-identical `StreamEvent`s  |
| Subagent cost rollup to parent `turnId`            | none                                           | `SpawnTreeState`                                     |
| Prompt caching (server-side prefix reuse)          | yes (OpenAI's automatic caching)               | preserved; Pleach doesn't interfere                  |
| Structured outputs / function calling              | yes                                            | preserved through `AiSdkProvider`                    |
| Fine-tuning, evals, vector stores, batch API       | yes                                            | preserved                                            |
| Admin API (org events, key management)             | yes                                            | none                                                 |
| Scale tier / dedicated capacity                    | yes                                            | none                                                 |
| Operational dashboard (usage, errors, rate limits) | OpenAI dashboard                               | none — Pleach doesn't replace it                     |

## When you don't need Pleach [#when-you-dont-need-pleach]

* A single-tenant enterprise app where finance doesn't need
  per-end-customer rollup, there's no downstream audit obligation
  beyond ZDR, and eval is "we eyeball the latest snapshot." The
  OpenAI Enterprise contract is sufficient; adding Pleach
  underneath buys you optionality you may not exercise.
* An internal-only tool where the OpenAI request id in your logs
  is enough audit trail. Pleach's overhead doesn't pay back until
  a finance, compliance, or eval question forces a query you
  can't answer from the vendor dashboard.
* A team that's already built a per-customer cost rollup,
  hash-chained audit row, and replay harness in-house and is
  happy operating them. The Pleach value is that you don't have
  to; if you already do, the [comparison page](/docs/comparison)
  covers the trade.

## Sibling SKUs that ride alongside the contract [#sibling-skus-that-ride-alongside-the-contract]

Two SKUs reach the same Enterprise-contract reader through
adjacent doors. Neither is required by the migration above; both
get raised by the same buying conversation.

* **`@pleach/coding-agent`** — the sandboxed coding-agent surface
  for the internal dev-tools team that often sits inside the same
  Enterprise contract footprint. The typed `CodingAgentRuntime`
  contract ships today at `0.2.0-alpha.0` on the `/runtime`
  subpath; method bodies and sandbox-provider wiring land in the
  next phase. The audit row is the same row, the `tenantId` axis
  is the same axis — an internal coding-agent's per-employee or
  per-team spend rolls up under the same `GROUP BY` as the rest
  of your Project traffic. See [Coding agent](/docs/coding-agent).
* **Language-agnostic contract.** The runtime substrate is wire
  shapes (HTTP+SSE, `StreamEvent`, `AuditableCall`, checkpoint
  envelope, version vector), not a TypeScript surface. A Go
  implementation round-trips a shared corpus of recorded turns
  against the contract today; the official `@pleach` Go runtime
  SKU is the next planned published implementation. This is the
  procurement-visible answer when Enterprise IT asks "is this
  TypeScript-only?" — the same contract supports a Java or
  Python runtime built against the same wire shapes. See
  [Language-agnostic contract](/docs/language-agnostic-contract).

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Pleach + OpenAI SDK" href="/docs/with-openai-sdk" description="The developer-direct case (when you're calling the OpenAI SDK yourself), and the longer Assistants API caveat." />

  <Card title="The AuditableCall row" href="/docs/auditable-call-row" description="What lands in your Postgres on every LLM call — the schema, the hash chain, and what you can join it against." />

  <Card title="The tenant facet" href="/docs/tenant-facet" description="How runtime.tenant stamps tenantId on every row, span, and outbound HTTP call — and the CI gate that enforces it." />

  <Card title="Fingerprint" href="/docs/fingerprint" description="The replay-determinism primitive — what's in the cache key, and how snapshot drift surfaces as a fingerprint delta." />
</Cards>
