> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindsight.io/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK examples

> The official SDK, plus copy‑paste integrations for Node, curl, LangChain, and LlamaIndex.

There are two ways to integrate. The **official SDK** wraps the scan
API and the reverse‑proxy configuration behind a typed client. Or,
because the LLM vendor SDKs already accept `base_url` and
`default_headers`, you can point them at the firewall with **no extra
dependency** at all. The snippets below cover both.

## Official SDK

<CardGroup cols={2}>
  <Card title="TypeScript / Node.js" icon="npm">
    `@blindsight/security`, available now on npm.
  </Card>

  <Card title="Python" icon="python">
    `blindsight-security`, coming soon.
  </Card>
</CardGroup>

### TypeScript / Node.js

```bash theme={null}
npm install @blindsight/security
```

Node.js 18+ (or any runtime with a global `fetch`), zero runtime
dependencies. Scan the prompt before the model, and the response
before the user:

```ts theme={null}
import OpenAI from "openai";
import { RuntimeSecurity } from "@blindsight/security";

const rs = new RuntimeSecurity({
  baseUrl: "https://api.your-blindsight.com",
  apiKey: "ak_live_…",
  appId: "app_…", // optional
});
const openai = new OpenAI();

const verdict = await rs.scanInput(userText, { provider: "openai", model: "gpt-4o" });
if (verdict.blocked) throw new Error(verdict.blockedReason ?? "blocked");
const safePrompt = verdict.safeText; // redacted when the prompt carries PII

const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: safePrompt }],
});
const answer = completion.choices[0].message.content ?? "";

const result = await rs.scanOutput(answer, { prompt: safePrompt });
const final = result.safeText; // empty when blocked
```

The client exposes `scanInput`, `scanOutput`, `scanFile`,
`scanToolCall`, and `scanBatch`. Every result carries `uuid`,
`verdict`, `injection`, `pii`, `redactedText`, `blockedReason`,
`latencyMs`, and `textLength`, plus the boolean flags `allowed` /
`flagged` / `redacted` / `blocked` and the convenience accessors
`safeText`, `injectionScore`, `piiCategories`, and `raw` (the full
JSON response). `assertNotBlocked(result)` throws `ContentBlockedError`
on a block verdict.

Files and agent tool‑calls use the same client:

```ts theme={null}
import { readFile, writeFile } from "node:fs/promises";
import { assertNotBlocked } from "@blindsight/security";

// File scanning
const bytes = await readFile("resume.pdf");
const scan = await rs.scanFile(bytes, { filename: "resume.pdf", mediaType: "application/pdf" });
if (scan.wasRedacted) await writeFile("resume.clean.pdf", scan.fileBytes);

// Tool-call gate
const gate = await rs.scanToolCall("delete_file", { path: requestedPath });
assertNotBlocked(gate); // throws on block
await deleteFile(gate.safeArguments);
```

To route a vendor client through the reverse proxy without writing
explicit scan calls, spread a proxy helper into the client options:

```ts theme={null}
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: OPENAI_KEY, ...rs.proxy.openai() });

import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: ANTHROPIC_KEY, ...rs.proxy.anthropic() });
```

`rs.proxy.gemini()`, `rs.proxy.vertex()`, `rs.proxy.bedrock()`, and
`rs.proxy.openaiCompatible({ provider: "groq" })` cover the rest. Each
returns `{ baseURL, defaultHeaders }`, ready to spread. All exceptions
derive from `BlindsightError`: `BlindsightApiError` (with `.statusCode`
and `.code`, specialised as `BlindsightAuthError` on 401/403 and
`BlindsightQuotaError` on 402/429), `BlindsightConnectionError`, and
`ContentBlockedError`.

### Python

<Note>
  The Python SDK (`blindsight-security`) is **coming soon**. Its
  surface mirrors the TypeScript client with snake‑case names
  (`rs.scan_input(...)`, `verdict.blocked`, `verdict.safe_text`). Until
  it ships, use the dependency‑free `httpx` pattern below, which is
  functionally identical.
</Note>

## Python: explicit scan calls (no SDK)

The portable pattern: scan input, call your model, scan output.

```python theme={null}
import httpx

BLINDSIGHT = httpx.Client(
    base_url="https://api.your-blindsight.com",
    headers={
        "X-API-Key": "ak_live_…",
        "X-Blindsight-App-Id": "app_…",
    },
    timeout=10.0,
)

def safe_chat(user_text: str) -> str:
    # Pre-flight scan
    pre = BLINDSIGHT.post(
        "/api/runtime-security/scan/input",
        json={"text": user_text},
    )
    pre.raise_for_status()
    verdict = pre.json()
    if verdict["verdict"] == "block":
        raise PermissionError(f"Blocked: {verdict['blocked_reason']}")
    safe_text = verdict["redacted_text"] if verdict["verdict"] == "redact" else user_text

    # Your model
    response = call_my_llm(safe_text)

    # Post-flight scan
    post = BLINDSIGHT.post(
        "/api/runtime-security/scan/output",
        json={"prompt": safe_text, "response": response},
    )
    post.raise_for_status()
    out = post.json()
    if out["verdict"] == "block":
        return f"I can't share that. (event: {out['uuid']})"
    return out["redacted_text"] if out["verdict"] == "redact" else response
```

