Vendor Integration Guide

For: Teams integrating applications with Axemere Gateway for the first time.

This guide walks through registering a workload, configuring credentials, pushing a policy bundle, and sending requests to AI providers through the gateway. Examples are provided in curl and Python for all six supported connectors.

Table of Contents


Prerequisites

You need:

  • Gateway URL (managed: https://<env>.gcp.gw.axemere.ai; self-hosted: http://localhost:7080)
  • Admin token (MVGC_ADMIN_TOKEN on the gateway) for setup steps
  • API key (for managed mode) or a registered workload token
  • At least one AI provider API key

Set shell variables used throughout this guide:

GATEWAY_URL="https://dev.gcp.gw.axemere.ai"
ADMIN_TOKEN="<your-admin-token>"
API_KEY="<your-api-key>"
ORG_ID="<your-org-id>"

Step 1 — Register a Workload

A workload represents a registered application. All requests must carry a workload_id that the gateway recognizes.

curl -s -X PUT "$GATEWAY_URL/v1/admin/workloads" \
  -H "MVGC-Admin-Token: $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workload_id": "wl-my-app",
    "org_id": "'"$ORG_ID"'",
    "name": "My Application",
    "allowed_connection_types": ["direct_api"],
    "default_attribution": {
      "customer_id": "cust-001",
      "project_id": "proj-my-app"
    }
  }'

Verify:

curl -s "$GATEWAY_URL/v1/admin/workloads" \
  -H "MVGC-Admin-Token: $ADMIN_TOKEN" | jq '.workloads[] | .workload_id'

Step 2 — Register a Credential

Axemere Gateway supports two credential modes:

ModeHow it worksWhen to use
byok (bring-your-own-key)Caller supplies the raw API key per-request in action.params or via credential_hint.keyDev/testing; caller controls the key
aliasGateway stores the API key; caller references it by credential_idProduction; keys stay server-side

Register an alias credential (server-side key):

curl -s -X PUT "$GATEWAY_URL/v1/admin/credentials" \
  -H "MVGC-Admin-Token: $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "cred-openai-prod",
    "org_id": "'"$ORG_ID"'",
    "connector_id": "openai",
    "mode": "alias",
    "secret_ref": "'"$OPENAI_API_KEY"'"
  }'

The secret_ref value is encrypted at rest. Once stored, it is never returned in API responses.


Step 3 — Configure a Policy Bundle

A policy bundle is a YAML document that controls which requests are allowed, rate-limited, budget-capped, or denied.

Allow all traffic from a workload

The minimal bundle that allows any request from wl-my-app to api.openai.com:

curl -s -X PUT "$GATEWAY_URL/v1/admin/policies" \
  -H "MVGC-Admin-Token: $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "bundle_yaml": "schema: mvgc.policy_bundle.v1\nbundle_id: default\nversion: \"1.0.0\"\nrules:\n  - layers: [identity]\n    rules:\n      - effect:\n          decision: allow\n        conditions:\n          workload.workload_id:\n            equals: wl-my-app\n  - layers: [targets]\n    rules:\n      - effect:\n          decision: allow\n        conditions:\n          action.target_host:\n            equals: api.openai.com\n"
  }'

For more complex bundles, push a multiline YAML string or use the mvgc sign-bundle subcommand to sign and push a bundle file.

Add rate limiting

Add a rule to the identity layer that limits wl-my-app to 60 requests per minute:

# rate_limit_bundle.yaml
schema: mvgc.policy_bundle.v1
bundle_id: default
version: "1.1.0"
rules:
  - layers: [identity]
    rules:
      - conditions:
          workload.workload_id:
            equals: wl-my-app
        effect:
          decision: allow
          rate_limit:
            requests_per_minute: 60

  - layers: [targets]
    rules:
      - conditions:
          action.target_host:
            equals: api.openai.com
        effect:
          decision: allow

Push the bundle:

BUNDLE_YAML=$(cat rate_limit_bundle.yaml)
curl -s -X PUT "$GATEWAY_URL/v1/admin/policies" \
  -H "MVGC-Admin-Token: $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg y "$BUNDLE_YAML" '{"bundle_yaml": $y}')"

