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

# Guardrails

> Set content policies, test your rules, and handle blocked requests in your application.

Use guardrails to set content policies for requests to the MKA1 API. You can block specific words, detect prompt-injection attempts, or describe your own policy for a model to evaluate.

Guardrails check user text before the answering model runs. You can also enable checks on generated text. They apply to Responses API requests, agent runs, Chat Completions, and batch requests when they execute.

When a guardrail blocks a request, your application receives a refusal message. A block returns HTTP 200; an evaluation failure can return an error. Read the refusal to explain the block to your user, as shown in [Detect a block in code](#detect-a-block-in-code).

## How guardrails are applied

Your requests can have an organization-wide policy and a team policy.
When both exist, both are enforced.
A team policy adds rules on top of the organization-wide policy; it never replaces or weakens it.
These policies apply to everyone in the organization or team. `X-On-Behalf-Of` does not create a separate policy for an end user.

Enabled guardrails check request input. To check generated text too, enable [output checks](#check-model-output-too) on each rule you want to apply.
The input check reads a string `input` or user-message text. It does not inspect image, audio, or file contents.
Requests using automatic model selection may have an additional guardrail check. See [Auto routing](/docs/auto-routing).

<Note>
  API keys need `read:guardrails` to view or test policies and `write:guardrails` to change them. Tests that include an enabled classifier also require `write:responses` because they invoke a model.
  Guardrails still apply to your response requests even if your key cannot view or change the policy.
</Note>

## Guardrail modes

| Mode | Blocks | Config fields |
| - | - | - |
| `classifier` | Content classified as `block` by a model using your policy prompt | `model`, `prompt`, `on_error` (default `fail_closed`), `rejection_message` |
| `ban_words` | Case-insensitive substring matches for any word or phrase on your list | `words` (default `[]`), `rejection_message` |
| `prompt_injection` | Text that matches prompt-injection detection patterns | `threshold` (0-1, default `0.7`), `rejection_message` |
| `leakage` | Text that matches system-prompt extraction patterns | `threshold` (0-1, default `0.7`), `rejection_message` |

Every guardrail also takes two fields outside `config`:

* `enabled` (default `true`) turns the guardrail on or off without removing it from the policy.
* `check_output` (off unless set) additionally applies the guardrail to model output.

Each mode has a default `rejection_message`.
Set a custom message to explain the restriction and suggest what the user can ask instead.
For example, banning `secret` also matches `secrets`; matching is not limited to whole words.

Prompt-injection and leakage checks use pattern detection, not a comparison against your actual system prompt.
If a pattern check cannot run, it does not block the request. For classifiers, you can [choose what happens when evaluation fails](#choose-what-happens-when-evaluation-fails).

## Configure guardrails

`PUT /api/v1/llm/guardrails` replaces a policy, so include every guardrail you want to keep.
Omit `team_id` or pass `null` to set the organization-wide policy (organization admin or owner only).
Pass a `team_id` to set that team's additional policy. With the required scope, team members can manage their own team's policy; organization admins can manage any team in their organization.
Rules that team members must not be able to remove belong in the organization-wide policy.

### Before you start

Use an API key with `read:guardrails`, `write:guardrails`, and `write:responses` for the examples below, and a model available to your organization.
Try the examples with a test team. Replace `<team-id>` with its ID and use that team's API key for test and response requests.
Before changing an existing team policy, save a copy from [`GET /api/v1/llm/guardrails/policies`](/api-reference/guardrails/list-guardrails-policies). This lets you restore it when you finish testing. `GET /guardrails` returns combined organization and team rules, so use the policies endpoint for your backup.

This example bans a word on both input and output, with a custom rejection message, and enables input checks for prompt injection and leakage:

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  import { SDK } from '@meetkai/mka1';

  const sdk = new SDK({ bearerAuth: 'Bearer <mka1-api-key>' });

  const policy = await sdk.guardrails.updateGuardrails({
    updateGuardrailsRequest: {
      teamId: '<team-id>',
      guardrails: [
        {
          mode: 'ban_words',
          enabled: true,
          checkOutput: true,
          config: {
            words: ['confidential'],
            rejectionMessage: 'This request mentions restricted material. Please rephrase.',
          },
        },
        {
          mode: 'prompt_injection',
          enabled: true,
        },
        {
          mode: 'leakage',
          enabled: true,
        },
      ],
    },
  });
  ```

  ```python Python SDK theme={null}
  from meetkai_mka1 import SDK
  from meetkai_mka1.models import ClassifierConfig, ClassifierGuardrail

  sdk = SDK(bearer_auth="Bearer <mka1-api-key>")

  policy = sdk.guardrails.update_guardrails(
      team_id="<team-id>",
      guardrails=[
          {
              "mode": "ban_words",
              "enabled": True,
              "check_output": True,
              "config": {
                  "words": ["confidential"],
                  "rejection_message": "This request mentions restricted material. Please rephrase.",
              },
          },
          {
              "mode": "prompt_injection",
              "enabled": True,
          },
          {
              "mode": "leakage",
              "enabled": True,
          },
      ],
  )
  ```

  ```csharp C# SDK theme={null}
  using MeetKai.MKA1;
  using MeetKai.MKA1.Types.Components;
  using System.Collections.Generic;
  using System;
  using System.Linq;
  using MeetKai.MKA1.Types.Requests;

  var sdk = new SDK(bearerAuth: "Bearer <mka1-api-key>");

  var policy = await sdk.Guardrails.UpdateGuardrailsAsync(body: new UpdateGuardrailsRequest()
  {
      TeamId = "<team-id>",
      Guardrails = new List<GuardrailConfig>()
      {
          GuardrailConfig.CreateBanWords(new BanWordsGuardrail()
          {
              CheckOutput = true,
              Config = new BanWordsConfig()
              {
                  Words = new List<string>() { "confidential" },
                  RejectionMessage = "This request mentions restricted material. Please rephrase.",
              },
          }),
          GuardrailConfig.CreatePromptInjection(new PromptInjectionGuardrail()),
          GuardrailConfig.CreateLeakage(new LeakageGuardrail()),
      },
  });
  ```

  ```bash CLI theme={null}
  mka1 guardrails update \
    --body '{
      "team_id": "<team-id>",
      "guardrails": [
        {
          "mode": "ban_words",
          "enabled": true,
          "check_output": true,
          "config": {
            "words": ["confidential"],
            "rejection_message": "This request mentions restricted material. Please rephrase."
          }
        },
        {
          "mode": "prompt_injection",
          "enabled": true
        },
        {
          "mode": "leakage",
          "enabled": true
        }
      ]
    }'
  ```

  ```bash Bash theme={null}
  curl https://apigw.mka1.com/api/v1/llm/guardrails \
    --request PUT \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "team_id": "<team-id>",
      "guardrails": [
        {
          "mode": "ban_words",
          "enabled": true,
          "check_output": true,
          "config": {
            "words": ["confidential"],
            "rejection_message": "This request mentions restricted material. Please rephrase."
          }
        },
        {
          "mode": "prompt_injection",
          "enabled": true
        },
        {
          "mode": "leakage",
          "enabled": true
        }
      ]
    }'
  ```
</CodeGroup>

To see the combined policy that applies to your requests, call [`GET /api/v1/llm/guardrails`](/api-reference/guardrails/get-effective-guardrails) or run `mka1 guardrails get`.
To view organization and team policies separately, use [`GET /api/v1/llm/guardrails/policies`](/api-reference/guardrails/list-guardrails-policies).
Organization admins see all policies in their organization; other callers see the organization-wide policy and their own team's policy.
When you finish testing, restore the saved team policy with `PUT`, or delete the team's policy with `DELETE /api/v1/llm/guardrails?team_id=<team-id>` if none existed before. Deleting a team policy leaves organization-wide rules enforced.

### Check model output too

Set `check_output: true` to apply a guardrail to the model's generated text.
For example, an output check can block a banned term that the model includes even though it was absent from the request.
It also covers Responses API output that would otherwise finish as `incomplete`, such as when a token limit is reached. A block returns a `completed` response containing a refusal. Generated tokens still count toward usage.

<Warning>
  Live streaming text can reach your client before the output check runs. If you must withhold unchecked output, buffer it until the terminal event or use a non-streaming request. See [Streaming](#streaming).
</Warning>

You are charged for tokens generated before an output check blocks the response. See [Usage and billing](#usage-and-billing).

## Classify content using your own policy

Use `mode: "classifier"` when your policy depends on meaning rather than specific words.
You supply `config.prompt` to describe which content to allow and which to block.
The classifier evaluates the content against your policy and returns `allow` or `block`.
You do not need prompt placeholders or JSON formatting instructions.

For example, keep a support assistant focused on orders, products, and returns.

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  const policy = await sdk.guardrails.updateGuardrails({
    updateGuardrailsRequest: {
      teamId: '<team-id>',
      guardrails: [{
        mode: 'classifier',
        enabled: true,
        config: {
          model: '<classifier-model-id>',
          prompt: 'Allow questions about orders, shipping, returns, refunds, and our products, including complaints. Block unrelated requests, such as writing fiction or completing homework.',
          onError: 'fail_closed',
          rejectionMessage: 'I can help with orders, returns, shipping, and product questions. Please ask about one of those topics.',
        },
      }],
    },
  });
  console.log(policy);
  ```

  ```python Python SDK theme={null}
  policy = sdk.guardrails.update_guardrails(
      team_id="<team-id>",
      guardrails=[
          ClassifierGuardrail(
              enabled=True,
              config=ClassifierConfig(
                  model="<classifier-model-id>",
                  prompt="Allow questions about orders, shipping, returns, refunds, and our products, including complaints. Block unrelated requests, such as writing fiction or completing homework.",
                  on_error="fail_closed",
                  rejection_message="I can help with orders, returns, shipping, and product questions. Please ask about one of those topics.",
              ),
          ),
      ],
  )
  print(policy)
  ```

  ```csharp C# SDK theme={null}
  var policy = await sdk.Guardrails.UpdateGuardrailsAsync(body: new UpdateGuardrailsRequest()
  {
      TeamId = "<team-id>",
      Guardrails = new List<GuardrailConfig>()
      {
          GuardrailConfig.CreateClassifier(new ClassifierGuardrail()
          {
              Enabled = true,
              Config = new ClassifierConfig()
              {
                  Model = "<classifier-model-id>",
                  Prompt = "Allow questions about orders, shipping, returns, refunds, and our products, including complaints. Block unrelated requests, such as writing fiction or completing homework.",
                  OnError = OnError.FailClosed,
                  RejectionMessage = "I can help with orders, returns, shipping, and product questions. Please ask about one of those topics.",
              },
          }),
      },
  });
  Console.WriteLine("Classifier policy saved.");
  ```

  ```bash CLI theme={null}
  mka1 guardrails update \
    --body '{
      "team_id": "<team-id>",
      "guardrails": [
        {
          "mode": "classifier",
          "enabled": true,
          "config": {
            "model": "<classifier-model-id>",
            "prompt": "Allow questions about orders, shipping, returns, refunds, and our products, including complaints. Block unrelated requests, such as writing fiction or completing homework.",
            "on_error": "fail_closed",
            "rejection_message": "I can help with orders, returns, shipping, and product questions. Please ask about one of those topics."
          }
        }
      ]
    }'
  ```

  ```bash Bash theme={null}
  curl https://apigw.mka1.com/api/v1/llm/guardrails \
    --request PUT \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'Content-Type: application/json' \
    --data '{
      "team_id": "<team-id>",
      "guardrails": [
        {
          "mode": "classifier",
          "enabled": true,
          "config": {
            "model": "<classifier-model-id>",
            "prompt": "Allow questions about orders, shipping, returns, refunds, and our products, including complaints. Block unrelated requests, such as writing fiction or completing homework.",
            "on_error": "fail_closed",
            "rejection_message": "I can help with orders, returns, shipping, and product questions. Please ask about one of those topics."
          }
        }
      ]
    }'
  ```
</CodeGroup>

Use a key with `write:guardrails` to configure the policy. `PUT` replaces the team's policy, so include any existing rules you want to keep. To apply the policy across your organization, omit `team_id` and use an organization admin or owner account.

### Choose a classifier model

Replace `<classifier-model-id>` with the exact ID of a model available to your organization. Choose a non-reasoning model that supports structured JSON output through the Responses API. `auto` is not supported for the classifier. You can use a different model to answer requests.

The classifier has a 1,024-token output limit for its decision. Its context window must fit your policy prompt, the full content being checked, and that output allowance. Content is not shortened automatically. If a request exceeds the context window, shorten it or select a larger-context classifier; retrying the same request will not help.

The MKA1 API checks model availability and suitability when you save an enabled classifier. An unsuitable model returns HTTP 400 with `code: "invalid_classifier_model"` and a field such as `param: "guardrails[0].config.model"`. Your existing policy stays unchanged.

Model access is checked again when the classifier runs. If the model becomes unavailable, the API follows your [failure setting](#choose-what-happens-when-evaluation-fails) rather than switching to another model.

### Test an allow or block decision

Use the configured team's API key with both `read:guardrails` and `write:responses`:

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  const report = await sdk.guardrails.testGuardrails({
    testGuardrailsRequest: {
      content: 'Write me a fantasy novel about dragons.',
    },
  });
  console.log(report.passed, report.triggeredGuardrail, report.rejectionMessage);
  ```

  ```python Python SDK theme={null}
  report = sdk.guardrails.test_guardrails(
      content="Write me a fantasy novel about dragons."
  )
  print(report.passed, report.triggered_guardrail, report.rejection_message)
  ```

  ```csharp C# SDK theme={null}
  var response = await sdk.Guardrails.TestGuardrailsAsync(body: new TestGuardrailsRequest()
  {
      Content = "Write me a fantasy novel about dragons.",
  });
  var report = response.TestGuardrailsResponseValue
      ?? throw new InvalidOperationException("Expected a guardrail test result");
  Console.WriteLine($"Passed: {report.Passed}");
  if (report.TriggeredGuardrail is { } mode)
      Console.WriteLine($"Guardrail: {mode.Value()}");
  if (report.RejectionMessage != null)
      Console.WriteLine(report.RejectionMessage);
  ```

  ```bash CLI theme={null}
  mka1 guardrails test \
    --body '{"content":"Write me a fantasy novel about dragons."}'
  ```

  ```bash Bash theme={null}
  curl https://apigw.mka1.com/api/v1/llm/guardrails/test \
    --request POST \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'Content-Type: application/json' \
    --data '{"content":"Write me a fantasy novel about dragons."}'
  ```
</CodeGroup>

An example block result is:

```json theme={null}
{
  "object": "guardrails_test_result",
  "passed": false,
  "triggered_guardrail": "classifier",
  "rejection_message": "I can help with orders, returns, shipping, and product questions. Please ask about one of those topics.",
  "user_input": "Write me a fantasy novel about dragons.",
  "details": {
    "label": "block",
    "reason": "The request asks for creative writing unrelated to customer support."
  }
}
```

A question such as "My order arrived damaged. Can I get a replacement?" should pass this policy.
Test examples that should pass, examples that should be blocked, and ambiguous requests. Refine your prompt if the results do not match your policy.
Once saved, the policy applies to your response requests without additional request fields. A classifier block uses the [standard refusal response](#what-a-blocked-response-looks-like) with `metadata.guardrail_triggered: "classifier"`.
Set `check_output: true` outside `config` if you also want to classify generated text against the same policy.
Input checks inspect user text, not image, audio, or file contents.

### Choose what happens when evaluation fails

| `config.on_error` | Behavior |
| - | - |
| `fail_closed` — default | Stop the request when the classifier cannot evaluate it. |
| `fail_open` | Continue without a result from the failed classifier. Other guardrails still apply. |

Use `fail_closed` when requests must pass the classifier before proceeding. Use `fail_open` when requests should continue if the classifier cannot run.

This setting covers unavailable models, revoked model access, rate limits, provider failures, timeouts, refusals from the classifier model, and invalid decisions.
An actual `block` decision always blocks, even with `fail_open`.
Each evaluation can take up to 30 seconds. Your policy prompt must be nonblank and contain no more than 16,000 characters. Requests that exceed the classifier's context window also follow this failure setting.

If the model is removed or becomes inaccessible, a foreground non-streaming request with `fail_closed` returns HTTP 503:

```json theme={null}
{
  "error": {
    "code": "guardrail_model_unavailable",
    "message": "The model used to evaluate this request's guardrail is no longer available or accessible. Please contact your organization administrator.",
    "type": "server_error",
    "param": null
  }
}
```

Other evaluation failures return `guardrail_evaluation_failed`. For a temporary failure, retry the request. If it persists, ask your organization administrator to check model access, rate limits, and context-window requirements.
An evaluation error means the classifier could not finish checking the content.
For background Responses requests or streams whose HTTP response has started, inspect the terminal `response.failed` event or retrieved response's `error.code` and `error.message`.
Chat Completions streams surface an error frame. An output evaluation runs after generation, so it cannot recall text already streamed to your client.

With `fail_open`, a test can return `passed: true` even when a classifier was skipped. Check `details.skipped_evaluations` to see which checks did not complete:

```json theme={null}
{
  "object": "guardrails_test_result",
  "passed": true,
  "user_input": "Where is my order?",
  "details": {
    "skipped_evaluations": [
      {
        "mode": "classifier",
        "code": "guardrail_model_unavailable",
        "message": "The model used to evaluate this request's guardrail is no longer available or accessible. Please contact your organization administrator."
      }
    ]
  }
}
```

<Note>
  The failure setting controls classifier evaluations. If the API cannot load your policies, requests currently continue without guardrail checks. Requests also continue when no policy is configured.
</Note>

Classifier checks add latency and are billed separately, including policy tests and checks that block a request. See [Usage and billing](#usage-and-billing).

## Test your policy

Use `POST /api/v1/llm/guardrails/test` to check sample content against your organization and team policies without generating an answer. It evaluates enabled input rules, including those that also have output checking enabled.

Use the API key for the team you want to test; this endpoint does not accept `team_id`. The key needs `read:guardrails`. If an enabled classifier applies, it also needs `write:responses` and must pass the spend-budget check. Classifier tests invoke the configured model and incur usage charges.

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  const report = await sdk.guardrails.testGuardrails({
    testGuardrailsRequest: {
      content: 'Summarize this confidential roadmap.',
    },
  });

  console.log(report.passed, report.triggeredGuardrail);
  ```

  ```python Python SDK theme={null}
  report = sdk.guardrails.test_guardrails(content="Summarize this confidential roadmap.")
  ```

  ```csharp C# SDK theme={null}
  var report = await sdk.Guardrails.TestGuardrailsAsync(body: new TestGuardrailsRequest()
  {
      Content = "Summarize this confidential roadmap.",
  });
  ```

  ```bash CLI theme={null}
  mka1 guardrails test --content 'Summarize this confidential roadmap.'
  ```

  ```bash Bash theme={null}
  curl https://apigw.mka1.com/api/v1/llm/guardrails/test \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "content": "Summarize this confidential roadmap."
    }'
  ```
</CodeGroup>

The result reports whether the content passed, and on a failure it names the guardrail and the message a real request would get back:

```json theme={null}
{
  "object": "guardrails_test_result",
  "passed": false,
  "triggered_guardrail": "ban_words",
  "rejection_message": "This request mentions restricted material. Please rephrase.",
  "user_input": "Summarize this confidential roadmap.",
  "details": { "matched_word": "confidential" }
}
```

`details` can explain why the content was blocked: `label` and `reason` for `classifier`, `matched_word` for `ban_words`, and a `score` or `reason` when provided by `prompt_injection` and `leakage` detection.

## What a blocked response looks like

For a foreground Responses API request, a guardrail block returns HTTP 200 with `status: "completed"` and `error: null`.
For a background request, inspect the terminal response after polling or streaming; the initial response can still be `queued` or `in_progress`.
Check these fields to identify a block:

* `metadata.guardrail_triggered` holds the mode of the guardrail that matched (`classifier`, `ban_words`, `prompt_injection`, or `leakage`).
* `output` contains a single assistant message whose only content part has `type: "refusal"`, carrying the guardrail's rejection message.
* `usage` is all zeros for an input block because the answering model did not run. Classifier usage is metered separately.

Here is a blocked response to the request from the examples above, showing the relevant fields:

```json theme={null}
{
  "id": "resp_a1b2c3d4e5f6",
  "object": "response",
  "status": "completed",
  "error": null,
  "model": "auto",
  "output": [
    {
      "id": "msg_f6e5d4c3b2a1",
      "type": "message",
      "role": "assistant",
      "status": "completed",
      "content": [
        {
          "type": "refusal",
          "refusal": "This request mentions restricted material. Please rephrase."
        }
      ]
    }
  ],
  "metadata": {
    "guardrail_triggered": "ban_words"
  },
  "usage": {
    "input_tokens": 0,
    "input_tokens_details": { "cached_tokens": 0 },
    "output_tokens": 0,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 0
  }
}
```

<Warning>
  Do not rely only on `output_text` to display a response. It is empty or absent when a request is blocked. Read the `refusal` content part so you can show the user an explanation.
</Warning>

## Detect a block in code

Read the `refusal` content part to show the caller a rejection message, and use `metadata.guardrail_triggered` to identify a platform guardrail block.
The SDK clients below use the same credentials as the configuration example. For the OpenAI SDK, initialize `openai` with your MKA1 API key and `baseURL: 'https://apigw.mka1.com/api/v1/llm/'`, as shown in [Generate a response](/docs/generate-a-response#send-a-simple-prompt).

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  const result = await sdk.llm.responses.create({
    responsesCreateRequest: {
      model: 'auto',
      input: 'Summarize this confidential roadmap.',
      stream: false,
    },
  });

  // This SDK method returns a response-or-stream union.
  if (!('output' in result)) throw new Error('Expected a non-streaming response');
  const triggered = result.metadata.guardrail_triggered;
  const refusal = result.output
    .flatMap((item) => item.type === 'message' && 'content' in item && Array.isArray(item.content)
      ? item.content.filter((part) => typeof part !== 'string' && part.type === 'refusal')
      : [])
    .at(0);
  if (refusal) {
    console.log(refusal.refusal);
    if (triggered) console.log(`Guardrail: ${triggered}`);
  } else {
    console.log(result.outputText);
  }
  ```

  ```ts OpenAI SDK theme={null}
  import OpenAI from 'openai';

  const openai = new OpenAI({
    apiKey: '<mka1-api-key>',
    baseURL: 'https://apigw.mka1.com/api/v1/llm/',
    defaultHeaders: { 'X-On-Behalf-Of': '<end-user-id>' },
  });

  const response = await openai.responses.create({
    model: 'auto',
    input: 'Summarize this confidential roadmap.',
    stream: false,
  });

  const triggered = response.metadata?.guardrail_triggered;
  const refusal = response.output
    .flatMap((item) => item.type === 'message' ? item.content : [])
    .find((part) => part.type === 'refusal');
  if (refusal) {
    console.log(refusal.refusal);
    if (triggered) console.log(`Guardrail: ${triggered}`);
  } else {
    console.log(response.output_text);
  }
  ```

  ```python Python SDK theme={null}
  res = sdk.llm.responses.create(
      model="auto",
      input="Summarize this confidential roadmap.",
  )

  triggered = res.metadata.get("guardrail_triggered")
  refusal = next(
      (part.refusal for item in res.output
       if item.type == "message" and isinstance(item.content, list)
       for part in item.content if getattr(part, "type", None) == "refusal"),
      None,
  )
  if refusal is not None:
      print(refusal)
      if triggered:
          print(f"Guardrail: {triggered}")
  else:
      print(res.output_text)
  ```

  ```csharp C# SDK theme={null}
  var response = await sdk.Llm.Responses.CreateAsync(new ResponsesCreateRequest()
  {
      Model = "auto",
      Input = ResponsesCreateRequestInput.CreateStr("Summarize this confidential roadmap."),
      Stream = false,
  });
  var res = response.ResponseObject
      ?? throw new InvalidOperationException("Expected a non-streaming response");

  var refusal = res.Output
      .SelectMany(item =>
          item.OutputMessage?.Content.Select(part => part.Refusal?.RefusalValue)
          ?? item.InputMessage?.Content.ArrayOfInputMessageContent?.Select(part => part.Refusal?.RefusalValue)
          ?? Enumerable.Empty<string?>())
      .FirstOrDefault(value => value != null);
  if (refusal != null)
  {
      Console.WriteLine(refusal);
      if (res.Metadata.TryGetValue("guardrail_triggered", out var triggered))
          Console.WriteLine($"Guardrail: {triggered}");
  }
  else
  {
      Console.WriteLine(res.OutputText);
  }
  ```

  ```bash Bash theme={null}
  curl https://apigw.mka1.com/api/v1/llm/responses \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "model": "auto",
      "input": "Summarize this confidential roadmap."
    }' | jq '([.output[] | select(.type == "message") | .content[]
      | select(.type == "refusal") | .refusal] | first) as $refusal
      | if $refusal != null
      then { blocked_by: .metadata.guardrail_triggered, message: $refusal }
      else { text: .output_text } end'
  ```
</CodeGroup>

The refusal content part also appears when a model declines a request on its own, without a guardrail.
Read refusal parts before falling back to `output_text`; use `metadata.guardrail_triggered` to label a platform guardrail block.
There is no top-level `response.refusal` field in the current MKA1 API.

## Streaming

For a foreground input block, the API sends `response.created` and `response.completed`, with the blocked response. Background streams can include earlier status events.
No `response.output_text.delta` events arrive, and there are no per-token refusal events.
Handle the final response event to display the refusal. These examples wait for that event before displaying any text, so unchecked output stays hidden. `response.completed`, `response.incomplete`, and `response.failed` each mark the end of a response.

<CodeGroup>
  ```ts TypeScript SDK theme={null}
  const stream = await sdk.llm.responses.create({
    responsesCreateRequest: {
      model: 'auto',
      input: 'Summarize this confidential roadmap.',
      stream: true,
    },
  });

  if (!(Symbol.asyncIterator in stream)) throw new Error('Expected a streaming response');

  for await (const { data: event } of stream) {
    if (event.type === 'response.completed' || event.type === 'response.incomplete') {
      const parts = event.response.output.flatMap((item) =>
        item.type === 'message' && 'content' in item && Array.isArray(item.content)
          ? item.content.filter((part) => typeof part !== 'string')
          : []);
      const refusal = parts.find((part) => part.type === 'refusal');
      const text = parts.flatMap((part) => part.type === 'output_text' ? [part.text] : []).join('');
      console.log(refusal ? refusal.refusal : text);
      break;
    } else if (event.type === 'response.failed') {
      console.error(event.response.error?.message ?? 'Response failed');
      break;
    }
  }
  ```

  ```ts OpenAI SDK theme={null}
  const stream = await openai.responses.create({
    model: 'auto',
    input: 'Summarize this confidential roadmap.',
    stream: true,
  });

  for await (const event of stream) {
    if (event.type === 'response.completed' || event.type === 'response.incomplete') {
      const parts = event.response.output.flatMap((item) =>
        item.type === 'message' ? item.content : []);
      const refusal = parts.find((part) => part.type === 'refusal');
      const text = parts.flatMap((part) => part.type === 'output_text' ? [part.text] : []).join('');
      console.log(refusal ? refusal.refusal : text);
    } else if (event.type === 'response.failed') {
      console.error(event.response.error?.message ?? 'Response failed');
    }
  }
  ```

  ```python Python SDK theme={null}
  stream = sdk.llm.responses.create(
      model="auto",
      input="Summarize this confidential roadmap.",
      stream=True,
  )

  for event in stream:
      if event.data.type in ("response.completed", "response.incomplete"):
          parts = [part for item in event.data.response.output
                   if item.type == "message" and isinstance(item.content, list)
                   for part in item.content]
          refusal = next((p.refusal for p in parts if getattr(p, "type", None) == "refusal"), None)
          text = "".join(p.text for p in parts if getattr(p, "type", None) == "output_text")
          print(refusal if refusal is not None else text)
      elif event.data.type == "response.failed":
          print(event.data.response.error)
  ```

  ```csharp C# SDK theme={null}
  var response = await sdk.Llm.Responses.CreateAsync(body: new ResponsesCreateRequest()
  {
      Model = "auto",
      Input = ResponsesCreateRequestInput.CreateStr("Summarize this confidential roadmap."),
      Stream = true,
  });
  using var stream = response.Object
      ?? throw new InvalidOperationException("Expected a streaming response");

  CreateResponseResponseBody? item;
  while ((item = await stream.Next()) != null)
  {
      if (item.Data.ResponseFailedEvent is { } failed)
      {
          Console.Error.WriteLine(failed.Response.Error?.Message ?? "Response failed");
          break;
      }

      var result = item.Data.ResponseCompletedEvent?.Response
          ?? item.Data.ResponseIncompleteEvent?.Response;
      if (result == null) continue;

      var refusal = result.Output
          .SelectMany(output =>
              output.OutputMessage?.Content.Select(part => part.Refusal?.RefusalValue)
              ?? output.InputMessage?.Content.ArrayOfInputMessageContent?.Select(part => part.Refusal?.RefusalValue)
              ?? Enumerable.Empty<string?>())
          .FirstOrDefault(value => value != null);
      var text = string.Concat(result.Output.SelectMany(output =>
          output.OutputMessage?.Content.Select(part => part.OutputText?.Text)
          ?? output.InputMessage?.Content.ArrayOfInputMessageContent?.Select(part => part.OutputText?.Text)
          ?? Enumerable.Empty<string?>()));
      Console.WriteLine(refusal ?? text);
      break;
  }
  ```
</CodeGroup>

If you display text as it arrives, an output check may block it after the user has already seen part of the answer. Replace the displayed text with the refusal from `response.completed`. For stored responses, the retrieved response also contains the refusal.

## Usage and billing

* Input blocks prevent the answering model from running, so its response reports zero tokens.
* Classifier checks are billed separately from the answering model. Their usage is not included in the main response's `usage`. This applies to policy tests and checks that block a request.
* Output checks run after generation. If they block a response, you are still charged for the generated tokens. Enabling classifier output checks adds another model call.

Pattern-based rules run before classifiers. If a pattern rule blocks the content, no classifier call is made.

Each classifier call counts toward the configured model's rate limits. If you use that same model to answer requests, both calls share its quota. Allow for this additional usage when choosing your model and rate limits.

Policy tests with an enabled classifier return HTTP 403 if a blocking spend limit has been reached, even with `fail_open`. Tests without an enabled classifier do not make model calls or require a spend-budget check.

## Chat Completions requests

For Chat Completions, check `finish_reason` and the refusal message to identify a block.
A blocked request returns HTTP 200 with `finish_reason: "content_filter"` and the rejection message in `message.refusal` instead of `message.content`:

```json theme={null}
{
  "id": "chatcmpl-a1b2c3d4e5f6",
  "object": "chat.completion",
  "created": 1756150000,
  "model": "auto",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "refusal": "This request mentions restricted material. Please rephrase."
      },
      "finish_reason": "content_filter"
    }
  ],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0
  }
}
```

The usage rules match the Responses API: zeros for an input block, real token counts for an output-stage block.
On a streamed chat completion, an output-stage block appends a final chunk with `finish_reason: "content_filter"` and the rejection message in `delta.refusal` after the text deltas.

Cached Chat Completions are checked against your current output policy before they are returned. If blocked, the cached text is withheld, including in streams.

If a streamed Chat Completion fails, handle its error frame even if the connection closes normally.

## Next steps

* Review the [Guardrails endpoints](/api-reference/guardrails/get-effective-guardrails) in the API reference, including [update](/api-reference/guardrails/update-guardrails-policy), [delete](/api-reference/guardrails/delete-guardrails-policy), and [test](/api-reference/guardrails/test-content-against-guardrails)
* See the [authentication deep dive](/docs/authentication-deep-dive) for giving each tenant its own guardrail policy
* See [usage auditing](/docs/usage-auditing) for recording guardrail outcomes alongside the rest of your usage events
* See [generate a response](/docs/generate-a-response) for the Responses API request workflow


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.