# Block secrets in an Encore service

URL: https://tailrace.dev/docs/guides/block-secrets-in-encore

> Add Tailrace middleware to raw OpenAI-compatible Encore endpoints so chat bodies and SSE streams respect policy.



Use `@tailrace/encore` to enforce data policy on OpenAI-compatible proxy endpoints in Encore.ts -
in-process, with no proxy.

## Prerequisites [#prerequisites]

* [Quickstart](/docs/get-started/quickstart) or equivalent [`createTailrace`](/docs/get-started/quickstart) setup
* `encore.dev` `>=1.46`

## Step 1: Install [#step-1-install]

```bash
pnpm add @tailrace/core @tailrace/encore encore.dev
```

## Step 2: Register service middleware [#step-2-register-service-middleware]

```ts
import { createTailrace } from "@tailrace/core";
import { tailraceEncore } from "@tailrace/encore";
import { Service } from "encore.dev/service";

export default new Service("api", {
  middlewares: [
    tailraceEncore(createTailrace(), {
      agent: "api",
      workflowId: "default",
    }),
  ],
});
```

## Step 3: Expose the chat proxy as `api.raw` [#step-3-expose-the-chat-proxy-as-apiraw]

Define the OpenAI-compatible proxy with `api.raw` so request/response bodies and SSE are available
via `req.rawRequest` / `req.rawResponse`. Typed Encore APIs that return structured chat payloads
still get a check when the body shape matches openai-compat, but raw endpoints are the supported
path for streaming proxies.

## How it works [#how-it-works]

| Location                | Tailrace boundary                                   | Direction            |
| ----------------------- | --------------------------------------------------- | -------------------- |
| Chat request messages   | `{ kind: "model", provider }` (`model` field as-is) | Outbound to upstream |
| JSON chat completion    | same model boundary                                 | Inbound              |
| SSE `text/event-stream` | same; carry-buffer across chunks                    | Inbound              |

When policy resolves to `block`, the middleware returns **422** with
`{ error: { type: "policy_violation", entity, rule } }` - never the raw value. That maps from
[`PolicyViolationError`](/docs/reference/errors/POLICY_VIOLATION).

For SSE, Tailrace cancels the upstream stream, emits one error `data:` event, and closes
(abort-only in v0.1).

Only `mode: "openai-compatible"` is supported. Shared helpers live in [`@tailrace/http`](/docs/reference/http).

## Common variations [#common-variations]

### Derive agent from the request [#derive-agent-from-the-request]

```ts
tailraceEncore(tailrace, {
  agent: (req) => String(req.data?.agentId ?? "api"),
  workflowId: (req) => String(req.requestMeta?.path ?? "default"),
});
```

### Verify with a synthetic key [#verify-with-a-synthetic-key]

Send a chat completion body whose messages include `sk_test_…FAKE`. Expect **422**. An
`example.com` email should tokenize under the default policy.

## See also [#see-also]

* [Encore integration](/docs/integrations/encore)
* [Encore reference](/docs/reference/encore)
* [Hono](/docs/integrations/hono) - same openai-compat contract on a different host
* [Boundaries](/docs/concepts/boundaries)
* Spec: [`docs/integrations.md`](https://github.com/TailraceHQ/tailrace/blob/main/docs/integrations.md) §13
