Developer Guide

Build third-party enforcer applications that integrate with the Airlock Integrations Gateway for human-in-the-loop approval.

Overview

Airlock enables human-in-the-loop approval for AI agent operations. An enforcer intercepts agent actions (commands, code changes, API calls) and routes them through Airlock for human review before execution.

Third-party enforcers communicate with the Integrations Gateway at https://igw.airlocks.io.

To build a third-party enforcer, you typically:

  1. Join the Developer Programme
  2. Register an Enforcer App
  3. Integrate using the Gateway SDK
  4. Implement the enforcer lifecycle (PAT or OAuth, consent, pairing, presence, artifacts, decision ack)

Step 1 — Join the Developer Programme

Apply via the Airlock Platform under Developer Programme. Your developer profile details (name/org, contact email, and optional web page) may be shown to end users during the consent flow.

Step 2 — Register an Enforcer App

Create an app entry under Developer Programme → My Apps. When created, you'll receive an App ID, Client ID, and (for confidential apps) a Client Secret (shown only once).

App kind reference

Kind Client Type Auth Method Typical Use Case
Web Public Client ID + Origin validation Browser-based enforcer UIs, dashboards
Mobile Public Client ID + Origin validation Mobile enforcer apps
Agent Confidential Client ID + Client Secret AI agent plugins, autonomous enforcers
Desktop Confidential Client ID + Client Secret Desktop IDE plugins, CLI tools
VsCodeExtension Confidential Client ID + Client Secret VS Code / IDE extension enforcers

Authentication methods

Public clients use X-Client-Id plus Origin validation. Confidential clients use both X-Client-Id and X-Client-Secret. Many flows are also user-scoped and include a user Bearer token.

Public client example:

GET /v1/exchanges/req-123 HTTP/1.1
Host: igw.airlocks.io
X-Client-Id: ABCDEFGHJKLMNPRSTUVWXYZabc
Origin: https://myapp.com
Authorization: Bearer <user-jwt>

Confidential client example:

POST /v1/artifacts HTTP/1.1
Host: igw.airlocks.io
X-Client-Id: ABCDEFGHJKLMNPRSTUVWXYZabc
X-Client-Secret: abcdEFGH1234567890abcdEFGH1234567890abcd
Authorization: Bearer <user-jwt>

Dual authentication (app + user)

Most enforcer operations are user-scoped. The gateway accepts user identity via X-PAT (Personal Access Token — recommended) or Authorization: Bearer <jwt> (Device Authorization Grant). Auth priority: the gateway checks X-PAT first; if absent, it falls back to the Bearer JWT. App credentials (X-Client-Id / X-Client-Secret or Origin) identify your app.

Step 3 — Integrate Using the Gateway SDK

The Gateway SDKs provide a consistent client surface for the Integrations Gateway. Initialize the client with the gateway URL and your app credentials.

SDK source and packages

Language Package Source
.NET Airlock.Gateway.Sdk github.com/airlockapp/gateway-clients
Python airlock-gateway github.com/airlockapp/gateway-clients
TypeScript @airlockapp/gateway-sdk github.com/airlockapp/gateway-clients
Go pkg.go.dev (Go package docs) github.com/airlockapp/gateway-clients
Rust airlock-gateway github.com/airlockapp/gateway-clients

Authentication quick start

Configure app credentials, then authenticate the user with a PAT (recommended) or a Bearer token after Device Auth Grant sign-in.

Personal Access Token (Python)

# Recommended: set a Personal Access Token (X-PAT header)
client.set_pat("airlock_pat_...")

Personal Access Token (TypeScript)

// Recommended: PAT from Platform or Mobile Approver (X-PAT header)
client.setPat("airlock_pat_...");

Bearer token (Python)

from airlock_gateway import AirlockGatewayClient

async with AirlockGatewayClient(
    "https://igw.airlocks.io",
    token="your-jwt-token",
) as client:
    echo = await client.echo()

App credentials (TypeScript)

import { AirlockGatewayClient } from "@airlockapp/gateway-sdk";

const client = new AirlockGatewayClient({
  baseUrl: "https://igw.airlocks.io",
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
});

Dual auth — Bearer after OAuth (TypeScript)

// After user login (Device Auth Grant or Auth Code + PKCE)
client.setBearerToken(accessToken);

Initialize the client

Python

from airlock_gateway import AirlockGatewayClient

client = AirlockGatewayClient(
    "https://igw.airlocks.io",
    client_id="your-client-id",
    client_secret="your-client-secret",
)

.NET

var httpClient = new HttpClient { BaseAddress = new Uri("https://igw.airlocks.io") };
httpClient.DefaultRequestHeaders.Add("X-Client-Id", "your-client-id");
httpClient.DefaultRequestHeaders.Add("X-Client-Secret", "your-client-secret");

var client = new AirlockGatewayClient(httpClient);

TypeScript

import { AirlockGatewayClient } from "@airlockapp/gateway-sdk";

const client = new AirlockGatewayClient({
  baseUrl: "https://igw.airlocks.io",
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
});

Go

client := airlock.NewClient(
    "https://igw.airlocks.io",
    airlock.WithClientCredentials("your-client-id", "your-client-secret"),
)

Step 4 — Implement the Enforcer Lifecycle

A complete enforcer typically follows this lifecycle:

