API Documentation

Integrate Mastiff Defense compliance guardrails into your store's chatbot.

Overview

Mastiff Defense is a compliance screening middleware: a three-layer AI guardrail that sits between your customers and your chatbot's LLM. Every message is screened before reaching the LLM, and every response is screened before reaching the customer.

There are two integration modes depending on your setup:

Middleware Mode (Recommended)

You have your own LLM or chatbot (Tidio, Gorgias, OpenAI, etc.). Use /screen/input and /screen/output to screen messages around your own LLM call.

All-in-One Mode

You don't have an AI backend yet. Use /evaluate and Mastiff Defense screens the input and returns an AI-generated response in one call. You provide the chat interface.

Base URL:

https://mastiffdefense.com

The API is currently unversioned. Changes are additive (new fields may appear on responses over time, so parse defensively), and any breaking change would be announced by email in advance.

Authentication

All requests require an API key passed in the request header. Your API key was provided when you installed the app. Each key is tied to your store.

X-API-Key: your-api-key-here

If your key is missing or invalid the request returns status: "unauthorized". Contact support if you need a new key.

How It Works

Every message passes through three guardrail layers in order:

Layer 1: Keywords
Layer 2: Policy Rules
Layer 3: Semantic Evaluation

If any layer blocks the message, processing stops immediately. The remaining layers are skipped. The same three layers run on both the input (before your LLM) and the output (after your LLM).

Middleware Mode

POST /screen/input

POST /screen/input Screen customer message before sending to your LLM

Run the customer's message through all three guardrail layers. If the result is clean, pass message to your LLM. If blocked, show the message field to the customer and stop.

Request Body

{ "message": "string (the customer's message, required)" }

message must be 5,000 characters or fewer; longer requests return HTTP 400 with an error field. The same limit applies to /screen/output. During screening, input is truncated to your store's configured message length limit (default 2,000 characters). The /screen/* endpoints apply this limit while screening but do not return it; the value is returned as limits.maxInputLength on /evaluate responses.

Response

{ "status": "clean", "message": "What is your return policy?", "riskScore": 0.1, "riskLevel": "low", "reason": "Standard customer service inquiry", "source": "semantic_eval" }

Response fields

Every screening response (from /screen/input, /screen/output, and /evaluate) carries these diagnostic fields alongside status:

FieldTypeMeaning
riskScore float, 0–1 How risky the content scored. Higher is riskier; 0 means clean.
riskLevel string low, medium, or high, banded from the score. A Layer 1 or Layer 2 hard block reports critical.
reason string A fixed category for the decision, safe to surface or log anywhere: one of policy_violation, sensitive_data, service_paused, backend_unavailable, invalid_api_key, or ok. It deliberately does not say which keyword, value, or phrase triggered the decision — that detail would tell anyone probing your storefront exactly how to work around it, and could echo a customer's own personal data back into their browser. The full explanation is written to your audit log and appears under Recent Flags on your store settings page.
source string Which layer decided: keyword_check (Layer 1), snapshot_check (Layer 2), or semantic_eval (Layer 3).

POST /screen/output

POST /screen/output Screen your LLM's response before delivering to customer

Run your LLM's response through all three guardrail layers. If clean or redacted, show the message field to the customer. If blocked, show the message field (a safe fallback) instead of the LLM's response.

Request Body

{ "message": "string (your LLM's response, required)" }

Response

{ "status": "clean", "message": "Your order will arrive in 3-5 business days.", "riskScore": 0.1, "riskLevel": "low", "reason": "Standard shipping information", "source": "semantic_eval" }

Response Statuses

StatusMeaningWhat to do
clean Passed all layers Use the message field as-is
blocked Violated policy, stop here Show message to customer (safe fallback)
redacted Sensitive content removed Show message (cleaned version)
paused Service is paused for this store (billing, quota, or merchant toggle) Show message (a customer-safe notice). Do not call your LLM
unavailable Temporary service issue on our side Show message (a customer-safe notice). Do not call your LLM; retry later
unauthorized API key missing, invalid, or rotated Show your own fallback and alert your team. The message is a diagnostic, not customer copy
error Unexpected server error Show fallback message, retry

