<p align="center">
<h1 align="center">Iteradian .NET SDK</h1>
<p align="center">Official .NET client library for the Iteradian Control Plane API</p>
</p>
<p align="center">
<img src="https://img.shields.io/badge/language-C%23-239120?style=flat-square&logo=csharp" alt="C#" />
<img src="https://img.shields.io/badge/.NET-6.0%2B-512BD4?style=flat-square&logo=dotnet" alt=".NET 6+" />
<img src="https://img.shields.io/badge/version-1.0.1-green?style=flat-square" alt="Version" />
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License" />
<img src="https://img.shields.io/badge/NuGet-Iteradian-004880?style=flat-square&logo=nuget" alt="NuGet" />
<img src="https://img.shields.io/badge/async%2Fawait-✓-brightgreen?style=flat-square" alt="Async" />
<img src="https://img.shields.io/badge/nullable-enabled-blueviolet?style=flat-square" alt="Nullable" />
</p>
Features
- Fully async — all methods return
Task<T> for async/await usage
- Resource-based API — clean
client.Auth, client.Organizations, client.Endpoints access pattern
- Full API coverage — Auth, Organizations, API Keys, Endpoints, Alerts, Logs, Subscriptions, Plans, Support
- Strongly typed — all API responses deserialized into C# models with
System.Text.Json
- Automatic auth — login automatically sets the bearer token
- Dual auth — supports both Bearer tokens and
X-API-Key headers
- Factory methods —
IteradianClient.WithApiKey() for API-key-only usage
- IDisposable — proper
HttpClient lifecycle management
- Nullable enabled — full nullable reference type support
Requirements
- .NET 6.0 or later
- System.Text.Json v8.0.0 (included)
Installation
NuGet
dotnet add package Iteradian
Package Reference
<PackageReference Include="Iteradian" Version="1.0.1" />
Quick Start
using Iteradian;
using Iteradian.Models;
// Create client and login
using var client = new IteradianClient("https://api.iteradian.com/api/v1");
var tokens = await client.Auth.LoginAsync("user@example.com", "password123");
Console.WriteLine($"Logged in as {tokens.User}");
// List organizations
var orgs = await client.Organizations.ListAsync();
var orgId = orgs[0]["id"]!.GetValue<string>();
// List endpoints
var endpoints = await client.Endpoints.ListAsync(orgId);
foreach (var ep in endpoints)
{
Console.WriteLine($"{ep.Name}: {ep.Status} ({ep.Region})");
}
// Create an API key
var key = await client.ApiKeys.CreateAsync(orgId, new CreateApiKeyRequest
{
Name = "Production Key",
Environment = "live",
IpAllowlist = new List<string> { "10.0.0.0/8" },
});
Console.WriteLine($"Created key: {key.Prefix}...");
// Query logs
var logs = await client.Logs.QueryAsync(orgId, "page=1&pageSize=20");
Console.WriteLine($"Total logs: {logs.Total}");
Using API Key Authentication
using var client = IteradianClient.WithApiKey(
"https://api.iteradian.com/api/v1",
"itrd_live_abc123..."
);
var endpoints = await client.Endpoints.ListAsync("org-id");
API Reference
Resource Accessors
| Property |
Type |
Description |
client.Auth |
AuthResource |
Authentication & account management |
client.Organizations |
OrganizationsResource |
Organization CRUD & members |
client.ApiKeys |
ApiKeysResource |
API key management |
client.Endpoints |
EndpointsResource |
Endpoint & network management |
client.Alerts |
AlertsResource |
Alerts, rules & notification channels |
client.Logs |
LogsResource |
Request log querying |
client.Subscriptions |
SubscriptionsResource |
Subscription & billing |
client.Plans |
PlansResource |
Plan catalog |
client.Support |
SupportResource |
Support ticket management |
Authentication (client.Auth)
| Method |
Description |
LoginAsync(email, password, twoFactorCode?) |
Login and auto-set token |
RegisterAsync(email, password, name) |
Register a new account |
RefreshAsync(refreshToken) |
Refresh access token |
LogoutAsync() |
Invalidate session |
Enable2FAAsync(password) |
Enable 2FA → TwoFASetup |
Verify2FAAsync(code) |
Verify 2FA code |
Disable2FAAsync(password, code) |
Disable 2FA (code = current 6-digit TOTP) |
RequestMagicLinkAsync(email) |
Send magic login link |
ForgotPasswordAsync(email) |
Request password reset |
ResetPasswordAsync(token, password) |
Reset password |
Organizations (client.Organizations)
| Method |
Description |
ListAsync() |
List all orgs |
GetAsync(orgId) |
Get org by ID → Organization |
CreateAsync(name) |
Create org → Organization |
UpdateAsync(orgId, name) |
Update org → Organization |
DeleteAsync(orgId) |
Delete org |
ListMembersAsync(orgId) |
List members |
InviteMemberAsync(orgId, email, role) |
Invite member |
RemoveMemberAsync(orgId, memberId) |
Remove member |
API Keys (client.ApiKeys)
| Method |
Description |
ListAsync(orgId) |
List keys → List<ApiKey> |
CreateAsync(orgId, req) |
Create key → ApiKey |
RevokeAsync(orgId, keyId) |
Revoke key |
RotateAsync(orgId, keyId) |
Rotate key → ApiKey |
AnalyticsAsync(orgId, keyId) |
Key analytics |
Endpoints (client.Endpoints)
| Method |
Description |
GetNetworksAsync() |
List networks → List<Network> |
ListAsync(orgId) |
List endpoints → List<Endpoint> |
GetAsync(orgId, endpointId) |
Get endpoint → Endpoint |
CreateAsync(orgId, req) |
Create endpoint → Endpoint |
DeleteAsync(orgId, endpointId) |
Delete endpoint |
PauseAsync(orgId, endpointId) |
Pause endpoint |
ResumeAsync(orgId, endpointId) |
Resume endpoint |
HealthAsync(orgId, endpointId) |
Health check |
MetricsAsync(orgId, endpointId) |
Performance metrics |
Alerts (client.Alerts)
| Method |
Description |
ListAsync(orgId) |
List alerts → List<Alert> |
ListRulesAsync(orgId) |
List rules → List<AlertRule> |
CreateRuleAsync(orgId, req) |
Create rule → AlertRule |
DeleteRuleAsync(orgId, ruleId) |
Delete rule |
TestChannelAsync(orgId, channelId) |
Send a test notification through a channel |
ListChannelsAsync(orgId) |
List notification channels |
Logs (client.Logs)
| Method |
Description |
QueryAsync(orgId, queryString?) |
Query logs → LogsResponse |
GetAsync(orgId, logId) |
Get single log → RequestLog |
StatsAsync(orgId) |
Aggregated stats |
FilterOptionsAsync(orgId) |
Available filter values |
Subscriptions (client.Subscriptions)
| Method |
Description |
GetAsync(orgId) |
Get subscription → Subscription |
ChangePlanAsync(orgId, planId, acceptedTerms) |
Change plan → Subscription. acceptedTerms is the caller's confirmation that the user accepted the plan-change billing terms; the server rejects the change unless it is true. |
CancelAsync(orgId) |
Cancel subscription |
ReactivateAsync(orgId) |
Reactivate subscription |
InvoicesAsync(orgId) |
List invoices |
Plans (client.Plans)
| Method |
Description |
ListAsync() |
List all plans → List<Plan> |
GetAsync(planId) |
Get plan → Plan |
Support (client.Support)
| Method |
Description |
CreateTicketAsync(orgId, req) |
Create ticket → SupportTicket |
ListTicketsAsync(orgId) |
List tickets → List<SupportTicket> |
GetTicketAsync(orgId, ticketId) |
Get ticket → SupportTicket |
AddMessageAsync(orgId, ticketId, msg) |
Add message |
UpdateStatusAsync(orgId, ticketId, status) |
Update status |
Models
All models are defined in Models.cs with full System.Text.Json serialization attributes:
| Model |
Key Fields |
AuthTokens |
AccessToken, RefreshToken, User |
TwoFASetup |
Secret, QrCode |
Organization |
Id, Name, Slug, CreatedAt, UpdatedAt |
ApiKey |
Id, Name, Prefix, Key?, Environment, Status, CreatedAt |
Network |
Id, Slug, Name, ChainId, Type, Environment, IsActive |
Endpoint |
Id, OrganizationId, NetworkId, Region, Name, Priority, Status, IsEnabled, TimeoutMs, RetryCount, metrics fields |
RequestLog |
Id, OrganizationId, Method, Status, StatusCode?, LatencyMs?, Timestamp |
LogsResponse |
Logs, Total, Page, PageSize, TotalPages |
Alert |
Id, Severity, Status, Title, Message, CreatedAt |
AlertRule |
Id, Name, Metric, Condition, Threshold, Severity, IsEnabled, CooldownMinutes |
Plan |
Id, Name, Slug, Price, Currency, Interval, IsActive |
Subscription |
Id, OrganizationId, PlanId, Status, CancelAtPeriodEnd |
SupportTicket |
Id, Subject, Category, Priority, Status, CreatedAt |
Request Models
LoginRequest · RegisterRequest · CreateApiKeyRequest · CreateEndpointRequest · CreateAlertRuleRequest · CreateTicketRequest
Error Handling
try
{
var orgs = await client.Organizations.ListAsync();
}
catch (IteradianException ex)
{
Console.WriteLine($"Status: {ex.StatusCode}"); // HTTP status code
Console.WriteLine($"Message: {ex.Message}"); // Error message
Console.WriteLine($"Error: {ex.Error}"); // Error type identifier
}
Project Structure
sdks/csharp/
├── Iteradian.csproj # Project file (.NET 6.0, NuGet metadata)
├── IteradianClient.cs # Main client class with resource accessors (384 lines)
├── IteradianException.cs # Typed exception class
├── Models.cs # All request/response models (433 lines)
└── README.md # This file
License
MIT © Iteradian
RPC gateway, rate limits and quotas
The RPC gateway is a different host from the control-plane API:
| Surface |
Base URL |
| Control plane |
https://api.iteradian.com/api/v1 |
| RPC gateway |
https://rpc.iteradian.com |
This SDK takes both, and defaults the gateway to https://rpc.iteradian.com.
- A batch of N calls costs N against your per-second rate limit and your
monthly quota. Batching saves round trips, not allowance.
- Limits are per subscription, aggregated over every API key your
organization holds. Creating more keys does not raise them.
- The gateway returns HTTP 429 with a JSON-RPC error body:
-32005 rate limited (retry after error.data.retryAfter), -32006 monthly
quota exhausted, -32007 overage spend cap reached, -32008 the API key's
own daily limit reached (error.data = { dailyLimit, used, cost, resetsAt }).
None of the last three recover before resetsAt (the billing period roll-over,
or the next 00:00 UTC for -32008), so retrying them is wasted work.
Plans (defaults; an admin can change them): Free 25 rps / 3M per month with no
overage (requests stop at the quota, no card needed), Developer 100 rps / 5M
with overage at $6 per million, Professional 500 rps / 30M at $4.50 per million,
Dedicated per contract from $4 per million. Trials are offered on Developer
only; a trial that ends unpaid falls back to Free. See sdks/SDK_CONTRACT.md
for the full table.
WebSocket
The gateway also serves JSON-RPC over WebSocket at
wss://rpc.iteradian.com/ws/v1/{network}. This SDK does not wrap it on
purpose: connect with any WebSocket client, such as the built-in System.Net.WebSockets.ClientWebSocket. Authenticate with the
X-Api-Key header, Authorization: Bearer <key>, or ?apiKey=<key> on the
URL (browsers cannot set headers on a WebSocket).
using System.Net.WebSockets;
using System.Text;
using var ws = new ClientWebSocket();
ws.Options.SetRequestHeader("X-Api-Key", "itrd_live_...");
await ws.ConnectAsync(
new Uri("wss://rpc.iteradian.com/ws/v1/eth-mainnet"), CancellationToken.None);
var subscribe = Encoding.UTF8.GetBytes(
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"]}");
await ws.SendAsync(subscribe, WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[1024 * 1024];
while (ws.State == WebSocketState.Open)
{
var result = await ws.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
// 1012: reconnect and re-subscribe. 4003: key revoked; do not reconnect.
Console.WriteLine($"Closed: {(int?)ws.CloseStatus}");
break;
}
Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
- Each message you send is charged like an HTTP request (a batch of N costs N).
Subscription notifications pushed to you are not charged.
- A message refused by a limit gets an ordinary JSON-RPC error under that
call's
id, with the same codes as above (-32005 to -32008). The socket
stays open.
- An organization can hold
max(5, floor(concurrency / 4)) streams at once;
past that the upgrade is refused with HTTP 429. Other upgrade failures: 400
bad path, 401/403 bad key, 404 unknown network, 503 no WebSocket-capable node.
- Close code
1012 means the upstream node restarted: reconnect and
re-subscribe. 4003 means the key was revoked or the organization suspended
(keys are re-checked every 60 s): do not reconnect.
- The server pings every 30 s. Messages are limited to 1 MB.
Changelog
1.0.1
Subscriptions.ChangePlanAsync takes a required bool acceptedTerms and
sends it as acceptedTerms; the server rejects plan changes without it.
Auth.Disable2FAAsync takes the 6-digit TOTP code alongside the password,
as the server requires.
Support.UpdateStatusAsync now calls PUT /orgs/{orgId}/support/tickets/{ticketId}/status
(it sent PATCH /support/tickets/{id}, which does not exist).
- Removed
Alerts.TestRuleAsync: it posted a rule ID to the channel-test
route. Use Alerts.TestChannelAsync(orgId, channelId).
- Gateway error
-32008 (per-API-key daily limit) is never retried;
IteradianException.IsQuotaExhausted covers it and IsDailyLimitReached
identifies it.