Add a budget cap

Add a budget rule that limits monthly spend on proj-my-app to $50 USD:

# Add to rules section:
  - layers: [budgets]
    rules:
      - conditions:
          attribution.project_id:
            equals: proj-my-app
        effect:
          decision: allow
          budget:
            monthly_usd_max: "50.00"

When the monthly limit is reached, the gateway returns HTTP 403 with reason: "budget_exceeded".


Step 4 — Send Requests

All requests use POST /v1/actions:execute (the Direct API connection type).

The Authorization: Bearer <api-key> header authenticates the caller on managed gateway. On self-hosted gateway, this header is optional (set MVGC_MANAGED_MODE=false).

OpenAI

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "api.openai.com",
      "target_path": "/v1/chat/completions",
      "params": {
        "model": "gpt-4o-mini",
        "messages": [{"role": "user", "content": "Hello"}],
        "max_tokens": 50
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Anthropic

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "api.anthropic.com",
      "target_path": "/v1/messages",
      "params": {
        "model": "claude-haiku-4-5-20251001",
        "max_tokens": 50,
        "messages": [{"role": "user", "content": "Hello"}]
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Google Gemini

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "generativelanguage.googleapis.com",
      "target_path": "/v1beta/models/gemini-2.0-flash:generateContent",
      "params": {
        "contents": [{"role": "user", "parts": [{"text": "Hello"}]}]
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Azure OpenAI

Replace <resource> with your Azure resource name and <deployment> with your deployment name.

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "<resource>.openai.azure.com",
      "target_path": "/openai/deployments/<deployment>/chat/completions?api-version=2024-02-01",
      "params": {
        "messages": [{"role": "user", "content": "Hello"}],
        "max_tokens": 50
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Cohere

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "api.cohere.com",
      "target_path": "/v2/chat",
      "params": {
        "model": "command-r-plus",
        "messages": [{"role": "user", "content": "Hello"}]
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Generic HTTP

Use the generic_http connector for any other HTTP API:

curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": "mvgc.action_request.v2",
    "org_id": "'"$ORG_ID"'",
    "workload_id": "wl-my-app",
    "connector_hint": "generic_http",
    "action": {
      "type": "ai.infer",
      "method": "POST",
      "target_host": "api.example.com",
      "target_path": "/v1/completions",
      "params": {
        "prompt": "Hello"
      }
    },
    "attribution": {"project_id": "proj-my-app"}
  }'

Python SDK Integration

OpenAI SDK — base_url swap

The simplest integration: point the OpenAI SDK at the gateway. The gateway intercepts the request, applies policies, and forwards it to OpenAI.

import os
from openai import OpenAI

client = OpenAI(
    # Point at the gateway instead of api.openai.com
    base_url=f"{os.environ['GATEWAY_URL']}/v1/actions:execute",
    api_key=os.environ["API_KEY"],
)

# The gateway wraps the standard OpenAI call automatically
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
    max_tokens=50,
    # Gateway-specific fields passed via extra_body
    extra_body={
        "schema": "mvgc.action_request.v2",
        "org_id": os.environ["ORG_ID"],
        "workload_id": "wl-my-app",
        "attribution": {"project_id": "proj-my-app"},
    },
)

print(response.choices[0].message.content)

Recommended approach: build a thin wrapper that constructs the full ActionRequest body:

import os
import httpx
from typing import Any

GATEWAY_URL = os.environ["GATEWAY_URL"]
API_KEY = os.environ["API_KEY"]
ORG_ID = os.environ["ORG_ID"]

def call_openai(
    workload_id: str,
    model: str,
    messages: list[dict[str, str]],
    *,
    project_id: str | None = None,
    max_tokens: int = 512,
) -> dict[str, Any]:
    """Send an OpenAI chat completion through the Axemere Gateway."""
    payload = {
        "schema": "mvgc.action_request.v2",
        "org_id": ORG_ID,
        "workload_id": workload_id,
        "action": {
            "type": "ai.infer",
            "method": "POST",
            "target_host": "api.openai.com",
            "target_path": "/v1/chat/completions",
            "params": {
                "model": model,
                "messages": messages,
                "max_tokens": max_tokens,
            },
        },
        "attribution": {"project_id": project_id} if project_id else {},
    }

    resp = httpx.post(
        f"{GATEWAY_URL}/v1/actions:execute",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json=payload,
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()


# Usage
result = call_openai(
    workload_id="wl-my-app",
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Summarize the meeting notes."}],
    project_id="proj-my-app",
)
print(result["result"]["body"]["choices"][0]["message"]["content"])

Anthropic SDK — base_url swap

import os
import anthropic

client = anthropic.Anthropic(
    base_url=os.environ["GATEWAY_URL"],
    api_key=os.environ["API_KEY"],
)

# Build the full ActionRequest and send via the /v1/actions:execute endpoint
import httpx

resp = httpx.post(
    f"{os.environ['GATEWAY_URL']}/v1/actions:execute",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
        "schema": "mvgc.action_request.v2",
        "org_id": os.environ["ORG_ID"],
        "workload_id": "wl-my-app",
        "action": {
            "type": "ai.infer",
            "method": "POST",
            "target_host": "api.anthropic.com",
            "target_path": "/v1/messages",
            "params": {
                "model": "claude-haiku-4-5-20251001",
                "max_tokens": 50,
                "messages": [{"role": "user", "content": "Hello"}],
            },
        },
        "attribution": {"project_id": "proj-my-app"},
    },
    timeout=30,
)
result = resp.json()
print(result["result"]["body"]["content"][0]["text"])

Requests library (any provider)

For providers without a Python SDK, or when you want full control:

import os
import requests

def gateway_request(
    target_host: str,
    target_path: str,
    params: dict,
    workload_id: str = "wl-my-app",
    project_id: str = "proj-my-app",
) -> dict:
    resp = requests.post(
        f"{os.environ['GATEWAY_URL']}/v1/actions:execute",
        headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
        json={
            "schema": "mvgc.action_request.v2",
            "org_id": os.environ["ORG_ID"],
            "workload_id": workload_id,
            "action": {
                "type": "ai.infer",
                "method": "POST",
                "target_host": target_host,
                "target_path": target_path,
                "params": params,
            },
            "attribution": {"project_id": project_id},
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

A cleaner version:

import os
import httpx

def execute_action(target_host: str, target_path: str, params: dict) -> dict:
    with httpx.Client(timeout=30) as client:
        resp = client.post(
            f"{os.environ['GATEWAY_URL']}/v1/actions:execute",
            headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
            json={
                "schema": "mvgc.action_request.v2",
                "org_id": os.environ["ORG_ID"],
                "workload_id": "wl-my-app",
                "action": {
                    "type": "ai.infer",
                    "method": "POST",
                    "target_host": target_host,
                    "target_path": target_path,
                    "params": params,
                },
                "attribution": {"project_id": "proj-my-app"},
            },
        )
        resp.raise_for_status()
        return resp.json()

LangChain Integration

There are two ways to route LangChain applications through the gateway:

Proxy approachFirst-class (ChatAiGateway)
Best forMigrating existing LangChain appsNew builds
Workload attributionShared, via request headerPer-agent, per-call, in request body
Provider supportOpenAI, Anthropic, Mistral, and others; not GeminiAll providers including Gemini
Provider API keysAlias (gateway-side) or BYOKAlias (gateway-side) or BYOK
Code changesMinimal (base URL swap)Install axemere-gateway-langchain

Proxy approach

Point ChatOpenAI or ChatAnthropic at the gateway using base_url. The gateway recognizes the X-MVGC-* headers and routes the request:

import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(
    model="gpt-4o-mini",
    openai_api_key=os.environ["API_KEY"],
    openai_api_base=f"{os.environ['GATEWAY_URL']}/v1/actions:execute",
    default_headers={
        "X-MVGC-Org-ID": os.environ["ORG_ID"],
        "X-MVGC-Workload-ID": "wl-my-app",
    },
)

response = llm.invoke([HumanMessage(content="Hello")])
print(response.content)

Note: The proxy approach works with any OpenAI-compatible provider. For Gemini (Google's generateContent API) and other non-OpenAI-compatible providers, use ChatAiGateway below.

First-class integration with ChatAiGateway

ChatAiGateway from the axemere-gateway-langchain package is a drop-in LangChain chat model that constructs the full ActionRequest body natively. Attribution fields (workload ID, project ID, labels) are part of the request itself, not inferred from headers, so every gateway record has complete attribution data immediately visible in the console.

Provider API keys are managed gateway-side. No provider credentials on the client.

Install:

pip install axemere-gateway-langchain

Usage:

import os
from axemere.gateway import AiGatewayConfig
from axemere.gateway.langchain import ChatAiGateway
from langchain_core.messages import HumanMessage

# Reads AXEMERE_GATEWAY_TOKEN and AXEMERE_PROJECT_ID from environment
gateway_cfg = AiGatewayConfig.from_env()

# Each agent gets its own workload ID for per-agent cost attribution
researcher = ChatAiGateway(
    provider="anthropic",
    model="claude-haiku-4-5-20251001",
    config=gateway_cfg,
    workload_id="wl-researcher",
    labels={"agent": "researcher"},
)

response = researcher.invoke([HumanMessage(content="Research LLM production costs")])
print(response.content)

Required environment variables:

AXEMERE_GATEWAY_TOKEN=axemere_k_...   # from Console → Gateway Keys
AXEMERE_PROJECT_ID=prj-my-app         # attribute all costs to this project

Reference implementation

The LangChain + Axemere Gateway demo is a complete multi-agent research pipeline built with LangChain LCEL and ChatAiGateway. It covers all four integration modes (explicit and proxy, managed and self-hosted) and generates a self-contained HTML + Markdown cost report.

Five-agent pipeline:

AgentProvider & ModelWorkload ID
PlannerOpenAI gpt-4o-miniwl_lclg_planner
Researcher ×NAnthropic claude-haiku-4-5wl_lclg_researcher
AnalystMistral mistral-large-latestwl_lclg_analyst
ComparatorOpenAI + Anthropic + Gemini (parallel)wl_lclg_comparator
ReporterAnthropic claude-sonnet-4-6wl_lclg_reporter

The Comparator uses ChatAiGateway regardless of mode; Gemini's generateContent API is not compatible with the OpenAI proxy format, so the first-class integration is required.

A live example report shows the full HTML output including per-agent token counts and cost attribution.


Verify a Request Completed

Every successful response includes a record_id. Use it to retrieve the execution record and confirm that metering data was captured:

RECORD_ID=$(curl -s -X POST "$GATEWAY_URL/v1/actions:execute" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{...}' | jq -r '.record_id')

curl -s "$GATEWAY_URL/v1/records/$RECORD_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '{
    decision: .decision,
    tokens_in: .metering.tokens_in,
    tokens_out: .metering.tokens_out,
    cost_usd: .metering.cost_usd
  }'

To verify the cryptographic inclusion proof in the ledger:

RECORD_HASH=$(curl -s "$GATEWAY_URL/v1/records/$RECORD_ID" \
  -H "Authorization: Bearer $API_KEY" | jq -r '.record_hash')

curl -s "$GATEWAY_URL/v1/verify/$RECORD_HASH" \
  -H "Authorization: Bearer $API_KEY" | jq '.status'
# "verified" or "pending_inclusion"

Monitoring and Governance via the Console

Once traffic is flowing through the gateway, use the Axemere Console at https://console.axemere.ai to monitor and manage it.

Execution Records (/records): view every request the gateway processed. Filter by workload, decision (allow, deny, rate_limit), and date range. Click any record to view the full policy trace and metering data.

Approvals (/approvals): when a policy uses require_approval, pending requests appear here. Org admins can approve or deny with a reason; approved requests are immediately retried by the originating workload.

Quarantine (/quarantine): workloads that trigger the risk scorer above threshold are quarantined automatically. Review the risk signals, assess the context, and release the workload when satisfied.

See the console for up-to-date details on each of these views.


See also