Discovery → Set PAT (or Sign In) → Consent Check → Pair → Heartbeat ↺

                                                  Submit Artifact → Wait for Decision → Ack delivery

                                                      Unpair → Sign Out

1. Discovery

Discover the gateway’s IdP configuration (and whether PAT auth is supported):

GET /v1/integrations/discovery

Response:

{
  "idp": {
    "baseUrl": "https://auth.airlocks.io/realms/airlock",
    "clientId": "airlock-integrations"
  },
  "auth": {
    "patSupported": true
  }
}

2a. PAT authentication (recommended)

The simplest user identity for enforcers: create a Personal Access Token from the Platform (Settings → Access Tokens) or the Mobile Approver. The SDK sends it as X-PAT on every request — no OIDC polling or token refresh.

# Set the PAT on the client (recommended for CLIs / scripts)
client.set_pat("airlock_pat_...")

2b. User authentication (Device Authorization Grant — fallback)

Use the OAuth 2.0 Device Authorization Grant (RFC 8628) when you need interactive browser-based login. Use OIDC client ID airlock-integrations. After obtaining an access token, set it on the SDK client:

# After obtaining the access token
client.set_bearer_token(access_token)

3. Consent check

Check whether the user has consented to your app:

GET /v1/consents/check

4. Workspace pairing

Initiate pairing, display the pairing code, and poll until completed:

response = client.initiate_pairing(
    device_id="dev-my-machine",
    enforcer_id="my-enforcer",
    enforcer_label="My Custom Enforcer",
    workspace_name="default",
)

# Display the pairing code to the user
print(f"Pairing code: {response.pairing_code}")
print("Enter this code in the Airlock mobile app.")

Poll for completion:

status = client.get_pairing_status(response.pairing_nonce)
if status.state == "completed":
    routing_token = status.routing_token
    # Save this token — you'll need it for all subsequent requests

4b. Pre-generated pairing code

Alternatively, an approver can pre-generate a pairing code from the Mobile Approver. The enforcer claims that code instead of initiating a new session.

Approver creates a session (Mobile Approver):

POST /v1/pairing/pre-generate

Enforcer claims the code (Python SDK):

response = client.claim_pairing(
    pairing_code="ABCD-1234",
    enforcer_id="my-enforcer",
    enforcer_label="My Custom Enforcer",
    workspace_name="default",
)
routing_token = response.routing_token

5. Presence heartbeat

While paired, send heartbeats periodically (e.g. every 30 seconds):

client.send_heartbeat(
    enforcer_id="my-enforcer",
    enforcer_label="My Custom Enforcer",
    workspace_name="default",
)

6. Artifact submission & decision polling

Submit an artifact and long-poll for a decision:

request_id = client.submit_artifact(
    enforcer_id="my-enforcer",
    artifact_type="command.review",
    artifact_hash="sha256-of-content",
    ciphertext={
        "alg": "xchacha20-poly1305",
        "data": "<base64-encrypted-payload>",
        "nonce": "<base64-nonce>",
        "tag": "<base64-auth-tag>",
    },
    metadata={
        "routingToken": routing_token,
        "workspaceName": "default",
    },
)

Wait for a decision:

decision = client.wait_for_decision(request_id, timeout_seconds=25)

if decision and decision.body:
    if decision.body.decision == "approve":
        # Execute the agent's action
        pass
    else:
        # Block the agent's action
        reason = decision.body.reason  # Optional reason from the approver

Acknowledge decision delivery. After the approver’s decision is delivered, call POST /v1/acks (via submit_ack / submitAck / etc.) so the gateway records receipt. Pass the decision envelope’s msgId and the exchange request_id. This is fire-and-forget: log failures but do not block your main flow.

# After receiving a decision: confirm delivery to the gateway (fire-and-forget)
await client.submit_ack(msg_id, request_id)

Withdraw a pending request:

client.withdraw_exchange(request_id)

7. Unpairing & sign-out

Revoke pairing when the user unpairs. For OAuth sign-out, revoke the refresh token at the Keycloak endpoint.

client.revoke_pairing(routing_token)

Architecture constraints

Third-party enforcers must communicate only with the Integrations Gateway (igw.airlocks.io).

┌─────────────────┐     HTTPS       ┌──────────────────────┐
│  Your Enforcer   │ ──────────────→ │ Integrations Gateway │
│  (3rd-party app) │                 │  (igw.airlocks.io)   │
└─────────────────┘                  └──────────┬───────────┘

                                     Internal routing

                                     ┌──────────▼───────────┐
                                     │   Platform Backend    │
                                     │   (not accessible     │
                                     │    to enforcers)      │
                                     └──────────────────────┘

Test enforcer reference implementations

The SDK repo includes interactive test enforcer CLIs that implement the full lifecycle. These are useful end-to-end references for pairing, presence, artifact submission, decision polling, delivery acknowledgment (POST /v1/acks), withdrawal, unpairing, and sign-out.

Run the test enforcers

.NET

cd src/dotnet/Airlock.Gateway.Sdk.TestEnforcer
dotnet run

TypeScript

cd src/typescript/test-enforcer
npm install
npx ts-node index.ts

Go

cd src/go
go run ./cmd/test-enforcer

Python

cd src/python
pip install -e .
python test_enforcer.py

Rust

cd src/rust
cargo run --bin test_enforcer

Run these from the root of the gateway-clients repository clone.