# REP for AI Agents â Integration Playbook
> A single-page, machine-readable playbook for AI coding agents integrating REP. Decision tree, install matrix, copy-paste recipes for SDK, Docker, Kubernetes, and every framework, plus the mistakes that break a REP setup.
Source: https://rep-protocol.dev/agents/
This page is written for AI coding agents. It is dense on purpose: everything needed to integrate REP into an application correctly, without reading the rest of the site.
Every page on this site is also available as plain-text Markdown by appending `.md` to its path â for example [`/reference/sdk.md`](/reference/sdk.md). A machine-readable index of all pages lives at [`/llms.txt`](/llms.txt), and the entire documentation set concatenated into one file at [`/llms-full.txt`](/llms-full.txt).
## What REP is
REP (Runtime Environment Protocol) moves frontend environment variables from **build time** to **container runtime**. Instead of `import.meta.env.VITE_API_URL` being replaced with a literal string during `npm run build`, a small Go gateway reads `REP_*` environment variables when the container starts and injects them into every HTML response as an inert `
);
}
```
```ts
// app/api/rep/session-key/route.ts
export { GET } from '@rep-protocol/next/session-key';
```
**Anything else** â run the real gateway in front of the dev server:
```bash
npx @rep-protocol/cli dev --proxy http://localhost:5173 --hot-reload
```
All three read `.env.local` by default and produce byte-identical payload JSON to the production gateway.
## Client API
```typescript
import { rep } from '@rep-protocol/sdk';
// or: import { get, getSecure, onChange, verify, meta } from '@rep-protocol/sdk';
rep.get('API_URL'); // string | undefined â synchronous
rep.get('API_URL', 'http://localhost'); // string â with default
await rep.getSecure('ANALYTICS_KEY'); // Promise â throws REPError
rep.getAll(); // Readonly> â PUBLIC only
rep.verify(); // boolean â SRI hash matches the payload
rep.meta(); // REPMeta | null â version, injectedAt, counts
rep.onChange('FLAGS', (next, prev) => {}); // () => void unsubscribe
rep.onAnyChange((key, next, prev) => {}); // () => void unsubscribe
```
Framework wrappers:
```tsx
const apiUrl = useRep('API_URL', 'http://localhost:3000'); // React, Vue
const { value, loading, error } = useRepSecure('ANALYTICS_KEY'); // React
const apiUrl = repStore('API_URL', 'http://localhost:3000'); // Svelte â $apiUrl
```
Behavioural facts that change how code should be written against this API:
- **`get()` is synchronous and must stay that way.** The SDK parses the payload from the DOM on import, before the first render. Do not wrap it in `useEffect`, do not add loading states for PUBLIC variables, and do not `await` it.
- **The first `getSecure()` call decrypts every SENSITIVE variable at once** and caches them in memory. Subsequent calls for other keys make no network request. Fetching them individually in parallel is wasted work.
- **`onChange()` connects the SSE stream lazily.** No listener means no connection. Hot reload requires the gateway to be run with `--hot-reload`.
- **`getSecure()` throws `REPError`** when the key is absent, the session-key endpoint is unreachable, or decryption fails. Handle it; do not let it reject unhandled at module scope.
## Gateway reference
| Flag | Env var | Default | Notes |
|---|---|---|---|
| `--mode` | `REP_GATEWAY_MODE` | `proxy` | `proxy` or `embedded` |
| `--upstream` | `REP_GATEWAY_UPSTREAM` | `localhost:80` | proxy mode only |
| `--static-dir` | `REP_GATEWAY_STATIC_DIR` | `/usr/share/nginx/html` | embedded mode only |
| `--port` | `REP_GATEWAY_PORT` | `8080` | |
| `--strict` | `REP_GATEWAY_STRICT` | `false` | Guardrail warnings become startup failures. Use in production. |
| `--manifest` | `REP_GATEWAY_MANIFEST` | â | Path to `.rep.yaml` |
| `--env-file` | `REP_GATEWAY_ENV_FILE` | â | Env vars still take precedence |
| `--hot-reload` | `REP_GATEWAY_HOT_RELOAD` | `false` | Enables `/rep/changes` |
| `--hot-reload-mode` | `REP_GATEWAY_HOT_RELOAD_MODE` | `signal` | `signal`, `file_watch`, `poll` |
| `--watch-path` | `REP_GATEWAY_WATCH_PATH` | â | Required for `file_watch` |
| `--allowed-origins` | `REP_GATEWAY_ALLOWED_ORIGINS` | â | CORS allowlist for `/rep/session-key`. Set it in production. |
| `--log-format` | `REP_GATEWAY_LOG_FORMAT` | `json` | `json` or `text` |
| `--health-port` | `REP_GATEWAY_HEALTH_PORT` | â | Serve `/rep/health` on a separate port |
Precedence: CLI flags â `REP_GATEWAY_*` env vars â manifest settings.
| Endpoint | Purpose |
|---|---|
| `GET /rep/health` | Variable counts, guardrail status, uptime. Use for readiness probes. |
| `GET /rep/session-key` | Single-use AES key, 30s TTL, rate-limited, origin-checked |
| `GET /rep/changes` | SSE stream of config changes (requires `--hot-reload`) |
## Optional: the manifest
`.rep.yaml` is not required â the gateway runs on naming convention alone. Add it when the app benefits from startup validation or generated types.
```yaml
version: "0.1.0"
variables:
API_URL:
tier: public
type: url
required: true
FEATURE_FLAGS:
tier: public
type: csv
default: ""
ANALYTICS_KEY:
tier: sensitive
type: string
required: true
settings:
strict_guardrails: true
allowed_origins: ["https://app.example.com"]
```
An optional variable that declares `default:` is [injected by the gateway](/guides/manifest.md#defaults) when it is unset, so `rep.get('FEATURE_FLAGS')` above returns `""` rather than `undefined`.
```bash
rep validate # fail fast on a malformed or incomplete manifest
rep typegen -o src/rep.d.ts # typed get() / getSecure() overloads
```
## Mistakes to avoid
These are the failure modes worth checking for in generated or reviewed code.
1. **Putting a real secret in `REP_PUBLIC_*`.** It ships as plaintext in page source. Guardrails catch the well-known formats; they cannot catch everything. Reach for `REP_SERVER_*`.
2. **Treating `REP_SENSITIVE_*` as a secret store.** Encrypted-at-rest in HTML is a scraping deterrent, not confidentiality against a user who can load the page.
3. **Making `get()` async.** It is synchronous by contract. Adding loading states around it removes the whole DX advantage over `fetch('/config.json')`.
4. **Changing `type="application/json"` on the injected script tag.** That attribute is why the browser treats the payload as inert data. Any other type makes it executable.
5. **Colliding names across tiers.** `REP_PUBLIC_API_URL` + `REP_SENSITIVE_API_URL` both strip to `API_URL`. The gateway exits at startup.
6. **Shipping a build-tool plugin as the production mechanism.** `@rep-protocol/vite` and `@rep-protocol/next` are dev-only and deliberately unhardened â no rate limiting, no single-use session keys. Production always means the gateway.
7. **Exposing the upstream port.** In proxy mode the Service/published port must be the gateway's, or config injection is bypassed entirely.
8. **Omitting `--allowed-origins` in production.** Without it the session-key endpoint has no origin allowlist.
9. **Expecting session keys to survive a restart.** Keys are ephemeral and regenerated on every gateway start â by design.
10. **Running the gateway behind a Brotli-compressing upstream.** Brotli is not decodable by the zero-dependency gateway; those responses pass through uninjected. Prefer gzip or identity.
## Verification checklist
After an integration, confirm all five:
```bash
# 1. Gateway sees the variables and guardrails pass
curl -s localhost:8080/rep/health | jq
# 2. The payload is actually in the HTML
curl -s localhost:8080/ | grep -o '