Kirtonic SDK
Protocol reference and integration guide for the TypeScript, Python, and Go clients.
Architecture
The Kirtonic SDK is a thin client over the Kirtonic public API (/api/v1/classify and /api/v1/outcomes). It performs no model inference locally; the classifier runs in the Kirtonic control plane and the SDK is responsible for marshalling requests, propagating verdicts to the caller, and recording outcomes.
The library is distributed in three implementations (TypeScript, Python, and Go) that converge on a single API contract. Where language idioms diverge (synchronous calls in Python, idiomatichttp.RoundTrippermiddleware in Go) the SDK preserves the host platform's conventions rather than imposing a uniform shape.
┌─────────────────┐ classify ┌────────────────────┐
│ Your service │ ─────────────▶ │ Kirtonic control │
│ (SDK client) │ ◀───────────── │ plane │
└─────────────────┘ verdict │ · classifier │
│ │ · policy engine │
│ allow │ · audit log │
▼ │ · webhook fanout │
┌─────────────────┐ └────────────────────┘
│ AI provider │ ▲
│ (OpenAI / │ outcome │
│ Anthropic / │ ────────────────────────-┘
│ Bedrock / …) │
└─────────────────┘The SDK is provider-agnostic. Two integration shapes are offered:
- Thin classifier. The caller invokes
classify()before its own AI request andlogOutcome()after. Request lifecycle (streaming, retries, error handling) remains the responsibility of the caller. Use this when integrating into an existing AI client or when the host language has no first-class provider library. - Wrapped client. The SDK provides drop-in shims for the official OpenAI and Anthropic libraries (TypeScript and Python) or an
http.RoundTrippermiddleware (Go). The shim classifies the input, forwards onallow, and records the outcome on success.blockandreviewverdicts are raised as typed errors so the caller can map them directly to HTTP responses at the boundary.
Authentication
All API requests authenticate with a workspace-scoped API key carried in the Authorization header as a bearer token. Keys are issued from the workspace settings page in the dashboard; each key is tied to a single workspace and can be rotated or revoked at any time without redeploying the SDK.
Authorization: Bearer <KIRTONIC_API_KEY> Content-Type: application/json User-Agent: kirtonic-sdk-<lang>/<version>
The SDK reads the key from the constructor argument first, then falls back to the KIRTONIC_API_KEY environment variable in the Python and Go clients. The TypeScript client requires explicit construction and does not consult environment variables, to remain consistent with conventional Node.js library patterns.
Keys must not be embedded in client-side JavaScript bundles or mobile binaries. The SDK is intended for server-side integration; classifying end-user input from a browser requires a server-side proxy or the Kirtonic browser extension, which uses a different token scheme.
Endpoint reference
3.1 POST /api/v1/classify
Submits a prompt for classification. Returns the verdict synchronously. Latency depends on the classifier configured for the workspace and the deployment topology. Self-hosted deployments depend on local classifier provisioning.
POST /api/v1/classify
{
"prompt": "<string, required>",
"category": "<string, optional>",
"user_id": "<string, optional>",
"metadata": { "<string>": <any>, ... } // optional
}Fields.
prompt: The text about to be sent to an AI provider. The SDK enforces a non-empty string; the control plane enforces a maximum length of 32 KiB.category: A free-form hint describing the workload (chat,rag,agent,embedding, etc.). Used by the policy engine to scope rule matching and by the dashboard for filtering.user_id: A stable identifier for the end-user. Drives per-user audit aggregation and rate limit accounting. Hashed at rest if the workspace has pseudonymisation enabled.metadata: An arbitrary JSON object persisted alongside the decision. Common keys:provider,model,session_id,trace_id. The wrappers auto-populateproviderandmodel.
200 OK
{
"decision_id": "dec_01J9X…",
"action": "allow" | "review" | "block",
"severity": "high" | "medium" | "low" | "clean",
"category": "<classified-category>",
"reason": "<human-readable rationale>",
"model_reasoning": "<full reasoning trace>", // paid plans only
"classified_at": "2026-05-23T17:42:11.318Z"
}3.2 POST /api/v1/outcomes
Records the AI provider's response against an earlier decision. Optional but recommended: this is the call that populates the audit log with the end-to-end request and response trace required for regulatory evidence.
POST /api/v1/outcomes
{
"decision_id": "<string, required>",
"response": "<string, required>",
"finish_reason": "<string, optional>", // OpenAI / Anthropic finish_reason
"latency_ms": <integer, optional>
}204 No Content
The outcome endpoint is idempotent on decision_id: re-submitting an outcome overwrites the previous record. This avoids orphaned decisions when client-side retries fire after a partial response.
Verdict object
The verdict is the contract between the SDK and the caller. Three actions are defined; client behaviour is fixed and documented here so that integrations behave identically across languages.
action caller behaviour
───────────── ───────────────────────────────────────────────
allow Forward to the AI provider. Record outcome on
response.
review Do NOT forward. Return decision_id to the
caller (HTTP 202 + Location header is the
recommended pattern). The reviewer's decision
arrives via webhook (configured in dashboard).
block Do NOT forward. Surface verdict.reason to the
end-user (HTTP 403 is the recommended status).4.1 Severity vs action
Severity is the classifier's assessment of inherent risk; action is the policy engine's decision about what to do about it. The two are not the same. A high severity prompt under a permissive policy may yield action: allow with the severity recorded for retrospective analysis; a medium severity prompt under a strict policy may yield action: block. The action is authoritative for runtime control flow; severity is informational.
Error model
The SDK distinguishes three error classes. The wrappers raise typed errors so callers can map directly to HTTP status codes at the application boundary; the thin client surfaces all three as ordinary runtime errors.
Error class Trigger Suggested HTTP
───────────────── ────────────────────────── ───────────────
BlockedError verdict.action === 'block' 403 Forbidden
ReviewError verdict.action === 'review' 202 Accepted
(return decision_id)
RuntimeError Network / 4xx / 5xx 502 Bad Gateway
(per fallback policy)5.1 Network failure semantics
The SDK does not implement implicit retries. Transient network failures and 5xx responses surface as plain exceptions so the caller can apply its own retry budget. This is deliberate: in regulated deployments, the appropriate fallback policy depends on the workload class, and a default retry loop in the SDK would mask policy intentions. See §11 for guidance on fallback policy.
5.2 Per-error fields
BlockedError and ReviewError exposedecision_id, severity, andcategory. The full verdict is also available on.verdict (Python and Go) or via the constructor argument (TypeScript) for callers that need access to model reasoning or to forward the decision into their own observability stack.
TypeScript / JavaScript API
Distributed as @kirtonic/sdk. Node.js 18+. Browser usage is unsupported; the SDK depends on a server-side API key.
npm install @kirtonic/sdk # wrappers require peer dependencies: npm install openai @anthropic-ai/sdk
6.1 Constructor
new Kirtonic({
apiKey: string // required
endpoint?: string // default 'https://app.kirtonic.io'
timeoutMs?: number // default 8000
fetch?: typeof fetch // default globalThis.fetch
})6.2 Methods
k.classify(input: ClassifyInput) → Promise<Verdict>
k.logOutcome(input: LogOutcomeInput) → Promise<void>
type ClassifyInput = {
prompt: string
category?: string
userId?: string
metadata?: Record<string, unknown>
}
type Verdict = {
decisionId: string
action: 'allow' | 'review' | 'block'
severity: 'high' | 'medium' | 'low' | 'clean'
category: string
reason: string
modelReasoning?: string
classifiedAt: string // ISO 8601
}
type LogOutcomeInput = {
decisionId: string
response: string
finishReason?: string
latencyMs?: number
}6.3 Errors
KirtonicBlockedError and KirtonicReviewErrorare the typed errors raised by the wrappers. Both extend the standardError class and expose the constructor verdict via public fields.
6.4 Thread safety
The client is safe to share across concurrent invocations. Internally each request runs in an isolated AbortController; no state is shared between calls beyond the immutable configuration.
Python API
Distributed as kirtonic. Python 3.9+. Built onhttpx for HTTP transport; the OpenAI and Anthropic wrappers are installable as optional extras.
pip install kirtonic # wrappers: pip install "kirtonic[openai]" pip install "kirtonic[anthropic]" pip install "kirtonic[all]"
7.1 Constructor
Kirtonic(
api_key: str | None = None, # falls back to KIRTONIC_API_KEY env var
endpoint: str = "https://app.kirtonic.io",
timeout_s: float = 8.0,
client: httpx.Client | None = None,
)7.2 Methods
k.classify(
prompt: str,
category: str | None = None,
user_id: str | None = None,
metadata: Mapping[str, Any] | None = None,
) → Verdict
k.log_outcome(
decision_id: str,
response: str,
finish_reason: str | None = None,
latency_ms: int | None = None,
) → None
k.close() # close the underlying httpx.Client7.3 Async usage
The current release exposes a synchronous client only. Async support (an AsyncKirtonic variant built on httpx.AsyncClient) is on the roadmap. In the interim, asynchronous callers should delegate classify() to a thread pool viaasyncio.to_thread.
7.4 Thread safety
The default constructor instantiates a private httpx.Clientwhich is thread-safe under standard CPython concurrency models. Pass a pre-configured httpx.Client via the clientargument to share a connection pool with the rest of the application.
Go API
Distributed under github.com/MK11TON/CloudWhisper/sdk/go. Go 1.21+. The standard library supplies all transport dependencies; no external modules are required for the base client.
go get github.com/MK11TON/CloudWhisper/sdk/go
8.1 Constructor
func New(cfg Config) (*Client, error)
type Config struct {
APIKey string // required
Endpoint string // default "https://app.kirtonic.io"
Timeout time.Duration // default 8 * time.Second
HTTPClient *http.Client // optional, bring your own
}8.2 Methods
func (c *Client) Classify(ctx context.Context, in ClassifyInput) (*Verdict, error)
func (c *Client) LogOutcome(ctx context.Context, in LogOutcomeInput) error
type ClassifyInput struct {
Prompt string
Category string
UserID string
Metadata map[string]interface{}
}
type Verdict struct {
DecisionID string
Action Action // "allow" | "review" | "block"
Severity Severity // "high" | "medium" | "low" | "clean"
Category string
Reason string
ModelReasoning string // omitempty
ClassifiedAt string
}8.3 Wrapper pattern
The Go wrappers are implemented as http.RoundTrippermiddleware (kopenai.GuardedTransport,kanthropic.GuardedTransport) rather than as wrapping types. Drop the transport into the http.Client that your OpenAI or Anthropic library consumes, and every request to the relevant API path is classified before egress. This decouples the wrapper from any specific Go client library and avoids type-system coupling to upstream interfaces.
client := &http.Client{
Transport: &kopenai.GuardedTransport{
Kirtonic: k,
Category: "chat",
},
}
// Pass to whichever OpenAI Go library you use.8.4 Error handling
Blocked and review verdicts are surfaced as *kirtonic.BlockedErrorand *kirtonic.ReviewError. Use errors.Asfor type-switched handling:
var blocked *kirtonic.BlockedError
if errors.As(err, &blocked) {
return ctx.JSON(403, gin.H{
"error": blocked.Verdict.Reason,
"decision_id": blocked.Verdict.DecisionID,
})
}Wrapper integration patterns
The wrapped clients are convenience layers over the thin client. They enforce three behaviours:
- Pre-classification. The prompt is assembled from the request body and submitted to
classify()before any outbound call to the AI provider. - Verdict enforcement. On
blockorreviewthe upstream call is suppressed and the typed error is raised. Onallowthe original request is forwarded unchanged. - Fire-and-forget outcome logging. On a successful upstream response,
logOutcome()is invoked asynchronously (background promise in TypeScript, swallowed exception in Python, goroutine in Go). Failures to log do not propagate to the caller; the user-visible response is never blocked by an audit-log write.
9.1 Prompt extraction
The wrappers concatenate all system and usermessages with double-newline separators to construct the classification input. assistant messages are excluded (they have already been classified on prior turns). Tool-call payloads and image blocks are reduced to their text components.
9.2 Metadata propagation
The provider name and model id are auto-injected into the metadata object on every classify call:{ provider: 'openai', model: 'gpt-4o' }. Callers can merge additional metadata via the wrapper-specific options object without losing the auto-populated fields.
9.3 Streaming
The current wrappers cover non-streaming completion calls only. Streaming integrations should use the thin client directly: classify the input prompt, open the stream, accumulate the response, then calllogOutcome() on stream close.
Self-hosted deployment
The SDK is endpoint-agnostic. Self-hosted Kirtonic deployments configure the endpoint argument to point at an internal URL; no traffic leaves the customer network perimeter. The contract is identical to the managed control plane. The SDK does not differentiate between the two deployment models at the protocol layer.
// TypeScript
new Kirtonic({
apiKey: process.env.KIRTONIC_API_KEY!,
endpoint: 'https://kirtonic.internal.example.com',
})
// Python
Kirtonic(api_key=..., endpoint="https://kirtonic.internal.example.com")
// Go
kirtonic.New(kirtonic.Config{
APIKey: os.Getenv("KIRTONIC_API_KEY"),
Endpoint: "https://kirtonic.internal.example.com",
})In air-gapped deployments the SDK's User-Agentheader (kirtonic-sdk-<lang>/<version>) is the only identifying string emitted; no telemetry is dispatched to any external endpoint by the SDK itself. The control plane is the only recipient of API traffic.
Operations: timeouts, retries, fallback
The SDK enforces a default per-request timeout of 8 seconds. This is conservative for an interactive AI use case where theclassify call sits on the critical path of every end-user response; production deployments should reduce the timeout to 1 or 2 seconds and pair it with an explicit fallback policy.
11.1 Fallback policy
Two fallback regimes are commonly correct depending on the workload class:
- Fail-open. If the classifier is unreachable, forward the request to the AI provider with an audit-log marker indicating unclassified delivery. Appropriate for low-risk consumer workloads where uptime is more valuable than per-request governance.
- Fail-closed. If the classifier is unreachable, refuse the request and surface a
503 Service Unavailableto the caller. Appropriate for regulated workloads (financial advice, healthcare, defence) where unclassified inference is a policy violation regardless of cause.
The SDK does not select between these policies. The caller must implement the chosen fallback explicitly in the catch block. This places the policy decision in the application layer, where it is visible to auditors and version-controlled alongside the rest of the application's safety logic.
11.2 Retries
Retries are the caller's responsibility. A bounded retry budget (e.g. one retry on connect failure, none on 4xx) is appropriate for most integrations. Do not retry on BlockedError orReviewError; both are deterministic responses from the policy engine and a retry will produce the same result.
11.3 Rate limits
The control plane enforces per-workspace rate limits returned via the standard X-RateLimit-* headers. The SDK does not inspect these headers automatically; callers running at high request rates should observe the headers and back off proactively. A429 Too Many Requests response surfaces as aRuntimeError.
Versioning
The SDK follows semantic versioning. The API protocol version is carried in the URL path (/api/v1/) and is independent of the SDK version. Compatibility commitments:
- Patch. Bug fixes, dependency upgrades that do not change the public API. Safe to upgrade without reading the changelog.
- Minor. Backwards-compatible additions to the SDK surface (new methods, new optional fields on existing methods).
- Major. Breaking changes to the SDK surface. A migration guide is published with the release and the previous major version receives security patches for twelve months from the date of the breaking release.
A new API protocol version (/api/v2/) ships as an independent route. Existing v1 traffic continues to function for at least eighteen months after v2 general availability.