## Node: reverse proxy (no SDK)

The shortest integration possible: swap one URL.

```javascript theme={null}
import OpenAI from "openai";

const openai = new OpenAI({
  baseURL: "https://api.your-blindsight.com/api/runtime-security/proxy/openai/v1",
  apiKey: process.env.OPENAI_API_KEY,
  defaultHeaders: {
    "X-API-Key": process.env.BLINDSIGHT_API_KEY,
    "X-Blindsight-App-Id": process.env.BLINDSIGHT_APP_ID,
  },
});

const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
});
```

## curl: agent tool‑call gate

The minimal one‑liner before a tool dispatch.

```bash theme={null}
verdict=$(curl -sS -X POST https://api.your-blindsight.com/api/runtime-security/scan/tool-call \
  -H "X-API-Key: ak_live_…" \
  -H "X-Blindsight-App-Id: app_…" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "$tool" --argjson args "$tool_args" \
        '{tool_name:$name, arguments:$args}')" \
  | jq -r .verdict)

if [ "$verdict" = "block" ]; then
  echo "tool blocked"; exit 1
fi
# ... execute the tool ...
```

## LangChain

LangChain's `ChatOpenAI` is a thin wrapper over the OpenAI Python SDK
and forwards `base_url` and `default_headers`. Point those at the
Blindsight reverse proxy and every LangChain call, chains, retrievers,
agent LLM steps, flows through Runtime Security.

```python theme={null}
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o",
    api_key=OPENAI_API_KEY,
    base_url=f"{BLINDSIGHT_URL}/api/runtime-security/proxy/openai/v1",
    default_headers={
        "X-API-Key": BLINDSIGHT_API_KEY,
        "X-Blindsight-App-Id": BLINDSIGHT_APP_ID,
    },
)

reply = llm.invoke("Hello")
```

`init_chat_model("openai:gpt-4o", base_url=..., default_headers=...)`
works the same way for projects that use the model‑string indirection.

For Anthropic, swap to `ChatAnthropic` from `langchain-anthropic`,
point `base_url` at `/api/runtime-security/proxy/anthropic`, and pass
the Blindsight key on `X-Blindsight-Key` (Anthropic reserves `x-api-key`
for the upstream provider).

<Tip>
  Want event metadata tagged with LangChain `run_id`? Add a small
  `BaseCallbackHandler` that posts to `/scan/input` from
  `on_llm_start` with `metadata={"langchain_run_id": str(run_id)}`.
  The handler can't rewrite the prompt (observability‑only) but the
  events show up in the dashboard joined to your LangChain traces.
</Tip>

## LlamaIndex

LlamaIndex's `OpenAI` LLM accepts `api_base` and `default_headers`:

```python theme={null}
from llama_index.llms.openai import OpenAI

llm = OpenAI(
    model="gpt-4o",
    api_key=OPENAI_API_KEY,
    api_base=f"{BLINDSIGHT_URL}/api/runtime-security/proxy/openai/v1",
    default_headers={
        "X-API-Key": BLINDSIGHT_API_KEY,
        "X-Blindsight-App-Id": BLINDSIGHT_APP_ID,
    },
)

reply = llm.complete("Hello")
```

To apply this globally across query engines, retrievers, and agents:

```python theme={null}
from llama_index.core import Settings
Settings.llm = llm
```

For Anthropic, `from llama_index.llms.anthropic import Anthropic`
with `api_base="/api/runtime-security/proxy/anthropic"` and the
Blindsight key on `X-Blindsight-Key`.

<Tip>
  **Agent tool calls.** The proxy sees LLM traffic but not the agent
  runtime's *execution* of a tool. If you need `scan/tool-call`
  coverage on `FunctionCallingAgent` or `ReActAgent` tool dispatches,
  wrap your `FunctionTool` so its `fn` posts to
  `/api/runtime-security/scan/tool-call` before invoking the real
  function. Same pattern works for LangChain `AgentExecutor`'s
  `BaseTool`.
</Tip>

## Picking a pattern

<AccordionGroup>
  <Accordion title="I just want it on, fast">
    Reverse proxy (Python or Node). One config change, full coverage.
  </Accordion>

  <Accordion title="I have custom middleware">
    Scan API. Two HTTP calls per request, full control over what you
    do with each verdict.
  </Accordion>

  <Accordion title="I have an agent that runs tools">
    Reverse proxy for the LLM, plus tool‑call scan wrapping every
    tool dispatch. The two combine to cover the whole loop.
  </Accordion>

  <Accordion title="I scan documents that aren't going to a model">
    Scan API `/scan/batch`. Up to 32 items per call, one license unit
    per item, much cheaper HTTP overhead.
  </Accordion>
</AccordionGroup>
