See More

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