Skip to main content
CID222Documentation

Chat API

Send messages through CID222's safety pipeline to any supported LLM provider. Input and output are filtered for PII, toxicity, and prompt injection in real time, and the response is streamed back as Server-Sent Events.

Create Chat Completion

POST /chat/completions

Streams a model completion through the input and output safety filters. The response is always Server-Sent Events (SSE) — there is no non-streaming JSON body.

Authentication

Authenticate with a tenant API key (Authorization: Bearer cid_key_...) or a user JWT.

Request Body

ParameterTypeRequiredDescription
modelstringYesModel ID enabled for your tenant (e.g. "gpt-4o", "claude-sonnet-4-6")
messagesarrayYesConversation messages. content is a string or an array of content parts
providerstringNoopenai, azure_openai, anthropic, google. Inferred from the model when omitted
streambooleanNoServer-Sent Events streaming. Defaults to true for this endpoint
temperaturenumberNoSampling temperature, 0–2
max_tokensnumberNoMaximum tokens in the response
top_pnumberNoNucleus sampling, 0–1
session_idstringNoAttach this turn to a conversation session (UUID)
contextsstring[]NoRAG passages used for grounding / hallucination checks
documentsarrayNoDocuments (pdf/docx/txt/csv, base64) auto-parsed, redacted, and added as context
content_filterstringNoFilter nickname overriding the tenant default

Message Format

// Plain text
{ "role": "user", "content": "Message content" }
// Multimodal: text + image
{
"role": "user",
"content": [
{ "type": "text", "text": "What's in this image?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,...", "detail": "auto" } }
]
}
// Text + document
{
"role": "user",
"content": [
{ "type": "text", "text": "Summarize this contract" },
{ "type": "document", "document": { "data": "JVBERi0xLjQK...", "type": "pdf", "name": "contract.pdf" } }
]
}

Images & documents are scanned too

Image and document content parts are scanned and redacted for PII before they reach the model, exactly like text.

Example Request

cURL
curl -N -X POST https://api.cid222.ai/chat/completions \
-H "Authorization: Bearer cid_key_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful customer service agent."
},
{
"role": "user",
"content": "My name is John Smith and my email is john@example.com"
}
]
}'

Streaming Response

The response is a text/event-stream. Each event is a JSON object on a data: line. Model text arrives in a top-level content field; the gateway buffers the LLM output for filtering and sends the complete answer as a single filtered-response event (finish_reason: stop). The stream ends with a token_usage event followed by data: [DONE].

SSE Stream
data: {"type":"user_message_processed","content":"My name is [NAME] and my email is [EMAIL]","original_had_issues":true,"entities_count":2,"masked_count":2,"flagged_count":0,"action_taken":"masked","filter_duration_ms":142}
data: {"id":"filtered-response","content":"Hello! How can I help you today?","finish_reason":"stop","filtered":false,"entities_masked":0}
data: {"type":"token_usage","usage":{"prompt_tokens":320,"completion_tokens":12,"total_tokens":332}}
data: [DONE]

Event Types

The stream interleaves model output with safety-pipeline events:

EventDescription
Text chunk (no type)Model text in a top-level content string: {"id":"...","object":"chat.completion.chunk","created":...,"model":"...","content":"Hello","finish_reason":null}
id: "filtered-response"The gateway buffers the model output for filtering, then sends the complete filtered answer as one event: content (full text), finish_reason: "stop", filtered, entities_masked. Treat this as the final answer
user_message_processedPII or safety match in the prompt was masked or flagged before sending (content is the masked prompt, plus masked_count, flagged_count, action_taken)
content_rejectedPrompt blocked (e.g. jailbreak). Carries error and entities; not forwarded to the provider
output_content_rejectedResponse blocked (severity: "critical", reason, actions_applied)
output_content_warningResponse contains flagged content (severity: "warning", message, actions_applied)
token_usageToken usage (usage.prompt_tokens, usage.completion_tokens, usage.total_tokens)
documents_processedAttached documents[] were parsed and filtered (count, summaries, processing_time_ms)
hallucinationBackground hallucination check flagged the response (RAG contexts flows)

Blocked Content

If the input or output violates policy, the pipeline emits a content_rejected or output_content_rejected event instead of model text and ends the stream. Blocked input is never sent to the provider, so no tokens are billed.

Blocked Input
data: {"error":"Content contains prohibited information","type":"content_rejected","entities":[{"type":"jailbreak","action":"reject","confidence":0.96}]}
data: [DONE]

Supported Models

The model parameter accepts any model enabled for your tenant. Call GET /models to list what is available.

ProviderModels
OpenAIgpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo
Anthropicclaude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5
Googlegemini-2.5-pro, gemini-2.5-flash
Azure OpenAIgpt-4o, gpt-4o-mini, gpt-4-turbo (your deployment names)

Error Codes

CodeDescription
400Invalid request body or parameters
401Invalid or missing authentication
403Forbidden (insufficient role or scope)
429Rate limit exceeded
500Internal server error
503Provider or ML service unavailable
Content blocks are delivered as SSE events, not as a 403 — read the event stream to detect content_rejected and output_content_rejected.