← Back to SDKs Go
Go —
Go
Go — airlock
A Go client SDK for the Airlock Integrations Gateway API. Requires Go 1.21+.
Installation
go get github.com/airlockapp/gateway-clients/src/go/airlockQuick Start
With Bearer Token
package main
import (
"fmt"
airlock "github.com/airlockapp/gateway-clients/src/go/airlock"
)
func main() {
client := airlock.NewClient("https://igw.airlocks.io", "your-token")
echo, err := client.Echo()
if err != nil {
panic(err)
}
fmt.Println(echo)
}With Enforcer App Credentials
client := airlock.NewClientWithCredentials(
"https://igw.airlocks.io",
"your-client-id",
"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("")Dual Auth (SetBearerToken)
// 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) | Login(onUserCode) |
| Web | Auth Code + PKCE (RFC 7636) | LoginWithAuthCode or GetAuthorizationURL + ExchangeCode |
| Mobile | Auth Code + PKCE (RFC 7636) | GetAuthorizationURL + ExchangeCode |
Gateway Client API
| Method | Description |
|---|---|
Echo() | Gateway discovery / health |
SetPat(pat) | Set Personal Access Token (X-PAT header) |
SetBearerToken(token) | Set Bearer token for user-scoped operations |
CheckConsent() | Check user consent for this enforcer app |
SubmitArtifact(request) | Submit artifact for approval |
GetExchangeStatus(requestID) | Get exchange status |
WaitForDecision(requestID, timeout) | Long-poll for decision |
SubmitAck(msgID, requestID...) | Acknowledge decision delivery (POST /v1/acks, fire-and-forget) |
WithdrawExchange(requestID) | Withdraw pending exchange |
InitiatePairing(request) | Start pairing session |
ClaimPairing(request) | Claim a pre-generated pairing code |
GetPairingStatus(nonce) | Poll pairing status |
RevokePairing(routingToken) | Revoke a pairing |
SendHeartbeat(request) | Presence heartbeat |
GetEffectiveDndPolicies(enforcerID, workspaceID, sessionID) | Fetch effective DND policies |
Auth Client (AirlockAuthClient)
| Method | Purpose |
|---|---|
Discover | OIDC discovery |
Login | Device code login |
LoginWithAuthCode | Auth code + PKCE (local callback) |
GetAuthorizationURL, ExchangeCode | Auth code + PKCE (manual redirect) |
RefreshToken, GetAccessToken | Token refresh / access |
Logout | Sign out (revoke) |
Pairing
Standard Pairing (Enforcer-Initiated)
// 1. Initiate a pairing session
resp, err := client.InitiatePairing(airlock.PairingInitiateRequest{
EnforcerID: "my-enforcer",
WorkspaceName: "my-project",
X25519PublicKey: myPublicKey,
})
// 2. Display pairing code to user
fmt.Printf("Pairing code: %s\n", resp.PairingCode)
// 3. Poll for approval from the mobile app
status, err := client.GetPairingStatus(resp.Nonce)
// status.State == "Completed" → save status.RoutingTokenPre-Generated Code Pairing (Approver-Initiated)
claim, err := client.ClaimPairing(airlock.PairingClaimRequest{
Code: "ABCD-1234",
EnforcerID: "my-enforcer",
WorkspaceName: "my-project",
X25519PublicKey: myPublicKey,
})
// claim.RoutingToken is ready to useConsent Check
status, err := client.CheckConsent()
if err != nil {
var gwErr *airlock.GatewayError
if errors.As(err, &gwErr) {
switch gwErr.ErrorCode {
case "app_consent_required":
// User hasn't granted consent
case "app_consent_pending":
// Consent request sent, waiting for approval
}
}
}Submit and Poll
// Submit an artifact for approval
requestID, err := client.SubmitArtifact(airlock.ArtifactSubmitRequest{
EnforcerID: "my-enforcer",
ArtifactHash: "sha256-hash",
Ciphertext: airlock.EncryptedPayload{
Alg: "aes-256-gcm",
Data: "base64-encrypted-content",
Nonce: "nonce",
Tag: "tag",
},
Metadata: map[string]string{"routingToken": "rt-abc"},
})
if err != nil {
panic(err)
}
// Wait for a decision (long-poll)
decision, err := client.WaitForDecision(requestID, 30)
if err != nil {
panic(err)
}
if decision != nil && decision.Body.IsApproved() {
fmt.Printf("Approved: %s\n", decision.Body.Reason)
}Error Handling
All errors use *GatewayError with helper methods:
requestID, err := client.SubmitArtifact(req)
if err != nil {
var gwErr *airlock.GatewayError
if errors.As(err, &gwErr) {
if gwErr.IsQuotaExceeded() { /* 429 */ }
if gwErr.IsPairingRevoked() { /* 403 pairing_revoked */ }
if gwErr.IsConflict() { /* 409 */ }
}
}Encryption
Uses X25519 ECDH via golang.org/x/crypto:
GenerateX25519KeyPair()— 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.
# From the repo root
cd src/go
go run ./cmd/test-enforcerPrerequisites: Go 1.21+. Configuration saved to ~/.airlock/test-enforcer-go.json.