All statuses are returned with HTTP 200. For clean, blocked, redacted, paused, and unavailable, the message field is safe to show the customer. Never forward a paused, unavailable, unauthorized, or error message into your own LLM as if it were the customer's text; only clean input should reach your LLM.

A redacted status means matched sensitive spans were masked in place inside the message (or response on /evaluate) field, and the rest of the text is preserved. Show the returned message as-is; it is the cleaned version, safe to deliver to the customer.

Middleware Mode: Code Examples

// Full middleware flow: screen input → your LLM → screen output async function handleCustomerMessage(customerMessage) { // Step 1: Screen the input const inputCheck = await fetch('https://mastiffdefense.com/screen/input', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'your-api-key-here', }, body: JSON.stringify({ message: customerMessage }), }).then(r => r.json()); if (inputCheck.status === 'blocked') { return inputCheck.message; // Safe block message, show to customer } if (inputCheck.status === 'paused' || inputCheck.status === 'unavailable') { return inputCheck.message; // Customer-safe service notice, do NOT call your LLM } if (inputCheck.status !== 'clean') { // unauthorized or error: fix your integration / retry, never // forward these service notices into your LLM console.error('Screening failed:', inputCheck.status); return 'Sorry, I am unable to help right now. Please try again shortly.'; } // Step 2: Call your own LLM with the screened input const llmResponse = await yourLLM(inputCheck.message); // Step 3: Screen the output const outputCheck = await fetch('https://mastiffdefense.com/screen/output', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'your-api-key-here', }, body: JSON.stringify({ message: llmResponse }), }).then(r => r.json()); // Always return the message field: clean, redacted, or blocked fallback return outputCheck.message; }
# Step 1: Screen the input curl -X POST https://mastiffdefense.com/screen/input \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{"message": "What is your return policy?"}' # Step 2: Call your own LLM (not shown, use your LLM provider) # Step 3: Screen the output curl -X POST https://mastiffdefense.com/screen/output \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{"message": "Returns are accepted within 30 days of purchase."}'
import requests API_KEY = 'your-api-key-here' BASE = 'https://mastiffdefense.com' def handle_customer_message(customer_message): # Step 1: Screen the input input_check = requests.post( f'{BASE}/screen/input', headers={'Content-Type': 'application/json', 'X-API-Key': API_KEY}, json={'message': customer_message} ).json() if input_check['status'] == 'blocked': return input_check['message'] # Safe block message, show to customer if input_check['status'] in ('paused', 'unavailable'): return input_check['message'] # Customer-safe service notice, do NOT call your LLM if input_check['status'] != 'clean': # unauthorized or error: fix your integration / retry, never # forward these service notices into your LLM return 'Sorry, I am unable to help right now. Please try again shortly.' # Step 2: Call your own LLM with the screened input llm_response = your_llm(input_check['message']) # Step 3: Screen the output output_check = requests.post( f'{BASE}/screen/output', headers={'Content-Type': 'application/json', 'X-API-Key': API_KEY}, json={'message': llm_response} ).json() # Always return the message field: clean, redacted, or blocked fallback return output_check['message']

All-in-One Mode

POST /evaluate

POST /evaluate All-in-one: screen + generate response

For merchants who don't have their own LLM. Mastiff Defense screens the input, generates an AI response, screens the output, and returns the final result in a single call.

Request Body

{ "userInput": "string (the customer's message, required)", "conversationHistory": [ { "role": "user", "content": "previous customer message" }, { "role": "assistant", "content": "previous AI response" } ] }

Conversation history is optional but improves response quality for multi-turn conversations. It is never stored on our servers, so send it with each request.

Request limits

