← Back to SDKs C#
.NET —
C#
.NET — Airlock.Gateway.Sdk
A .NET 8+ client SDK for the Airlock Integrations Gateway API.
Installation
dotnet add package Airlock.Gateway.SdkQuick Start
With Bearer Token
using Airlock.Gateway.Sdk;
using Airlock.Gateway.Sdk.Models;
var client = new AirlockGatewayClient("https://igw.airlocks.io", bearerToken: "your-jwt-token");With Enforcer App Credentials
var client = new AirlockGatewayClient(
"https://igw.airlocks.io",
clientId: "your-client-id",
clientSecret: "your-client-secret");Authentication
Personal Access Token (PAT)
PAT is the recommended authentication for user-scoped operations. Sent via the X-PAT header.
// After obtaining a PAT from the mobile app (Settings → Access Tokens)
client.SetPat("airlock_pat_...");
// Clear PAT when no longer needed
client.SetPat(null);Dual Auth (SetBearerToken)
After creating a client with credentials, set a user's Bearer token for user-scoped operations.
// After user login (Device Auth Grant or Auth Code + PKCE)
client.SetBearerToken(accessToken);Authentication by Enforcer App Kind
| Kind | OAuth2 Flow | SDK Methods |
|---|---|---|
| Agent / Desktop / VsCode | Device Auth Grant (RFC 8628) | LoginAsync(onUserCode) |
| Web | Auth Code + PKCE (RFC 7636) | LoginWithAuthCodeAsync or GetAuthorizationUrlAsync + ExchangeCodeAsync |
| Mobile | Auth Code + PKCE (RFC 7636) | GetAuthorizationUrlAsync + ExchangeCodeAsync |
Gateway Client API
| Method | Description |
|---|---|
EchoAsync() | Gateway discovery / health |
SetPat(pat) | Set Personal Access Token (X-PAT header) |
SetBearerToken(token) | Set Bearer token for user-scoped operations |
CheckConsentAsync() | Check user consent for this enforcer app |
SubmitArtifactAsync(request) | Submit artifact for approval |
EncryptAndSubmitArtifactAsync(request) | Encrypt plaintext and submit as artifact |
GetExchangeStatusAsync(requestId) | Get exchange status |
WaitForDecisionAsync(requestId, timeout) | Long-poll for decision |
SubmitAckAsync(msgId, requestId?) | Acknowledge decision delivery (POST /v1/acks, fire-and-forget) |
VerifyDecision(decision, hash, publicKey) | Verify decision signature and binding |
WithdrawExchangeAsync(requestId) | Withdraw pending exchange |
InitiatePairingAsync(request) | Start pairing session |
ClaimPairingAsync(request) | Claim a pre-generated pairing code |
GetPairingStatusAsync(nonce) | Poll pairing status |
RevokePairingAsync(routingToken) | Revoke a pairing |
SendHeartbeatAsync(request) | Presence heartbeat |
GetEffectiveDndPoliciesAsync(enforcerId, workspaceId, sessionId) | Fetch effective DND policies |
Auth Client (AirlockAuthClient)
| Method | Purpose |
|---|---|
DiscoverAsync | Load OIDC discovery |
LoginAsync | Device code login |
LoginWithAuthCodeAsync | Auth code + PKCE (local callback) |
GetAuthorizationUrlAsync, ExchangeCodeAsync | Auth code + PKCE (manual redirect) |
RefreshTokenAsync, GetAccessTokenAsync | Refresh / access token |
LogoutAsync | Sign out (revoke) |
Pairing
Standard Pairing (Enforcer-Initiated)
// 1. Initiate a pairing session
var pairing = await client.InitiatePairingAsync(new PairingInitiateRequest
{
EnforcerId = "my-enforcer",
WorkspaceName = "my-project",
X25519PublicKey = myPublicKey, // For E2E encryption key exchange
});
// 2. Display pairing code to user (or show QR)
Console.WriteLine($"Pairing code: {pairing.PairingCode}");
// 3. Poll for approval from the mobile app
var status = await client.GetPairingStatusAsync(pairing.Nonce);
if (status.State == "Completed")
{
var routingToken = status.RoutingToken; // Save for future requests
}Pre-Generated Code Pairing (Approver-Initiated)
// Claim a pre-generated code from the mobile app
var claim = await client.ClaimPairingAsync(new PairingClaimRequest
{
Code = "ABCD-1234", // Code from the mobile app
EnforcerId = "my-enforcer",
WorkspaceName = "my-project",
X25519PublicKey = myPublicKey,
});
// The response contains the routing token and pairing details
var routingToken = claim.RoutingToken;Consent Check
try
{
var status = await client.CheckConsentAsync();
// status == "approved" — proceed normally
}
catch (AirlockGatewayException ex) when (ex.ErrorCode == "app_consent_required")
{
// User hasn't granted consent — prompt them to approve in the mobile app
}
catch (AirlockGatewayException ex) when (ex.ErrorCode == "app_consent_pending")
{
// Consent request sent, waiting for user approval
}Submit and Poll
// Submit an artifact for approval
var requestId = await client.SubmitArtifactAsync(new ArtifactSubmitRequest
{
EnforcerId = "my-enforcer",
ArtifactHash = "sha256-hash",
Ciphertext = new EncryptedPayload
{
Alg = "aes-256-gcm",
Data = "base64-encrypted-content",
Nonce = "nonce",
Tag = "tag"
},
Metadata = new Dictionary<string, string>
{
["routingToken"] = "rt-abc"
}
});
// Wait for a decision (long-poll)
var decision = await client.WaitForDecisionAsync(requestId, timeoutSeconds: 30);
if (decision?.Body?.IsApproved == true)
{
Console.WriteLine($"Approved: {decision.Body.Reason}");
}Transparent Encryption (Encrypt + Submit)
var requestId = await client.EncryptAndSubmitArtifactAsync(new EncryptedArtifactRequest
{
EnforcerId = "my-enforcer",
Plaintext = "the content to approve",
RoutingToken = "rt-abc",
EncryptionKey = sharedAesKey, // Derived from X25519 ECDH during pairing
});Decision Verification
var result = client.VerifyDecision(decision, expectedArtifactHash, signerPublicKeyBase64Url);
if (result.IsValid)
{
Console.WriteLine($"Verified decision: {result.Decision}");
}
else
{
Console.WriteLine($"Verification failed: {result.FailureReason}");
}Error Handling
All errors throw AirlockGatewayException with helper properties:
try
{
await client.SubmitArtifactAsync(request);
}
catch (AirlockGatewayException ex) when (ex.IsQuotaExceeded)
{
// Handle quota exceeded (429)
}
catch (AirlockGatewayException ex) when (ex.IsPairingRevoked)
{
// Handle revoked pairing (403)
}
catch (AirlockGatewayException ex) when (ex.IsConflict)
{
// Handle idempotency conflict (409)
}Encryption
The SDK includes CryptoHelpers for X25519 ECDH key exchange and AES-256-GCM encryption/decryption using NSec.Cryptography:
GenerateX25519KeyPair()— generates a raw 32-byte X25519 keypair (base64url)DeriveSharedKey(myPrivate, peerPublic)— ECDH + HKDF-SHA256 (info:HARP-E2E-AES256GCM)AesGcmEncrypt/AesGcmDecrypt— AES-256-GCM with detached nonce and tag
Test Enforcer CLI
A fully interactive TUI that demonstrates the complete enforcer lifecycle — setup wizard, Device Auth Grant sign-in, PAT configuration, consent check, workspace pairing, artifact submission, and sign-out.
# From the repo root
cd src/dotnet
# Run the test enforcer
dotnet run --project Airlock.Gateway.Sdk.TestEnforcerPrerequisites: .NET 10 SDK. Configuration saved to ~/.airlock/test-enforcer.json.