Skip to content

Controller API

The Controller API is the main integration point for LLM applications. It accepts message conversations and returns analysis results from the configured modules.

A live OpenAPI reference is available at api.link2.ai/docs.


Integration modes

As a separate service

Your application calls the LLM as usual and sends a copy of the messages to the LINK2AI.Trust Controller API in a separate request. Results are available synchronously or stored for later review. See the Analysis API.

Deployment as separate service

As an OpenAI proxy

LINK2AI.Trust acts as a drop-in replacement for the OpenAI base URL. It forwards requests to OpenAI, analyzes both the input and output, and returns the LLM response alongside analysis results. Modules configured in blocking mode can reject requests before they ever reach the LLM. See the OpenAI Proxy API.

Deployment as LLM proxy


Authentication

Every request to the Controller API must include a valid API key. Keys are created in the Portal under Settings → API keys and are scoped to a project.

Two authentication methods are supported:

Bearer token (standard — used with the Analysis API):

Authorization: Bearer l2-<key_id>-<secret>

Custom header (used alongside OpenAI's Authorization header in proxy mode):

LINK2AI-API-KEY: l2-<key_id>-<secret>


Analysis API

Use the Analysis API when your application calls the LLM directly and you want to send the messages to LINK2AI.Trust separately.

POST /v1/analyzeInput

Analyzes the user-facing side of a conversation. Runs input-focused modules (malicious_intent, off_topic, guardrails, harmful_content, pii, secrets, usage).

Call this before sending the request to the LLM.

Headers

Header Value
Authorization Bearer <api_key>
Content-Type application/json

Request body

{
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "How do I reset my password?" }
  ],
  "model": "gpt-4o-mini",
  "configuration": { ... },
  "cache": {
    "no_cache": false,
    "no_store": false
  }
}
Field Type Required Description
messages array Yes Full conversation in OpenAI message format (system, user, assistant, tool roles).
model string Yes The model identifier used for the LLM call (e.g. gpt-4o-mini).
configuration object No Per-module configuration that replaces the project's saved configuration for this call; only the modules it lists run. See Overriding configuration per request. If omitted, the project's saved configuration is used.
cache.no_cache bool No Skip reading from the analysis cache. Default: false.
cache.no_store bool No Skip writing results to the analysis cache. Default: false.

Response body

{
  "results": {
    "malicious_intent": {
      "duration_ms": 45.2,
      "status": "success",
      "secure": true,
      "result": {
        "message_labels": [
          { "label": "SAFE", "score": 0.998 }
        ]
      },
      "cache_hit": null,
      "error_message": null
    },
    "usage": {
      "duration_ms": 1.1,
      "status": "success",
      "secure": true,
      "result": {
        "input_tokens": 24,
        "output_tokens": null,
        "messages": [ ... ],
        "model": "gpt-4o-mini"
      },
      "cache_hit": null,
      "error_message": null
    }
  },
  "module_errors": false,
  "secure": true
}
Field Type Description
results object Map of module ID → ModuleResult. Only modules in Included in Response or Blocking mode appear here. Modules in Monitoring only mode run in the background and are never in this map.
module_errors bool true if any module returned a failure status.
secure bool false if any security module flagged the interaction.

ModuleResult fields

Field Type Description
status "success" | "failure" Whether the module ran without error.
duration_ms float | null Module execution time in milliseconds.
secure bool | null false if this module flagged the interaction. Always true for non-security modules (e.g. usage). null if the module could not run.
result object | null Module-specific result payload. See Modules. null on failure.
cache_hit object | null Present if the result was served from cache. Contains event_id (string) and similarity (float | null).
error_message string | null Error details when status is "failure".
units int | null Number of priced operations the module performed, e.g. one per guardrail. Determines the credits charged.

Status codes

Code Meaning
200 Analysis completed.
400 No modules are configured for this project.
401 Missing or invalid API key.
402 The organization has no credits left. The request is not analyzed. See Running out of credits.

POST /v1/analyzeOutput

Analyzes the LLM response side of a conversation. Runs output-focused modules (adherence, guardrails, harmful_content, pii, secrets, usage).

Call this after receiving the LLM response. Include the assistant message in the messages array.

Request and response structure are identical to /v1/analyzeInput.


Python example

import os
import httpx

API_KEY = os.environ["LINK2AI_API_KEY"]
BASE_URL = "https://api.link2.ai"

