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
- Step 1 — Register a Workload
- Step 2 — Register a Credential
- Step 3 — Configure a Policy Bundle
- Step 4 — Send Requests
- Python SDK Integration
- LangChain Integration
- Verify a Request Completed
- See also
Prerequisites
You need:
- Gateway URL (managed:
https://<env>.gcp.gw.axemere.ai; self-hosted:http://localhost:7080) - Admin token (
MVGC_ADMIN_TOKENon 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:
| Mode | How it works | When to use |
|---|---|---|
byok (bring-your-own-key) | Caller supplies the raw API key per-request in action.params or via credential_hint.key | Dev/testing; caller controls the key |
alias | Gateway stores the API key; caller references it by credential_id | Production; 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 approach | First-class (ChatAiGateway) | |
|---|---|---|
| Best for | Migrating existing LangChain apps | New builds |
| Workload attribution | Shared, via request header | Per-agent, per-call, in request body |
| Provider support | OpenAI, Anthropic, Mistral, and others; not Gemini | All providers including Gemini |
| Provider API keys | Alias (gateway-side) or BYOK | Alias (gateway-side) or BYOK |
| Code changes | Minimal (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
generateContentAPI) and other non-OpenAI-compatible providers, useChatAiGatewaybelow.
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:
| Agent | Provider & Model | Workload ID |
|---|---|---|
| Planner | OpenAI gpt-4o-mini | wl_lclg_planner |
| Researcher ×N | Anthropic claude-haiku-4-5 | wl_lclg_researcher |
| Analyst | Mistral mistral-large-latest | wl_lclg_analyst |
| Comparator | OpenAI + Anthropic + Gemini (parallel) | wl_lclg_comparator |
| Reporter | Anthropic claude-sonnet-4-6 | wl_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
- Developer Integration Guide: full request schema reference, streaming, delegation tokens
- Pricing Reference: per-model cost rates
- Configuration: Policies: full policy DSL reference
- Configuration: Workloads: workload field reference
- Configuration: Credentials: credential field reference
- Admin API Reference: all admin endpoints
- Troubleshooting: common errors and how to resolve them