pleach
Integrate

Migrating from OpenAI Enterprise

Keep your OpenAI Enterprise contract. Pleach sits underneath — AuditableCall row in your Postgres, per-axis rollup inside a Project, replay-deterministic eval.

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

┌─────────────────────────────────────────────────────────────┐
│  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

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:

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

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

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

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_hashrow_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

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

Before PleachWith Pleach
Project-level usage in OpenAI dashboardPer-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 onlyReplay any recorded turn byte-identically against its fingerprint
Multi-snapshot deployments rely on the SDK not falling backFamily-locked at session start — provider error surfaces as an explicit halt, not silent snapshot switch

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 has the longer treatment and the per-shape recommendation.

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 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

ConcernOpenAI Enterprise's slotPleach's slot
Sending tokens to the modelyesnone — delegates to provider
ZDR on prompts/completionsvendor contractnone — Pleach governs YOUR DB only
SSO / SAML / SCIMyesnone
Per-project API keys, budgets, rate limitsyesnone — Pleach reads the same key
Project-level usage reportingUsage APInone
Per-end-customer cost rollup inside a Projectnonetenant_id on every AuditableCall row
Tamper-evident downstream audit row in YOUR DBnoneprev_hash + row_hash hash chain
Family + transport lock across snapshotsnoneruntime.sessions.create() freezes at session start
Replay-deterministic LLM streamnone — same prompt may return different streamFingerprint replays byte-identical StreamEvents
Subagent cost rollup to parent turnIdnoneSpawnTreeState
Prompt caching (server-side prefix reuse)yes (OpenAI's automatic caching)preserved; Pleach doesn't interfere
Structured outputs / function callingyespreserved through AiSdkProvider
Fine-tuning, evals, vector stores, batch APIyespreserved
Admin API (org events, key management)yesnone
Scale tier / dedicated capacityyesnone
Operational dashboard (usage, errors, rate limits)OpenAI dashboardnone — Pleach doesn't replace it

When you don't 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 covers the trade.

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.
  • 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.

Where to go next

On this page