Integrations
Route any agent framework through Sentisec with a base-URL change — and what your agent loop actually does when a tool call is blocked.
Sentisec speaks the Anthropic Messages API and the OpenAI Chat Completions API. Any SDK or framework built against either shape routes through it by changing one argument — the base URL. Your prompts, your tools, and your agent loop stay exactly as they are.
This section has a recipe per framework, a per-transport capability map, and the one thing most integration docs skip: what your agent actually experiences when Sentisec stops a tool call.
Pick your integration
OpenAI SDK
The official openai Python and Node clients, via base_url, and the
Python Agents SDK.
Anthropic SDK
The official anthropic Python and Node clients, via base_url.
LangChain
ChatOpenAI / ChatAnthropic, and what create_agent and the legacy
AgentExecutor do under a block.
LangGraph
Prebuilt agents and custom StateGraph loops, with the full blocked-call
message trace.
CrewAI
crewai's LLM wrapper, and where a block shows up for a crew.
PydanticAI
OpenAIModel / AnthropicModel, typed tools, and the retry path.
LlamaIndex
Agents, the tool-calling serialiser, and RAG ingestion.
What each transport covers
The capability map — what is inspected and enforced on each API shape.
Calls waiting for approval
Recognise a held answer, wait for the decision, and re-send so the approved call runs once.
SaaS platforms
One workspace per customer, provisioned from your signup flow.
The pattern
Every integration on this site is the same two lines of change:
- Point the client's base URL at your workspace.
- Use your Sentisec API key in place of the provider key. Sentisec holds the upstream credential, so your provider key never ships in your application config.
Everything downstream of that — tool schemas, streaming, retries, structured outputs — is unchanged. Frameworks that wrap a provider client inherit the change without knowing Sentisec exists.
If you would rather instrument in-process than swap a URL, the Python SDK
ships wrap_anthropic and wrap_openai helpers; see the
Python SDK quickstart.
Base URLs
https://api.sentisec.ch/ws/<your-workspace-id> # the official Anthropic SDKs, Claude Code
https://api.sentisec.ch/ws/<your-workspace-id>/v1 # OpenAI-shaped clients, the Vercel AI SDK, MastraYour workspace id is on the dashboard next to your API key.
Whether the URL ends in /v1 depends on the client, not on the API it
speaks. Use the same shape as the client's own default vendor URL:
- No
/v1for the official Anthropic SDKs and Claude Code. Their default ishttps://api.anthropic.comand they append/v1/messagesthemselves, so adding/v1produces/v1/v1/messagesand a404. - Ends in
/v1for the OpenAI SDKs and the Vercel AI SDK's@ai-sdk/openaiand@ai-sdk/anthropicproviders. Their default already ends in/v1(https://api.openai.com/v1,https://api.anthropic.com/v1) and they append only the route:/messages,/chat/completionsor/responses.
Vercel AI SDK and Mastra
The AI SDK's Anthropic provider is not the official Anthropic SDK: its
base URL ends in /v1. Mastra agents take an AI SDK model, so the
same URL applies to them.
import { createAnthropic } from "@ai-sdk/anthropic";
const anthropic = createAnthropic({
baseURL: "https://api.sentisec.ch/ws/<your-workspace-id>/v1", // ends in /v1
apiKey: process.env.SENTISEC_API_KEY, // your Sentisec key
});Pass anthropic("<model id>") as the model of an AI SDK call or a Mastra
Agent. createOpenAI from @ai-sdk/openai takes the same baseURL.
The AI SDK's Amazon Bedrock provider is different: it speaks Converse and
is configured as in Bedrock → Native Converse.
Use the workspace-scoped form. It is the one shape that guarantees your workspace's own policy travels with every request, on every transport. The recipes on this site all use it, and so should your production config.
What happens when a tool call is blocked
This is the part worth reading before you wire anything, because it decides how your agent behaves on the day a block actually fires.
A block never fails your request. It returns 200, with no exception and
no error status, in the provider's own format.
The default: withhold
Sentisec removes the blocked call from the answer and puts an explanation in its place, as ordinary assistant text:
[Sentisec blocked tool_use: Sentisec policy blocked this tool call. …]The shape is the same on Messages, Chat Completions, Responses and Bedrock
Converse, streamed or not. Other calls in the same answer that only read
keep their own decision. A call that changes something is not sent when
another call in the same answer is held or refused (see
calls proposed together). The turn ends with
a standard value, tool_use or tool_calls while an allowed call
remains, end_turn or stop otherwise. There is no Sentisec-specific
stop reason.
With no allowed call left, your agent loop ends the run as it would on any final answer, and the explanation is that answer. It reaches the person or code that called the agent, not the model. You need no special case in your code, and the blocked call never runs.
Calls proposed together
A model often proposes several calls in one answer, for example a refund and a reply that confirms it. Sentisec reads such an answer as one plan. When one of its calls is held for approval or refused, the other calls in that answer that change something are not sent either. A reply cannot tell a customer about a refund that is still waiting for a person.
- Calls that only read still run. A call only reads when its tool's class in protection setup says so. A tool with no class counts as one that changes something.
- A call that needs approval itself is held with its own review. If two calls in one answer each need approval, such as two refunds, each is held and an admin approves each one. The agent still waits once. A call refused on its own is refused with its own explanation.
- The agent is told why. A call that was not sent comes back the way a blocked call does. In place of a rule, it carries a fixed explanation: it was proposed together with a call that is waiting for approval, or with a call that was refused, so it has not run. It names no rule and no value.
- The agent proposes it again. After an approval, the agent's retry of the held call is released once, and each call it proposes alongside is judged on its own. After a refusal, the agent proposes a call again on its own if it still needs it.
- On a stream, the frames of a call that changes something arrive once every call in the answer has been decided, and so does everything after them. Text before them streams as the model writes it.
- On
shadowandobservenothing is held back. The calls run, and the session records that they would have been held back.
This applies to the API endpoints. In subscription mode, Claude Code and Codex are checked one call at a time.
Opt in: rename
If you would rather the model re-plans in the same run, a workspace admin
can choose rename in the dashboard, on Protection → Enforce under
More settings → When a call is blocked, or with
PUT /v1/workspaces/{workspace_id}/recovery.
The blocked call then keeps its id and its place in the answer, but comes
back named sentisec_tool_call_blocked, with these arguments:
{ "error": "tool_call_blocked", "reason": "<why the call was stopped>" }A call held for approval also carries offers, which tell your
application where to wait for the decision; see
calls waiting for approval.
Your framework answers it the way it answers any tool it does not have.
Sentisec replaces that answer with its own ruling before the model reads
it, so the model learns why and can take another route. A third mode,
notice, does the same under a notice tool your agent declares itself.
Rename needs a client whose tool loop answers an unknown tool. These are the clients and versions it works with:
| Client | Default (withhold) | With rename |
|---|---|---|
anthropic 0.120.2, tool runner | The run ends with the explanation | The model gets the ruling and re-plans |
langchain 1.3.14, create_agent | The run ends with the explanation | The model gets the ruling and re-plans |
langgraph 1.2.9, create_react_agent | The run ends with the explanation | The model gets the ruling and re-plans |
pydantic-ai 2.20.0 | The run ends with the explanation | The model gets the ruling and re-plans |
llama-index-core 0.14.23, FunctionAgent | The run ends with the explanation | The model gets the ruling and re-plans |
crewai 1.15.8 | The run ends with the explanation | Re-plans, except on streamed Responses |
openai 2.50.0 | The run ends with the explanation | No tool loop: the renamed call reaches your own dispatch code |
openai-agents 0.23.1, Runner | The run ends with the explanation | On Responses without streaming, the model gets the ruling and re-plans when you set RunConfig(tool_not_found_behavior="return_error_to_model"). Without it, the SDK raises an error on the unknown tool and the run ends. Chat Completions and streamed runs are not covered yet. See the recipe |
@mastra/core 1.75.0, Agent on @ai-sdk/anthropic 4.0.78 | The run ends with the explanation | On Messages, streamed or not, the model gets the ruling and re-plans |
The same list, with the API each client was checked on, is in the
qualification field of GET /v1/workspaces/{workspace_id}/recovery.
Rename applies to the API endpoints only. The subscription lanes for Claude Code and Codex never use it.
Which to choose
The mode decides who learns about a block: the model, inside the run, or your code, after it.
withhold. When nothing else in the answer runs, the run ends, and its final answer is Sentisec's explanation. That text is written for your code, not for your customer, and the model does not get to act on it, so the agent cannot tell the customer anything about the block. If your application shows the agent's final answer to a customer, recognise the explanation and show your own message instead.rename. The model reads the ruling in the same run. It can take another route, or tell the customer in its own words what happened, for example that a person has to approve a refund before it goes through.
Use rename when your client is on the list above for the API you
call. Keep withhold otherwise: a client that is not on the list can
raise an error on the renamed call, or hand it to your own dispatch code.
In both modes the blocked call never runs, and a call held for approval
can still go through once a person approves it; see
calls waiting for approval.
A refused destination the agent can fix
Sometimes a recipient, an address or an account is refused because its value came from content outsiders wrote, such as an inbound email, and not from your organisation's own records or the user's request. The explanation then names the argument and says where its value must come from: your organisation's own records or the user's request. It invites the agent to take the value from there and propose the call again. It never shows the value. The new call is checked again from scratch, so a destination an outsider chose is still refused.
The agent can act on this only when it reads the explanation inside its
run, which takes a recovery opt-in such as rename. Under the default,
withhold, the run ends and the explanation reaches the person or code
that called the agent. withhold stays the default.
When the agent does fix it, for example by looking the recipient up in your records, and its new call goes through, the session reads Blocked, agent recovered. Nobody needs to act on it. The refusal stays on the record and its review stays open, so you can still look at what the agent was asked to do. See session findings.
The other cases
- Monitor-only (shadow). Every call passes untouched, including calls Sentisec would hold, the calls proposed together with them, and calls it could not read. The verdict still records what enforcing would have done. New workspaces start here.
- A call held for approval. The explanation says the call awaits approval, and carries an offer with the address where your application reads the decision. After an admin approves it once, the agent's retry of the same request is released once, with the arguments the admin approved. An application that opens a new job, event or conversation for each attempt sends a new request, and a reworded retry there asks again: re-send the original instead. How to wait and re-send is in calls waiting for approval.
- A tool call written as text. If a model prints a tool call as text instead of making one, the whole answer is withheld and the turn ends.
- Sentisec cannot evaluate a call. The answer is still
200; the call is withheld behind a fixed explanation that carries a reference id.
Each framework page below shows what that framework does with a block.
Frameworks not listed here
If your framework targets Anthropic Messages or OpenAI Chat Completions — directly or through a provider abstraction — the base-URL swap works the same way. The recipes here are the ones we maintain and re-check; a framework's absence is not a limitation, just a gap in this list. If you want yours covered, tell us.
The recipes are Python. The TypeScript agent ecosystem (Vercel AI SDK, Mastra, OpenAI Agents SDK for Node) is not covered by a recipe on this site yet, though the official OpenAI and Anthropic Node clients are — see their pages above. For the AI SDK and Mastra base URL, see Vercel AI SDK and Mastra. A Mastra agent that waits for an approval and re-sends is in calls waiting for approval.
Automation platforms
The n8n-nodes-sentisec community node is in private preview and not ready
for production workflows. If gating an n8n workflow is on your roadmap,
talk to us — the Gmail triage
recipe shows the workflow shape it targets.
Related
- Providers — the upstream model providers Sentisec dispatches to
- CLI agents — Claude Code, Cursor, Codex CLI
- Examples — end-to-end runnable recipes
- Drop-in replacement — what changes and what does not
Azure OpenAI
Azure OpenAI is not available on Sentisec yet. What to use in the meantime, and how to tell us you need it.
What each transport covers
The per-transport capability map — what Sentisec inspects and enforces on each API shape, which frameworks have a maintained recipe, and which providers carry traffic.