← Back to SDKs

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/airlock

Quick 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

KindOAuth2 FlowSDK Methods
Agent / Desktop / VsCodeDevice Auth Grant (RFC 8628)Login(onUserCode)
WebAuth Code + PKCE (RFC 7636)LoginWithAuthCode or GetAuthorizationURL + ExchangeCode
MobileAuth Code + PKCE (RFC 7636)GetAuthorizationURL + ExchangeCode

Gateway Client API

MethodDescription
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)

MethodPurpose
DiscoverOIDC discovery
LoginDevice code login
LoginWithAuthCodeAuth code + PKCE (local callback)
GetAuthorizationURL, ExchangeCodeAuth code + PKCE (manual redirect)
RefreshToken, GetAccessTokenToken refresh / access
LogoutSign 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.RoutingToken

Pre-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 use

Consent 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-enforcer

Prerequisites: Go 1.21+. Configuration saved to ~/.airlock/test-enforcer-go.json.