Developer Guide

Build Host Enforcer applications that integrate with your organization's Airlock Integrations Gateway for human-in-the-loop approval.

Enterprise deployment. All examples use placeholders for your environment. Your Airlock team or platform administrator provides Gateway URL, identity realm, and enforcer app credentials. See also Getting started and What are Airlock Apps?.

Sample code language

Applies to all SDK examples below. HTTP and JSON blocks stay protocol-specific.

Overview

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

Enforcers communicate only with the Integrations Gateway at https://<your-gateway-host>. Identity is provided by your organization's OIDC realm at https://<your-auth-host>/realms/<your-realm>.

To integrate a custom or sample enforcer in an enterprise deployment:

  1. Obtain Gateway URL, auth realm, and enforcer app credentials from your administrator
  2. Initialize a Gateway SDK client with those values
  3. Implement the enforcer lifecycle (PAT or OAuth, consent, pairing, presence, artifacts, decision ack)
  4. Point approvers at your organization-distributed Mobile Approver for pairing and decisions

Deployment placeholders

Setting Placeholder
Gateway base URL https://<your-gateway-host>
Auth realm (OIDC) https://<your-auth-host>/realms/<your-realm>
Enforcer Client ID <your-client-id>
Enforcer Client Secret <your-client-secret> (confidential clients)
User PAT <your-pat>

Step 1 — Obtain deployment credentials

Your organization deploys the Airlock Gateway and identity provider inside its infrastructure. Before writing integration code, collect:

  • Gateway base URL (https://<your-gateway-host>)
  • OIDC realm URL (https://<your-auth-host>/realms/<your-realm>)
  • Enforcer app registration (Client ID and, for confidential clients, Client Secret)
  • Network access from enforcer hosts to the Gateway (TLS, firewall rules)

If you are evaluating Airlock, contact us for deployment architecture and credential provisioning.

Step 2 — Register an enforcer application

Each enforcer integration is registered as an Airlock App in your enterprise deployment. Your platform administrator creates the app and issues a Client ID and (for confidential clients) a Client Secret. Developer profile metadata (name/org, contact email) may be shown to approvers during the consent flow.

See What are Airlock Apps? for Client ID, Client Secret, and consent concepts.

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:

HTTP
GET /v1/exchanges/req-123 HTTP/1.1
Host: <your-gateway-host>
X-Client-Id: <your-client-id>
Origin: https://myapp.example.com
Authorization: Bearer <user-jwt>

Confidential client example:

HTTP
POST /v1/artifacts HTTP/1.1
Host: <your-gateway-host>
X-Client-Id: <your-client-id>
X-Client-Secret: <your-client-secret>
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

# Recommended: set a Personal Access Token (X-PAT header)
# Issued by your org admin or from the Mobile Approver
client.set_pat("<your-pat>")

Bearer token

from airlock_gateway import AirlockGatewayClient

async with AirlockGatewayClient(
    "https://<your-gateway-host>",
    token="<user-jwt>",
) as client:
    echo = await client.echo()

Dual auth — Bearer after OAuth

# After user login (Device Auth Grant or Auth Code + PKCE)
client.set_bearer_token(access_token)

Initialize the client

from airlock_gateway import AirlockGatewayClient

client = AirlockGatewayClient(
    "https://<your-gateway-host>",
    client_id="<your-client-id>",
    client_secret="<your-client-secret>",
)

Step 4 — Implement the Enforcer Lifecycle

A complete enforcer typically follows this lifecycle:

Diagram
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):

HTTP
GET /v1/integrations/discovery

Response:

JSON
{
  "idp": {
    "baseUrl": "https://<your-auth-host>/realms/<your-realm>",
    "clientId": "<your-integrations-client-id>"
  },
  "auth": {
    "patSupported": true
  }
}

2a. PAT authentication (recommended)

The simplest user identity for enforcers: obtain a Personal Access Token from your platform administrator or create one in the organization-distributed 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("<your-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 against your organization's identity provider. The integrations client ID is returned by discovery (typically <your-integrations-client-id>). 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:

HTTP
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 approver
print(f"Pairing code: {response.pairing_code}")
print("Enter this code in the Mobile Approver.")

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

HTTP
POST /v1/pairing/pre-generate

Enforcer claims the code:

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

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 your identity provider's token endpoint.

client.revoke_pairing(routing_token)

Architecture constraints

Host Enforcers must communicate only with the Integrations Gateway (<your-gateway-host>). Do not call internal platform services directly — the Gateway is the sole external integration boundary for enforcer applications in an enterprise deployment.

Diagram
┌─────────────────┐     HTTPS       ┌──────────────────────┐
│  Your Enforcer   │ ──────────────→ │ Integrations Gateway │
│  (custom / sample)│                 │  (<your-gateway-host>)   │
└─────────────────┘                  └──────────┬───────────┘

                                     Internal routing

                                     ┌──────────▼───────────┐
                                     │  Enterprise platform  │
                                     │  services (internal;  │
                                     │  not exposed to       │
                                     │  enforcers directly)  │
                                     └──────────────────────┘

Test enforcer reference implementations

The gateway-clients repository includes interactive test enforcer CLIs that implement the full lifecycle. Configure them to point at https://<your-gateway-host> using your organization's Client ID and Secret. They 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

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

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