12. C# and Java REST Integration
When To Use This Integration Path
Use this path when your application stack is primarily:
- C#
- Java
- Spring Boot
- ASP.NET
- a custom orchestration layer that does not use the official JS, Python, or Vercel SDK wrappers
You do not need to wait for a native SDK to integrate AgentID. The public Data Plane API is enough to enforce guardrails and persist runtime telemetry.
What You Get Without An SDK
Direct REST integration still gives you the core backend contract:
/api/v1/guardremains the enforcement authority- prompts can still be blocked before provider billing
- strict PII leakage checks still run on the backend
transformed_inputcan still be returned for prompt rewriting/api/v1/ingeststill records telemetry, cost, and business metrics/api/v1/ingest/finalizestill closes the lifecycle row
What You Must Implement Yourself
Without an official SDK, you must own the orchestration that the wrappers normally provide:
- generate and persist a stable
client_event_id - call
/api/v1/guardbefore the provider call - stop execution when
allowed=false - apply
transformed_inputbefore calling the upstream LLM - call
/api/v1/ingestafter the model returns - optionally call
/api/v1/ingest/finalize - extract text yourself for multimodal requests
- implement any local client-side masking or fallback logic you want
Practical rule:
prompt_for_provider = guardResponse.transformed_input ?? original_input
If you skip this step, backend rewrite/masking decisions will not reach the actual provider call.
Recommended Integration Point
Do not spread AgentID calls across controllers or frontend code.
Put the integration in the backend layer that already owns model execution, for example:
AiGatewayModelExecutionServiceLlmOrchestrator- a shared Spring
Service - a shared ASP.NET application service
That gives you one place to enforce:
- guard-before-execute
- request correlation
- ingest telemetry
- retry behavior
Runtime Flow
The recommended direct integration lifecycle is:
1. Generate client_event_id
2. Optional: GET /api/v1/agent/config
3. POST /api/v1/guard
4. If denied: stop
5. If allowed: use transformed_input when present
6. Call the LLM provider
7. POST /api/v1/ingest
8. Optional: POST /api/v1/ingest/finalize
Authentication
Use one of these headers on every request:
x-agentid-api-key: sk_live_...
Content-Type: application/json
or:
Authorization: Bearer sk_live_...
Content-Type: application/json
x-agentid-api-key is the preferred server-side header.
Correlation And Idempotence
Use these identifiers consistently:
client_event_id: one stable UUID for the full request lifecycleguard_event_id: returned by/api/v1/guard, pass it into ingest metadataevent_id: idempotency key for/api/v1/ingest
Recommended implementation:
- set
event_id == client_event_id - reuse the same
event_idon safe network retries to/api/v1/ingest
Guard Request Example
{
"system_id": "cf84f936-95bc-45a4-bf98-9a1a16bf29c9",
"input": "Summarize this claims review for the customer.",
"model": "gpt-4o-mini",
"user_id": "user-123",
"client_event_id": "0467f78f-5821-4827-a09b-d59a9b08866c",
"request_identity": {
"user_agent": "Continero-App/1.0",
"tenant_id": "acme-prod"
},
"client_capabilities": {
"framework": "custom-java"
}
}
Guard Response Fields To Honor
At minimum, your code should read:
allowedreasonclient_event_idguard_event_idtransformed_inputshadow_modesimulated_decision
Behavior:
- if
allowed=false, do not call the provider - if
allowed=trueandtransformed_inputexists, call the provider withtransformed_input
Ingest Request Example
{
"event_id": "0467f78f-5821-4827-a09b-d59a9b08866c",
"system_id": "cf84f936-95bc-45a4-bf98-9a1a16bf29c9",
"input": "Summarize this claims review for the customer.",
"output": "Here is a concise summary of the claims review.",
"model": "gpt-4o-mini",
"usage": {
"prompt_tokens": 33,
"completion_tokens": 19,
"total_tokens": 52
},
"tokens": {
"input": 33,
"output": 19,
"total": 52
},
"latency": 1450,
"event_type": "complete",
"severity": "info",
"timestamp": "2026-03-24T11:30:00Z",
"user_id": "user-123",
"metadata": {
"client_event_id": "0467f78f-5821-4827-a09b-d59a9b08866c",
"guard_event_id": "7a7394af-77a9-4d32-a714-ad7af7ab86f3"
}
}
Finalize Request Example
{
"client_event_id": "0467f78f-5821-4827-a09b-d59a9b08866c",
"system_id": "cf84f936-95bc-45a4-bf98-9a1a16bf29c9",
"sdk_ingest_ms": 83
}
PII, Masking, And Rewrite Semantics
There are two different layers to understand:
Backend Guard Enforcement
This works without an SDK:
- strict PII leakage detection
- hard blocks from
/api/v1/guard - backend prompt rewrite via
transformed_input
SDK-Local Client Behavior
This is not automatic without an SDK:
- local client-side masking before remote guard
- local fail-close fallback logic when backend guard is unreachable
- automatic prompt rewriting inside provider wrappers
Direct REST customers therefore still get backend protection, but they must implement the client-side wrapper behavior themselves if they want near-SDK parity.
Multimodal Requests
Direct REST integrations should follow the same contract as the official SDKs:
- send text-bearing content to
/api/v1/guard - pass binary attachments through to the provider unchanged
- do not expect the guard hot path to OCR or vision-scan files
If your application accepts image or file attachments, extract the user text for /guard and preserve the original attachment payload for the upstream provider call.
Streaming Guidance
For streaming providers:
- call
/api/v1/guardbefore starting the stream - start the provider stream only if allowed
- collect final usage and latency after stream completion
- call
/api/v1/ingest - optionally call
/api/v1/ingest/finalize
The direct REST path works for streaming, but you must own the end-of-stream telemetry orchestration yourself.
C# Pseudo-Code
public async Task<string> GenerateWithAgentIdAsync(
string systemId,
string userPrompt,
string model,
string userId)
{
var clientEventId = Guid.NewGuid().ToString();
var eventId = clientEventId;
var guard = await AgentIdGuardAsync(new
{
system_id = systemId,
input = userPrompt,
model = model,
user_id = userId,
client_event_id = clientEventId,
client_capabilities = new
{
framework = "csharp-custom"
}
});
if (!guard.allowed)
{
throw new SecurityException($"AgentID blocked request: {guard.reason}");
}
var promptForProvider = !string.IsNullOrWhiteSpace(guard.transformed_input)
? guard.transformed_input
: userPrompt;
var llmResult = await CallProviderAsync(promptForProvider, model);
await AgentIdIngestAsync(new
{
event_id = eventId,
system_id = systemId,
input = promptForProvider,
output = llmResult.Text,
model = model,
usage = new
{
prompt_tokens = llmResult.PromptTokens,
completion_tokens = llmResult.CompletionTokens,
total_tokens = llmResult.TotalTokens
},
tokens = new
{
input = llmResult.PromptTokens,
output = llmResult.CompletionTokens,
total = llmResult.TotalTokens
},
latency = llmResult.LatencyMs,
event_type = "complete",
severity = "info",
timestamp = DateTime.UtcNow.ToString("O"),
user_id = userId,
metadata = new
{
client_event_id = clientEventId,
guard_event_id = guard.guard_event_id
}
});
await AgentIdFinalizeAsync(new
{
client_event_id = clientEventId,
system_id = systemId,
sdk_ingest_ms = 50
});
return llmResult.Text;
}
Java Pseudo-Code
public String generateWithAgentId(
String systemId,
String userPrompt,
String model,
String userId) throws Exception {
String clientEventId = UUID.randomUUID().toString();
String eventId = clientEventId;
Map<String, Object> guardRequest = new HashMap<>();
guardRequest.put("system_id", systemId);
guardRequest.put("input", userPrompt);
guardRequest.put("model", model);
guardRequest.put("user_id", userId);
guardRequest.put("client_event_id", clientEventId);
Map<String, Object> clientCapabilities = new HashMap<>();
clientCapabilities.put("framework", "java-custom");
guardRequest.put("client_capabilities", clientCapabilities);
GuardResponse guard = agentIdGuard(guardRequest);
if (!guard.allowed()) {
throw new RuntimeException("AgentID blocked request: " + guard.reason());
}
String promptForProvider = (guard.transformedInput() != null && !guard.transformedInput().isBlank())
? guard.transformedInput()
: userPrompt;
ProviderResult llm = callProvider(promptForProvider, model);
Map<String, Object> ingestRequest = new HashMap<>();
ingestRequest.put("event_id", eventId);
ingestRequest.put("system_id", systemId);
ingestRequest.put("input", promptForProvider);
ingestRequest.put("output", llm.text());
ingestRequest.put("model", model);
ingestRequest.put("latency", llm.latencyMs());
ingestRequest.put("event_type", "complete");
ingestRequest.put("severity", "info");
ingestRequest.put("timestamp", Instant.now().toString());
ingestRequest.put("user_id", userId);
Map<String, Object> usage = new HashMap<>();
usage.put("prompt_tokens", llm.promptTokens());
usage.put("completion_tokens", llm.completionTokens());
usage.put("total_tokens", llm.totalTokens());
ingestRequest.put("usage", usage);
Map<String, Object> tokens = new HashMap<>();
tokens.put("input", llm.promptTokens());
tokens.put("output", llm.completionTokens());
tokens.put("total", llm.totalTokens());
ingestRequest.put("tokens", tokens);
Map<String, Object> metadata = new HashMap<>();
metadata.put("client_event_id", clientEventId);
metadata.put("guard_event_id", guard.guardEventId());
ingestRequest.put("metadata", metadata);
agentIdIngest(ingestRequest);
Map<String, Object> finalizeRequest = new HashMap<>();
finalizeRequest.put("client_event_id", clientEventId);
finalizeRequest.put("system_id", systemId);
finalizeRequest.put("sdk_ingest_ms", 50);
agentIdFinalize(finalizeRequest);
return llm.text();
}