← Back to SDKs C#
.NET —
C#
.NET — Airlock.Gateway.Sdk
A .NET 8+ client SDK for the Airlock Integrations Gateway API.
Installation
Bash
dotnet add package Airlock.Gateway.Sdk dotnet add package Airlock.Gateway.Sdk Quick Start
With Bearer Token
.NET
using Airlock.Gateway.Sdk;
using Airlock.Gateway.Sdk.Models;
var client = new AirlockGatewayClient("https://<your-gateway-host>", bearerToken: "your-jwt-token"); using Airlock.Gateway.Sdk;
using Airlock.Gateway.Sdk.Models;
var client = new AirlockGatewayClient("https://<your-gateway-host>", bearerToken: "your-jwt-token"); With Enforcer App Credentials
.NET
var client = new AirlockGatewayClient(
"https://<your-gateway-host>",
clientId: "your-client-id",
clientSecret: "your-client-secret"); var client = new AirlockGatewayClient(
"https://<your-gateway-host>",
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.
.NET
// After obtaining a PAT from your org admin or Mobile Approver
client.SetPat("airlock_pat_...");
// Clear PAT when no longer needed
client.SetPat(null); // After obtaining a PAT from your org admin or Mobile Approver
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.
.NET
// After user login (Device Auth Grant or Auth Code + PKCE)
client.SetBearerToken(accessToken); // 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)
.NET
// 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 Approver
var status = await client.GetPairingStatusAsync(pairing.Nonce);
if (status.State == "Completed")
{
var routingToken = status.RoutingToken; // Save for future requests
} // 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 Approver
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)
.NET
// Claim a pre-generated code from the Mobile Approver
var claim = await client.ClaimPairingAsync(new PairingClaimRequest
{
Code = "ABCD-1234", // Code from the Mobile Approver
EnforcerId = "my-enforcer",
WorkspaceName = "my-project",
X25519PublicKey = myPublicKey,
});
// The response contains the routing token and pairing details
var routingToken = claim.RoutingToken; // Claim a pre-generated code from the Mobile Approver
var claim = await client.ClaimPairingAsync(new PairingClaimRequest
{
Code = "ABCD-1234", // Code from the Mobile Approver
EnforcerId = "my-enforcer",
WorkspaceName = "my-project",
X25519PublicKey = myPublicKey,
});
// The response contains the routing token and pairing details
var routingToken = claim.RoutingToken; Consent Check
.NET
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 Approver
}
catch (AirlockGatewayException ex) when (ex.ErrorCode == "app_consent_pending")
{
// Consent request sent, waiting for user approval
} 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 Approver
}
catch (AirlockGatewayException ex) when (ex.ErrorCode == "app_consent_pending")
{
// Consent request sent, waiting for user approval
} Submit and Poll
.NET
// 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}");
} // 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)
.NET
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
}); 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
.NET
var result = client.VerifyDecision(decision, expectedArtifactHash, signerPublicKeyBase64Url);
if (result.IsValid)
{
Console.WriteLine($"Verified decision: {result.Decision}");
}
else
{
Console.WriteLine($"Verification failed: {result.FailureReason}");
} 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:
.NET
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)
} 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.
Bash
# From the repo root
cd src/dotnet
# Run the test enforcer
dotnet run --project Airlock.Gateway.Sdk.TestEnforcer # From the repo root
cd src/dotnet
# Run the test enforcer
dotnet run --project Airlock.Gateway.Sdk.TestEnforcer Prerequisites: .NET 10 SDK. Configuration saved to ~/.airlock/test-enforcer.json.