headers = {"Authorization": f"Bearer {API_KEY}"}

messages = [
    {"role": "system", "content": "You are a helpful assistant for a car rental company."},
    {"role": "user", "content": "What cars do you have available?"},
]

# Analyze input before calling the LLM
input_analysis = httpx.post(
    f"{BASE_URL}/v1/analyzeInput",
    json={"messages": messages, "model": "gpt-4o-mini"},
    headers=headers,
).json()

if not input_analysis["secure"]:
    raise ValueError("Input blocked by security modules")

# ... call the LLM, get assistant_response ...

# Analyze output after receiving the LLM response
messages_with_response = messages + [{"role": "assistant", "content": assistant_response}]

output_analysis = httpx.post(
    f"{BASE_URL}/v1/analyzeOutput",
    json={"messages": messages_with_response, "model": "gpt-4o-mini"},
    headers=headers,
).json()

OpenAI Proxy API

The proxy API is a drop-in replacement for OpenAI's API. It forwards your request to OpenAI, analyzes both the input and output, and returns the normal OpenAI response with input_analysis and output_analysis fields appended.

Modules configured in blocking mode will reject the request before it reaches OpenAI and return HTTP 422.

POST /openai/chat/completions

Proxies to the OpenAI Chat Completions API.

Headers

Header Value
Authorization Bearer <openai_api_key>
LINK2AI-API-KEY <link2ai_api_key>
Content-Type application/json

The request body is passed through to OpenAI unchanged. The response is the standard OpenAI ChatCompletion object with two additional fields:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "choices": [ ... ],
  "usage": { ... },
  "input_analysis": {
    "results": { ... },
    "module_errors": false,
    "secure": true
  },
  "output_analysis": {
    "results": { ... },
    "module_errors": false,
    "secure": true
  }
}

Streaming

Streaming requests ("stream": true) are supported. When streaming, only input_analysis is included (attached to the first SSE chunk). Output analysis is not available in streaming mode.

Status codes

Code Meaning
200 LLM call succeeded and analysis is included.
422 A blocking module flagged the input. The response body is the AnalyzeResponse for the blocked analysis. No LLM response is returned.
401 Missing or invalid LINK2AI API key.
402 The organization has no credits left. The request is not forwarded. See Running out of credits.

POST /openai/responses

Proxies to the OpenAI Responses API. Same authentication, extension fields, and blocking behavior as /openai/chat/completions.

Python example (OpenAI SDK)

The OpenAI Python SDK works without any changes — just point base_url at the Controller and add the LINK2AI-API-KEY header.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.link2.ai/openai",
    api_key=os.environ["OPENAI_API_KEY"],
    default_headers={"LINK2AI-API-KEY": os.environ["LINK2AI_API_KEY"]},
)

response = client.chat.completions.create(
    messages=[
        {"role": "system", "content": "Always speak like a pirate."},
        {"role": "user", "content": "Hello!"},
    ],
    model="gpt-4o-mini",
)

print(response.choices[0].message.content)

# Access analysis results
input_results = response.input_analysis["results"]
output_results = response.output_analysis["results"]

# Check if a guardrail was violated
if not response.output_analysis["secure"]:
    print("Output blocked by a security module")

Running out of credits

When the organization has used up its credits, the analysis and proxy endpoints answer 402 Payment Required instead of analyzing the request partially:

{
  "detail": {
    "message": "included usage is exhausted",
    "reason": "included usage is exhausted",
    "code": "usage_exhausted"
  }
}
code Meaning
usage_exhausted The credits of the current period and any extra credits are used up.
no_usage The organization's plan includes no credits.

Requests are admitted again as soon as credits are available, for example in the next period or after an organization owner changes the plan.


Service endpoints

GET /status

Returns the health and availability of all configured modules. Useful for monitoring and debugging.

{
  "modules": {
    "malicious_intent": {
      "module_id": "malicious_intent",
      "available": true,
      "response_time": 38.5
    },
    "adherence": {
      "module_id": "adherence",
      "available": false,
      "response_time": null
    }
  },
  "time": "2026-05-04T12:00:00Z"
}

response_time is the measured latency in milliseconds from a health-check probe, or null if the module could not be reached.

GET /info

Returns basic service metadata.

{
  "controller_id": "abc123",
  "loglevel": "INFO",
  "version": "2.3.0"
}