Official Seclai C# SDK for .NET, targeting netstandard2.0 (compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+).
Provides strongly-typed async methods for the entire Seclai API: agents, knowledge bases, memory banks, sources, content, evaluations, solutions, governance, alerts, search, AI assistants, and more.
dotnet add package Seclai.Sdkusing Seclai;
using Seclai.Models;
var client = new SeclaiClient(new SeclaiClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("SECLAI_API_KEY"),
// BaseUri defaults to https://seclai.com
});
// Run an agent
var run = await client.RunAgentAsync("sc_ag_123", new AgentRunRequest { Input = "hello" });
Console.WriteLine($"Run {run.RunId}: {run.Status}");| Option | Default | Description |
|---|---|---|
ApiKey |
SECLAI_API_KEY env var |
API key for x-api-key header authentication. |
AccessToken |
— | Static OAuth2 bearer token. |
AccessTokenProvider |
— | Async function called per request: Func<CancellationToken, Task<string>>. |
Profile |
SECLAI_PROFILE / "default" |
SSO profile name from ~/.seclai/config. |
ConfigDir |
SECLAI_CONFIG_DIR / ~/.seclai |
Config/cache directory path. |
AutoRefresh |
true |
Auto-refresh expired SSO tokens using the cached refresh token. |
AccountId |
— | Account ID sent as X-Account-Id header. |
BaseUri |
https://seclai.com |
API base URL. Falls back to SECLAI_API_URL env var. |
HttpClient |
internal | Bring your own HttpClient (useful for DI or testing). |
Timeout |
120 s | HTTP request timeout. Ignored when a custom HttpClient is provided. |
DefaultHeaders |
none | Extra headers appended to every request. |
SeclaiClient implements IDisposable. When you let it create its own HttpClient, wrap it in a using statement so the client is disposed properly:
using var client = new SeclaiClient(new SeclaiClientOptions { ApiKey = "sk_..." });If you supply your own HttpClient, the client does not dispose it — you manage its lifetime.
Credentials are resolved via a chain (first match wins):
- Explicit
ApiKeyoption - Explicit
AccessTokenoption (static string) - Explicit
AccessTokenProvideroption (async delegate, called per request) SECLAI_API_KEYenvironment variable- SSO — cached tokens from
~/.seclai/sso/cache/(always available as fallback)
// API key
using var client = new SeclaiClient(new SeclaiClientOptions { ApiKey = "sk-..." });
// Static bearer token
using var client = new SeclaiClient(new SeclaiClientOptions { AccessToken = "eyJhbGciOi..." });
// Dynamic bearer token provider (called per request)
using var client = new SeclaiClient(new SeclaiClientOptions
{
AccessTokenProvider = async ct => await GetTokenFromVaultAsync(ct)
});
// SSO profile (uses cached tokens, auto-refreshes)
using var client = new SeclaiClient(new SeclaiClientOptions { Profile = "my-profile" });
// Environment variable
// set SECLAI_API_KEY=sk-...
using var client = new SeclaiClient(new SeclaiClientOptions());SSO is the default fallback when no explicit credentials are provided. The SDK includes built-in production SSO defaults, so no configuration is needed:
npx @seclai/cli auth login # authenticate via browser — works immediatelyTo customize SSO settings (e.g. for a staging environment), use seclai configure sso
or set environment variables:
| Variable | Description | Default |
|---|---|---|
SECLAI_SSO_DOMAIN |
Cognito domain | auth.seclai.com |
SECLAI_SSO_CLIENT_ID |
Cognito app client ID | 4bgf8v9qmc5puivbaqon9n5lmr |
SECLAI_SSO_REGION |
AWS region | us-west-2 |
// CRUD
var agents = await client.ListAgentsAsync(page: 1, limit: 20);
var agent = await client.CreateAgentAsync(new CreateAgentRequest { Name = "Bot" });
var fetched = await client.GetAgentAsync(agent.Id);
var updated = await client.UpdateAgentAsync(agent.Id, new UpdateAgentRequest { Name = "Updated" });
await client.DeleteAgentAsync(agent.Id);
// Agent definitions
var def = await client.GetAgentDefinitionAsync(agent.Id);
await client.UpdateAgentDefinitionAsync(agent.Id, new UpdateAgentDefinitionRequest
{
ExpectedChangeId = def.ChangeId
});
// Export / import an agent
var exported = await client.ExportAgentAsync(agent.Id, download: false);
// Round-trip the export through a Dictionary so the import endpoints can accept it.
var payloadJson = JsonSerializer.Serialize(exported);
var payload = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(payloadJson)!;
// Validate the payload first to surface unresolved entity refs in this account.
var preview = await client.PreviewImportAgentAsync(new AgentImportPreviewRequest { AgentDefinition = payload });
var entityRemap = new Dictionary<string, string>();
foreach (var refEntry in preview.UnresolvedRefs ?? new())
{
if (refEntry.TryGetValue("ref_id", out var refId) && refId.ValueKind == JsonValueKind.String)
{
// Replace "<target-uuid>" with an id from refEntry["alternatives"]
// before calling CreateAgentAsync; empty values are rejected.
entityRemap[refId.GetString()!] = "<target-uuid>";
}
}
// Commit — EntityRemap substitutes workflow refs before save.
var imported = await client.CreateAgentAsync(new CreateAgentRequest
{
Name = "Imported",
AgentDefinition = payload,
EntityRemap = entityRemap,
});
// imported.ImportWarnings lists any items that couldn't be applied.// Start a run
var run = await client.RunAgentAsync("ag1", new AgentRunRequest { Input = "hello" });
// List runs, get details, cancel
var runs = await client.ListAgentRunsAsync("ag1", page: 1, limit: 20);
var detail = await client.GetAgentRunAsync("run1", includeStepOutputs: true);
await client.CancelAgentRunAsync("run1");
await client.DeleteAgentRunAsync("run1");
// Search across runs
var results = await client.SearchAgentRunsAsync(new AgentTraceSearchRequest { Query = "error" });
// Discover which files (if any) an agent expects before staging uploads
var refs = await client.GetAgentAttachmentReferencesAsync("ag1");
// refs.RequiresUploads reports whether the agent accepts files; refs.Agent lists the
// exact names / indexes / glob patterns a run-time upload batch must satisfy.
// Download a file attachment emitted by a step in a run (raw HttpResponseMessage).
// attachmentId is the URL-safe-base64 storage_key from run output manifests / webhooks.
using var attachment = await client.DownloadAgentRunAttachmentAsync("run1", "attachment_id");// SSE streaming — yields AgentRunEvent items as they arrive
await foreach (var evt in client.RunStreamingAgentAsync("ag1",
new AgentRunStreamRequest { Input = "hello" }))
{
Console.WriteLine($"[{evt.Event}] {evt.Run?.Status}");
}
// Wait for the final result from the SSE stream
var final = await client.RunStreamingAgentAndWaitAsync("ag1",
new AgentRunStreamRequest { Input = "hello" },
timeout: TimeSpan.FromSeconds(120));
// Poll-based alternative
var polled = await client.RunAgentAndPollAsync("ag1",
new AgentRunRequest { Input = "hello" },
pollInterval: TimeSpan.FromSeconds(2),
timeout: TimeSpan.FromMinutes(5));// CRUD
var sources = await client.ListSourcesAsync(page: 1, limit: 20);
var source = await client.CreateSourceAsync(new CreateSourceRequest { Name = "Docs", SourceType = "upload" });
await client.DeleteSourceAsync(source.Id);
// Upload file (max 200 MiB; MIME type auto-inferred from file extension)
var bytes = File.ReadAllBytes("./doc.pdf");
var upload = await client.UploadFileToSourceAsync("sc1", bytes, "doc.pdf",
title: "My Doc", mimeType: "application/pdf");
// Stream-based upload — avoids loading the entire file into memory
await using var stream = File.OpenRead("./large.pdf");
var upload2 = await client.UploadFileToSourceAsync("sc1", stream, "large.pdf",
title: "Large Doc");
// Inline text upload
await client.UploadInlineTextToSourceAsync("sc1", new InlineTextUploadRequest
{
Text = "Hello world", Title = "greeting"
});
// Content management
var content = await client.GetContentDetailAsync("cv1");
await client.DeleteContentAsync("cv1");
var embeddings = await client.ListContentEmbeddingsAsync("cv1", page: 1, limit: 50);var estimate = await client.EstimateSourceExportAsync("s1",
new EstimateExportRequest { Format = "csv" });
var export = await client.CreateSourceExportAsync("s1",
new CreateExportRequest { Format = "csv" });
var status = await client.GetSourceExportAsync("s1", export.Id);
using var response = await client.DownloadSourceExportAsync("s1", export.Id);
await client.DeleteSourceExportAsync("s1", export.Id);var kbs = await client.ListKnowledgeBasesAsync(sort: "created_at");
var kb = await client.CreateKnowledgeBaseAsync(new CreateKnowledgeBaseRequest { Name = "KB" });
await client.UpdateKnowledgeBaseAsync(kb.Id, new UpdateKnowledgeBaseRequest { Name = "Renamed" });
await client.DeleteKnowledgeBaseAsync(kb.Id);var banks = await client.ListMemoryBanksAsync();
var bank = await client.CreateMemoryBankAsync(new CreateMemoryBankRequest
{
Name = "Chat Memory", Type = "conversation"
});
var stats = await client.GetMemoryBankStatsAsync(bank.Id); // JsonElement
var templates = await client.ListMemoryBankTemplatesAsync(); // JsonElement
await client.CompactMemoryBankAsync(bank.Id);
await client.DeleteMemoryBankAsync(bank.Id);
// AI-assisted configuration
var config = await client.GenerateMemoryBankConfigAsync(
new MemoryBankAiAssistantRequest { UserInput = "build a chat memory" });// Criteria
var criteria = await client.ListEvaluationCriteriaAsync("ag1");
var created = await client.CreateEvaluationCriteriaAsync("ag1",
new CreateEvaluationCriteriaRequest { StepId = "s1" });
var summary = await client.GetEvaluationCriteriaSummaryAsync(created.Id);
await client.DeleteEvaluationCriteriaAsync(created.Id);
// Results
var results = await client.ListAgentEvaluationResultsAsync("ag1", page: 1);
var nonManual = await client.GetNonManualEvaluationSummaryAsync();
// Test before creating
var test = await client.TestDraftEvaluationAsync("ag1",
new TestDraftEvaluationRequest { AgentInput = "test input" });var solutions = await client.ListSolutionsAsync();
var sol = await client.CreateSolutionAsync(new CreateSolutionRequest { Name = "My Solution" });
// Link/unlink resources
await client.LinkAgentsToSolutionAsync(sol.Id, new LinkResourcesRequest { Ids = new List<string> { "ag1" } });
await client.LinkSourceConnectionsToSolutionAsync(sol.Id, new LinkResourcesRequest { Ids = new List<string> { "s1" } });
await client.LinkKnowledgeBasesToSolutionAsync(sol.Id, new LinkResourcesRequest { Ids = new List<string> { "kb1" } });
// AI-assisted solution planning
var plan = await client.GenerateSolutionAiPlanAsync(sol.Id,
new AiAssistantGenerateRequest { UserInput = "add a FAQ bot" });
var accepted = await client.AcceptSolutionAiPlanAsync(
sol.Id, plan.ConversationId!, new AiAssistantAcceptRequest());// AI-assisted governance
var gov = await client.GenerateGovernanceAiPlanAsync(
new GovernanceAiAssistantRequest { UserInput = "create a content safety policy" });
var conversations = await client.ListGovernanceAiConversationsAsync();var alerts = await client.ListAlertsAsync(status: "active"); // JsonElement
var alert = await client.GetAlertAsync("al1"); // JsonElement
await client.ChangeAlertStatusAsync("al1", new ChangeStatusRequest { Status = "resolved" });
await client.AddAlertCommentAsync("al1", new AddCommentRequest { Body = "Fixed" });
// Alert configs
var configs = await client.ListAlertConfigsAsync(); // JsonElement
await client.DeleteAlertConfigAsync("ac1");
// Organization preferences
var prefs = await client.ListOrganizationAlertPreferencesAsync();var alerts = await client.ListModelAlertsAsync(); // JsonElement
await client.MarkModelAlertReadAsync("ma1");
await client.MarkAllModelAlertsReadAsync();
var recs = await client.GetModelRecommendationsAsync("model1"); // JsonElement
// Model playground experiments
var experiment = await client.CreateExperimentAsync(
new PlaygroundCreateRequest { Prompt = "...", ModelIds = new List<string> { "model1" } });
var experiments = await client.ListExperimentsAsync(); // JsonElement
var detail = await client.GetExperimentAsync("exp1"); // JsonElement
await client.CancelExperimentAsync("exp1");
await client.DeleteExperimentAsync("exp1"); // soft-delete, preserves audit historyvar results = await client.SearchAsync(query: "my bot", entityType: "agent"); // JsonElement// Knowledge base assistant
var plan = await client.AiAssistantKnowledgeBaseAsync(
new AiAssistantGenerateRequest { UserInput = "create a docs knowledge base" });
await client.AcceptAiAssistantPlanAsync(
plan.ConversationId!, new AiAssistantAcceptRequest());
// Source and memory bank assistants
await client.AiAssistantSourceAsync(new AiAssistantGenerateRequest { UserInput = "plan" });
await client.AiAssistantMemoryBankAsync(new MemoryBankAiAssistantRequest { UserInput = "plan" });
// Feedback
await client.SubmitAiFeedbackAsync(new AiAssistantFeedbackRequest
{
Feature = "chat", Rating = "positive"
});try
{
await client.GetAgentAsync("nonexistent");
}
catch (ApiValidationException ex)
{
// 422 — validation errors
Console.WriteLine(ex.Message);
foreach (var err in ex.Errors)
Console.WriteLine($" {err.Loc}: {err.Msg}");
}
catch (ApiException ex)
{
// Other HTTP errors (401, 403, 404, 500, …)
Console.WriteLine($"{ex.StatusCode}: {ex.Message}");
}Full API reference is available on the Seclai docs site.
To generate docs locally:
make docsTests target net10.0 (requires the .NET 10 SDK).
dotnet testThis repo uses DocFX to generate and publish API docs to GitHub Pages.
Pages structure:
/latest/is updated on each release build frommain/<version>/is published for each release tag (e.g.1.2.3)
Build docs locally:
dotnet tool restore
rm -rf build/docs build/api
dotnet tool run docfx docfx.json
# static site output (upload `build/docs` to any static host, incl. GitHub Pages)
open build/docs/index.html
# if your browser/preview blocks JS/CSS when using file://, serve it locally:
# cd build/docs && python3 -m http.server 8000Or using the Makefile:
make docs
make docs-serve