Skip to content

Generic HTTP Integration ​

Integrate any AI agent or application with MeshGuard using standard HTTP calls. No SDK required.

Overview ​

MeshGuard provides a simple HTTP API that any agent can use:

  1. Authenticate — Include your JWT token in requests
  2. Route through proxy — Send requests to /proxy/*
  3. Handle responses — Check for policy decisions in status codes

This guide shows you how to integrate using any HTTP client.

Prerequisites ​

  • MeshGuard gateway running (see Quickstart)
  • An agent token
  • Any HTTP client (curl, Python requests, fetch, etc.)

Quick Start ​

1. Get Your Token ​

bash
# Create an agent
meshguard agent create my-agent --trust verified

# Copy the JWT token that's printed

2. Make a Request ​

bash
# Set your token
export TOKEN="eyJhbGciOiJIUzI1NiIs..."

# Make a governed request
curl http://localhost:3100/proxy/any/path \
  -H "Authorization: Bearer $TOKEN"

3. Check the Response ​

StatusMeaning
2xxRequest allowed and proxied successfully
401Invalid or expired token
403Policy denied the request
429Rate limited
5xxGateway or target error

Authentication ​

Bearer Token ​

Include your JWT in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Token Structure ​

Your token contains:

json
{
  "sub": "agent_abc123",        // Agent ID
  "name": "my-agent",           // Agent name
  "trust": "verified",          // Trust tier
  "tags": ["myapp"],            // Tags for policy matching
  "org": "org_xyz",             // Organization (optional)
  "iat": 1706000000,            // Issued at
  "exp": 1706086400             // Expires at
}

Token Refresh ​

Tokens expire (default: 24h). Generate a new one:

bash
meshguard agent token <agent-id>

API Reference ​

Gateway Endpoints ​

Root Info ​

bash
curl http://localhost:3100/
json
{
  "name": "MeshGuard Gateway",
  "version": "0.1.0",
  "mode": "enforce",
  "docs": "https://meshguard.app/docs"
}

Health Check ​

bash
curl http://localhost:3100/health
json
{
  "status": "healthy",
  "timestamp": "2026-01-25T12:00:00Z",
  "version": "0.1.0",
  "mode": "enforce"
}

Proxy Requests ​

All requests to /proxy/* are authenticated, policy-checked, and forwarded:

bash
# GET request
curl http://localhost:3100/proxy/api/users \
  -H "Authorization: Bearer $TOKEN"

# POST request
curl http://localhost:3100/proxy/api/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "John"}'

# Any method works
curl -X DELETE http://localhost:3100/proxy/api/users/123 \
  -H "Authorization: Bearer $TOKEN"

Response Headers ​

MeshGuard adds headers to responses:

HeaderDescription
X-MeshGuard-Decisionallow or deny
X-MeshGuard-PolicyPolicy name that matched
X-MeshGuard-Trace-IdTrace ID for this request
X-MeshGuard-Agent-IdYour agent ID

Error Responses ​

401 Unauthorized ​

json
{
  "error": "unauthorized",
  "message": "Invalid or expired token"
}

403 Forbidden ​

json
{
  "error": "forbidden",
  "message": "Policy denied request",
  "reason": "Action 'delete:users' not allowed",
  "policy": "default-policy",
  "action": "delete:users",
  "trace_id": "trace_abc123"
}

429 Too Many Requests ​

json
{
  "error": "rate_limited",
  "message": "Rate limit exceeded",
  "retry_after": 60,
  "limit": "30 requests per minute"
}

Action Mapping ​

MeshGuard maps HTTP requests to actions:

MethodPathAction
GET/proxy/api/usersread:api/users
POST/proxy/api/userswrite:api/users
PUT/proxy/api/users/123write:api/users/123
PATCH/proxy/api/users/123write:api/users/123
DELETE/proxy/api/users/123delete:api/users/123

Custom Action Header ​

Override the automatic action mapping:

bash
curl http://localhost:3100/proxy/api/data \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-MeshGuard-Action: custom:action"

curl Examples ​

Basic GET ​

bash
curl -s http://localhost:3100/proxy/api/data \
  -H "Authorization: Bearer $TOKEN" \
  | jq .

POST with JSON ​

bash
curl -s http://localhost:3100/proxy/api/items \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Widget", "price": 9.99}' \
  | jq .

With Trace ID ​

bash
curl -s http://localhost:3100/proxy/api/data \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Trace-ID: my-trace-123" \
  | jq .

Check Policy Decision ​

bash
# Get headers to see decision
curl -sI http://localhost:3100/proxy/api/data \
  -H "Authorization: Bearer $TOKEN" \
  | grep -i meshguard

Handle Errors ​

bash
#!/bin/bash
TOKEN="your-token"
MESHGUARD="http://localhost:3100"

response=$(curl -s -w "\n%{http_code}" "$MESHGUARD/proxy/api/data" \
  -H "Authorization: Bearer $TOKEN")

body=$(echo "$response" | head -n -1)
status=$(echo "$response" | tail -n 1)

case $status in
  200) echo "Success: $body" ;;
  401) echo "Auth failed - check token" ;;
  403) echo "Policy denied: $(echo $body | jq -r .reason)" ;;
  429) echo "Rate limited - slow down" ;;
  *)   echo "Error $status: $body" ;;
esac

Python Examples ​

Using requests ​

python
import requests
import os

MESHGUARD_URL = os.getenv("MESHGUARD_URL", "http://localhost:3100")
MESHGUARD_TOKEN = os.getenv("MESHGUARD_TOKEN")

class MeshGuardClient:
    """Simple MeshGuard HTTP client."""
    
    def __init__(self, url: str, token: str):
        self.base_url = url.rstrip("/")
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {token}"
    
    def request(self, method: str, path: str, **kwargs) -> dict:
        """Make a governed request."""
        url = f"{self.base_url}/proxy{path}"
        response = self.session.request(method, url, **kwargs)
        
        # Handle common errors
        if response.status_code == 401:
            raise AuthError("Invalid or expired token")
        if response.status_code == 403:
            data = response.json()
            raise PolicyError(data.get("reason", "Policy denied"))
        if response.status_code == 429:
            raise RateLimitError("Rate limit exceeded")
        
        response.raise_for_status()
        return response.json() if response.content else {}
    
    def get(self, path: str, **kwargs):
        return self.request("GET", path, **kwargs)
    
    def post(self, path: str, **kwargs):
        return self.request("POST", path, **kwargs)
    
    def put(self, path: str, **kwargs):
        return self.request("PUT", path, **kwargs)
    
    def delete(self, path: str, **kwargs):
        return self.request("DELETE", path, **kwargs)


class MeshGuardError(Exception):
    pass

class AuthError(MeshGuardError):
    pass

class PolicyError(MeshGuardError):
    pass

class RateLimitError(MeshGuardError):
    pass


# Usage
client = MeshGuardClient(MESHGUARD_URL, MESHGUARD_TOKEN)

try:
    # Read data
    data = client.get("/api/users")
    print(data)
    
    # Create item
    result = client.post("/api/items", json={"name": "Widget"})
    print(result)
    
except PolicyError as e:
    print(f"Blocked by policy: {e}")
except AuthError as e:
    print(f"Auth failed: {e}")
except RateLimitError:
    print("Slow down!")

Async with httpx ​

python
import httpx
import asyncio
import os

MESHGUARD_URL = os.getenv("MESHGUARD_URL", "http://localhost:3100")
MESHGUARD_TOKEN = os.getenv("MESHGUARD_TOKEN")

async def governed_request(method: str, path: str, **kwargs):
    """Make an async governed request."""
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method,
            f"{MESHGUARD_URL}/proxy{path}",
            headers={"Authorization": f"Bearer {MESHGUARD_TOKEN}"},
            **kwargs
        )
        
        if response.status_code == 403:
            data = response.json()
            raise PermissionError(data.get("reason"))
        
        response.raise_for_status()
        return response.json() if response.content else {}


async def main():
    # Parallel governed requests
    results = await asyncio.gather(
        governed_request("GET", "/api/users"),
        governed_request("GET", "/api/items"),
        governed_request("GET", "/api/settings"),
        return_exceptions=True
    )
    
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            print(f"Request {i} failed: {result}")
        else:
            print(f"Request {i} succeeded: {result}")


asyncio.run(main())

JavaScript/TypeScript Examples ​

Using fetch ​

typescript
const MESHGUARD_URL = process.env.MESHGUARD_URL ?? 'http://localhost:3100';
const MESHGUARD_TOKEN = process.env.MESHGUARD_TOKEN!;

class MeshGuardClient {
  private baseUrl: string;
  private token: string;

  constructor(url: string, token: string) {
    this.baseUrl = url.replace(/\/$/, '');
    this.token = token;
  }

  async request<T = any>(
    method: string,
    path: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseUrl}/proxy${path}`;
    
    const response = await fetch(url, {
      method,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json',
        ...options.headers,
      },
      ...options,
    });

    if (response.status === 401) {
      throw new Error('Invalid or expired token');
    }
    
    if (response.status === 403) {
      const data = await response.json();
      throw new Error(`Policy denied: ${data.reason}`);
    }
    
    if (response.status === 429) {
      throw new Error('Rate limit exceeded');
    }

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const text = await response.text();
    return text ? JSON.parse(text) : null;
  }

  get<T = any>(path: string) {
    return this.request<T>('GET', path);
  }

  post<T = any>(path: string, body: any) {
    return this.request<T>('POST', path, { body: JSON.stringify(body) });
  }

  put<T = any>(path: string, body: any) {
    return this.request<T>('PUT', path, { body: JSON.stringify(body) });
  }

  delete<T = any>(path: string) {
    return this.request<T>('DELETE', path);
  }
}

// Usage
const client = new MeshGuardClient(MESHGUARD_URL, MESHGUARD_TOKEN);

async function main() {
  try {
    const users = await client.get('/api/users');
    console.log('Users:', users);

    const newItem = await client.post('/api/items', { name: 'Widget' });
    console.log('Created:', newItem);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

main();

Node.js with axios ​

javascript
const axios = require('axios');

const meshguard = axios.create({
  baseURL: process.env.MESHGUARD_URL || 'http://localhost:3100',
  headers: {
    'Authorization': `Bearer ${process.env.MESHGUARD_TOKEN}`,
  },
});

// Add response interceptor for error handling
meshguard.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 403) {
      const reason = error.response.data?.reason || 'Policy denied';
      return Promise.reject(new Error(`MeshGuard: ${reason}`));
    }
    return Promise.reject(error);
  }
);

// Usage
async function fetchData() {
  const { data } = await meshguard.get('/proxy/api/data');
  return data;
}

Go Example ​

go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type MeshGuardClient struct {
    BaseURL string
    Token   string
    Client  *http.Client
}

func NewMeshGuardClient(url, token string) *MeshGuardClient {
    return &MeshGuardClient{
        BaseURL: url,
        Token:   token,
        Client:  &http.Client{},
    }
}

func (c *MeshGuardClient) Request(method, path string, body interface{}) ([]byte, error) {
    var bodyReader io.Reader
    if body != nil {
        jsonBody, _ := json.Marshal(body)
        bodyReader = bytes.NewBuffer(jsonBody)
    }

    req, err := http.NewRequest(method, c.BaseURL+"/proxy"+path, bodyReader)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+c.Token)
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.Client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)

    if resp.StatusCode == 403 {
        return nil, fmt.Errorf("policy denied: %s", string(respBody))
    }
    if resp.StatusCode >= 400 {
        return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
    }

    return respBody, nil
}

func main() {
    client := NewMeshGuardClient(
        os.Getenv("MESHGUARD_URL"),
        os.Getenv("MESHGUARD_TOKEN"),
    )

    // GET request
    data, err := client.Request("GET", "/api/users", nil)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Response:", string(data))
}

Tracing ​

Set Trace ID ​

Include a trace ID to correlate requests:

bash
curl http://localhost:3100/proxy/api/data \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Trace-ID: my-conversation-123"

View Trace ​

bash
meshguard audit trace my-conversation-123

Generate Trace IDs ​

python
import uuid

# Per-conversation trace
conversation_id = str(uuid.uuid4())

# Include in all requests for that conversation
headers = {
    "Authorization": f"Bearer {token}",
    "X-Trace-ID": conversation_id
}

Best Practices ​

1. Handle All Error Cases ​

python
try:
    result = client.get("/api/data")
except AuthError:
    # Token expired - refresh and retry
    refresh_token()
    result = client.get("/api/data")
except PolicyError as e:
    # Log and inform user
    log_policy_violation(e)
    return "This action is not allowed by policy"
except RateLimitError:
    # Back off and retry
    time.sleep(60)
    result = client.get("/api/data")

2. Use Meaningful Trace IDs ​

python
# ❌ Bad - random trace per request
trace_id = str(uuid.uuid4())

# ✅ Good - trace per conversation/workflow
trace_id = f"user-{user_id}-chat-{chat_id}"

3. Log Policy Decisions ​

python
response = requests.get(url, headers=headers)

# Log the decision
decision = response.headers.get("X-MeshGuard-Decision")
policy = response.headers.get("X-MeshGuard-Policy")
logger.info(f"MeshGuard: {decision} by {policy}")

4. Implement Retry Logic ​

python
import time
from functools import wraps

def with_retry(max_attempts=3, backoff=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_error = None
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except RateLimitError:
                    last_error = "Rate limited"
                    time.sleep(backoff * (2 ** attempt))
                except AuthError:
                    # Don't retry auth errors
                    raise
            raise Exception(f"Max retries exceeded: {last_error}")
        return wrapper
    return decorator

@with_retry(max_attempts=3)
def fetch_data():
    return client.get("/api/data")

Testing ​

Test Authentication ​

bash
# Valid token should work
curl -s http://localhost:3100/proxy/api/test \
  -H "Authorization: Bearer $TOKEN" \
  -w "\nStatus: %{http_code}\n"

# Invalid token should fail
curl -s http://localhost:3100/proxy/api/test \
  -H "Authorization: Bearer invalid-token" \
  -w "\nStatus: %{http_code}\n"

Test Policy Enforcement ​

bash
# Allowed action
curl -s http://localhost:3100/proxy/api/read \
  -H "Authorization: Bearer $TOKEN" \
  -w "\nStatus: %{http_code}\n"

# Denied action
curl -s -X DELETE http://localhost:3100/proxy/api/delete \
  -H "Authorization: Bearer $TOKEN" \
  -w "\nStatus: %{http_code}\n"

Automated Tests ​

python
import pytest

def test_authentication():
    """Test that valid tokens work."""
    response = requests.get(
        f"{MESHGUARD_URL}/proxy/api/test",
        headers={"Authorization": f"Bearer {VALID_TOKEN}"}
    )
    assert response.status_code == 200

def test_invalid_token():
    """Test that invalid tokens are rejected."""
    response = requests.get(
        f"{MESHGUARD_URL}/proxy/api/test",
        headers={"Authorization": "Bearer invalid"}
    )
    assert response.status_code == 401

def test_policy_allow():
    """Test that allowed actions succeed."""
    response = requests.get(
        f"{MESHGUARD_URL}/proxy/api/read",
        headers={"Authorization": f"Bearer {TOKEN}"}
    )
    assert response.status_code == 200

def test_policy_deny():
    """Test that denied actions fail."""
    response = requests.delete(
        f"{MESHGUARD_URL}/proxy/api/delete",
        headers={"Authorization": f"Bearer {TOKEN}"}
    )
    assert response.status_code == 403

Troubleshooting ​

Connection Refused ​

MeshGuard isn't running:

bash
cd meshguard
bun run src/index.ts

401 on Every Request ​

Token issues:

bash
# Check token validity
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .

# Generate new token
meshguard agent token <agent-id>

403 on Expected Allow ​

Policy mismatch:

bash
# See what's allowed
meshguard policy allowed <agent-id>

# Test specific action
meshguard policy test <agent-id> "read:api/data"

# Check audit log
meshguard audit tail -n 5

Requests Timing Out ​

Check target connectivity:

bash
# Test direct connection to target
curl -v $PROXY_TARGET/api/test

# Check MeshGuard health
curl http://localhost:3100/health

Next Steps ​

Governance Control Plane for AI Agents