conversationHistory accepts at most 10 entries, each with role of user or assistant and content of 5,000 characters or fewer. Exceeding any of these returns HTTP 400 with an error field and no response field. Since history grows with every turn, always trim client-side before sending: keep only the last 10 messages (history.slice(-10) in JavaScript, history[-10:] in Python), as shown in the examples below.

Response

{ "status": "allowed", "response": "The AI-generated response to show the customer", "riskScore": 0.12, "riskLevel": "low", "reason": "No policy violations detected", "source": "semantic_eval", "limits": { "maxInputLength": 2000 } }

The response field is customer-safe for every status except unauthorized (an integration problem: show your own fallback and alert your team). Authenticated responses also include a limits object with your store's configured maxInputLength, which you can use to size input fields. The status values match the table above, with allowed in place of clean.

All-in-One Mode: Code Examples

const response = await fetch('https://mastiffdefense.com/evaluate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'your-api-key-here', }, body: JSON.stringify({ userInput: customerMessage, // Trim client-side: the API rejects more than 10 entries (HTTP 400) conversationHistory: history.slice(-10), }), }); if (!response.ok) { // HTTP 400 (bad request shape) or other failure: the body has an // `error` field and no `response` field chatbot.reply('Sorry, I am unable to help right now. Please try again.'); return; } const data = await response.json(); // Customer-safe for every status except 'unauthorized' if (data.status === 'unauthorized') { chatbot.reply('Sorry, I am unable to help right now. Please try again.'); return; } chatbot.reply(data.response);
curl -X POST https://mastiffdefense.com/evaluate \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key-here" \ -d '{ "userInput": "What is your return policy?", "conversationHistory": [] }'
import requests response = requests.post( 'https://mastiffdefense.com/evaluate', headers={ 'Content-Type': 'application/json', 'X-API-Key': 'your-api-key-here', }, json={ 'userInput': customer_message, # Trim client-side: the API rejects more than 10 entries (HTTP 400) 'conversationHistory': history[-10:], } ) data = response.json() # HTTP 400 bodies have an 'error' field and no 'response' field if not response.ok or data.get('status') == 'unauthorized': chatbot.reply('Sorry, I am unable to help right now. Please try again.') else: # Customer-safe for every status except 'unauthorized' chatbot.reply(data['response'])

General

Error Handling

Wrap all API calls in try/catch. If a request fails or times out, show a fallback message and retry.

try { const result = await screenInput(customerMessage); // handle result } catch (error) { chatbot.reply('Sorry, I am unable to process your request right now. Please try again.'); }

Average response time is under 1 second for keyword blocks, 1-3 seconds when semantic analysis runs. The platform does not hold a request open on a fixed per-request timeout clock, so set your own client-side timeout (30 seconds is a sensible ceiling) and treat a timeout like any other failed request: show a fallback and retry. (Request rate is a separate concern — see Rate limits below.) Note that a timed-out or proxied failure may return an HTML error page rather than JSON, so guard your JSON parsing.

The /screen/input, /screen/output, and /evaluate endpoints are stateless and side-effect-free (no conversation is stored server-side), so retrying a failed or timed-out request is always safe.

Rate limits

Requests to /evaluate, /screen/input, and /screen/output are rate-limited to 15 requests per minute for each unique combination of API key and client IP address, with a per-IP backstop of 120 requests per minute. The first limit gives each of a storefront's shoppers their own pool, so a busy store scales with its shopper count.

An over-limit request returns HTTP 429 with the body { "error": "Too many requests." }. There is no status field on this response, so detect it with !response.ok (or by checking for HTTP 429) rather than by reading status, and back off before retrying.

Server-side integrations

Because the primary limit keys on API key and client IP, a middleware or server-side integration that calls Mastiff Defense from a single backend IP shares one 15/min bucket across all of its customers. High-traffic stores integrating this way should email [email protected] to have the limit raised.

Fail Mode

Fail mode controls what happens when Layer 3 (AI semantic analysis) is temporarily unavailable, for example during an upstream model outage. Layers 1 and 2 (keyword matching and policy rules) always run regardless.

Fail Open (Default)

If semantic analysis goes down, messages that passed Layers 1 and 2 are allowed through. Your chatbot keeps working. Suitable for most retail stores where uptime matters more than deep compliance coverage during outages.

Fail Closed

If semantic analysis goes down, all messages are blocked until service recovers. Your chatbot stops responding. Suitable for stores handling sensitive topics (health, finance, legal) where compliance must be guaranteed at all times, even at the cost of availability.

Fail mode is configured per-tenant in your policy settings. The tradeoff is straightforward:

ModeDuring L3 outageBest for
open Chatbot keeps working (L1+L2 still protect you) Retail, e-commerce, general customer service
closed Chatbot stops responding entirely Sensitive industries: health, finance, legal

If no fail mode is configured, the platform defaults to open. Merchants who installed the app can change fail mode themselves on the store settings page under the Fail Mode toggle. API-only tenants without access to a settings page can email [email protected].

Platform Guides

Tidio

Lyro compatibility

Mastiff Defense cannot be used alongside Lyro. Lyro runs its own autonomous conversation loop and Tidio disables external flow integrations while it is active, making it impossible to screen messages in either direction. If you deactivate Lyro and switch to Tidio's standard Flow builder, we can walk you through the setup. Email [email protected] and we'll get you configured.

Gorgias: Step-by-Step Setup

Gorgias is a helpdesk for online stores. Mastiff Defense integrates with Gorgias to screen outgoing agent responses, logging anything sensitive before your support team sends it to a customer.

Which mode to use with Gorgias

Use Middleware output mode (/screen/output) to screen agent responses. This catches sensitive pricing, internal notes, or policy violations in support replies and logs them to your Mastiff Defense audit trail.

How the Gorgias integration works

Gorgias HTTP integrations are fire-and-forget: the integration sends a message to Mastiff Defense, but Gorgias cannot read the response back or act on it. Each flagged message is recorded in your Mastiff Defense audit log as a compliance decision: which guardrail triggered, the risk score, and a generalized reason. The message text itself is not stored (a deliberate privacy decision), so use the log to spot when and why flags happen, then locate the message in the Gorgias ticket timeline by timestamp. The trigger fires on every new message (both customer messages and agent replies), so all traffic through a ticket is screened.

Step 1: Create the HTTP Integration

In Gorgias, go to Settings → HTTP integration, then click the Manage tab. Click Add HTTP integration in the top right. Fill in the form as follows:

Integration name: Mastiff Defense Trigger: Ticket message created URL: https://mastiffdefense.com/screen/output HTTP Method: POST Headers: X-API-Key: your-api-key-here Request Body (JSON): { "message": "{{ticket.messages[-1].body_text}}" }

Leave OAuth2 disabled. Authentication is handled by the X-API-Key header above. Click Add Integration.

Step 2: Review flagged messages

When Mastiff Defense flags a message, it appears in the Recent Flags section of your store settings page. The log shows which guardrail triggered, the risk score, and a reason. If you need help reviewing anything, email [email protected].

If you need real-time blocking (stopping a reply before it reaches the customer), contact us. This requires a custom Gorgias API callback setup that we can configure for your store.

Step 3: Test it

Open a test ticket in Gorgias and have an agent send a reply containing something that should be flagged (e.g., paste an internal pricing formula or a keyword from your policy). Within a few seconds a new entry should appear in your Mastiff Defense audit log showing the block decision, which guardrail triggered, and the reason.

Need Help Setting This Up?

Integration setup takes about 10 minutes for most stores, but every setup is a little different. If you get stuck at any step (wrong variable name, unexpected response, or the flow isn't firing), email us and we'll walk you through it.

Free setup support

Email [email protected] with your store domain and which platform you're using (Tidio, Gorgias, or something else). We'll respond within one business day.

Using a different platform, such as Zendesk, Freshdesk, Intercom, Re:amaze, or a custom chatbot? The same API works with any platform that can make HTTP requests. The code examples in the Code Examples section above apply directly.