Quickstart: Complete Onboarding
This guide walks through the complete Arbitex onboarding flow, from first start to a fully configured gateway with DLP enforcement, policy rules, webhook notifications, and audit verification. If you want the 5-minute version, see the Getting Started Quickstart.
Prerequisites
Section titled “Prerequisites”- Docker 24.x+ and Docker Compose v2 — verify with
docker compose version - API key for at least one AI provider — OpenAI, Anthropic, Google Gemini, Azure OpenAI, Groq, Mistral, Cohere, AWS Bedrock, or any OpenAI-compatible endpoint
- curl — for sending test requests from the terminal
Step 1 — Start the gateway
Section titled “Step 1 — Start the gateway”Clone the Outpost repository and start the stack:
git clone https://github.com/arbitex/outpost.gitcd outpostcp .env.example .envdocker compose up -dDocker Compose starts the following services:
| Service | Port | Description |
|---|---|---|
outpost |
8300 | AI proxy — accepts chat completion requests |
outpost |
8301 (localhost only) | Admin API — configuration and management |
Confirm the gateway is ready:
curl http://localhost:8300/health{ "status": "ok", "version": "0.34.0", "dlp_tiers_active": ["regex", "ner"], "siem_status": "disabled", "cert_days_remaining": 87, "policy_bundle_age_seconds": 12.4, "credint_loaded": false, "geoip_anon_db_loaded": false, "geoip_city_loaded": false, "audit_logger_healthy": true, "budget_tracker_healthy": true}"status": "ok" confirms the gateway is running. For full readiness (policy bundle loaded and all critical subsystems up), use GET /ready instead.
Step 2 — Configure your first provider
Section titled “Step 2 — Configure your first provider”Add your AI provider credentials so the gateway can route requests.
# Set your gateway admin key from .envexport ARBITEX_ADMIN_KEY="<arb_admin_...>"
# Add an Anthropic providercurl -X POST http://localhost:8301/api/v1/admin/providers \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "anthropic-primary", "provider_type": "anthropic", "api_key": "sk-ant-your-anthropic-key", "is_default": true }'Expected response:
{ "id": "prov_01ABC123", "name": "anthropic-primary", "provider_type": "anthropic", "is_default": true, "status": "active", "created_at": "2026-03-15T00:00:00Z"}"status": "active" confirms the gateway validated your API key.
Supported providers
Section titled “Supported providers”| Provider type | provider_type value |
|---|---|
| Anthropic | anthropic |
| OpenAI | openai |
| Azure OpenAI | azure_openai |
| Google Gemini | google |
| AWS Bedrock | bedrock |
| Groq | groq |
| Mistral | mistral |
| Cohere | cohere |
| OpenAI-compatible | openai_compatible |
For OpenAI-compatible providers, also set "base_url" to the endpoint URL.
Step 3 — Create a proxy API key
Section titled “Step 3 — Create a proxy API key”Create an API key that your applications will use to authenticate with the gateway:
curl -X POST http://localhost:8301/api/v1/admin/api-keys \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "dev-testing", "role": "user"}'{ "id": "key_01DEF456", "name": "dev-testing", "key": "arb_live_xxxxxxxxxxxxxxxxxxxxxxxx", "role": "user", "created_at": "2026-03-15T00:00:00Z"}export ARBITEX_API_KEY="<arb_live_...>"Step 4 — Send your first request
Section titled “Step 4 — Send your first request”The gateway accepts requests in the standard OpenAI chat completions format. Use provider/model-id in the model field.
curl http://localhost:8300/v1/chat/completions \ -H "Authorization: Bearer $ARBITEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-4-20250514", "messages": [ { "role": "user", "content": "What is the capital of France? Answer in one sentence." } ], "max_tokens": 64 }'import osfrom openai import OpenAI
client = OpenAI( base_url="http://localhost:8300/v1", api_key=os.environ["ARBITEX_API_KEY"],)
response = client.chat.completions.create( model="anthropic/claude-sonnet-4-20250514", messages=[ { "role": "user", "content": "What is the capital of France? Answer in one sentence.", } ], max_tokens=64,)
print(response.choices[0].message.content)# → The capital of France is Paris.import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:8300/v1", apiKey: process.env.ARBITEX_API_KEY,});
const response = await client.chat.completions.create({ model: "anthropic/claude-sonnet-4-20250514", messages: [ { role: "user", content: "What is the capital of France? Answer in one sentence.", }, ], max_tokens: 64,});
console.log(response.choices[0].message.content);// → The capital of France is Paris.Response headers the gateway adds to every request:
| Header | Meaning |
|---|---|
X-Arbitex-Request-Id |
Unique ID — use to look up the audit log entry |
X-Arbitex-Policy-Outcome |
Policy Engine decision: ALLOW, BLOCK, or REDACT |
X-Arbitex-DLP-Findings |
Number of sensitive data findings in the prompt/response |
X-Arbitex-Routing-Mode |
How the request was routed: Single, Fallback, or Balanced |
Step 5 — Verify in the audit log
Section titled “Step 5 — Verify in the audit log”Every request is recorded in the tamper-evident audit log. Retrieve the most recent entry:
curl "http://localhost:8301/api/v1/admin/audit-logs/?limit=1" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY"{ "items": [ { "id": "evt_01ABC...", "user_id": "usr_dev-testing", "action": "chat.completion", "model_id": "claude-sonnet-4-20250514", "provider": "anthropic", "outcome": "ALLOW", "dlp_findings": [], "credint_hit": false, "token_count_input": 20, "token_count_output": 8, "latency_ms": 843, "created_at": "2026-03-15T00:00:00.000Z", "src_ip": "172.18.0.1" } ], "total": 1, "limit": 1, "offset": 0}| Field | What it tells you |
|---|---|
outcome |
Policy Engine decision — ALLOW, BLOCK, or REDACT |
dlp_findings |
Array of DLP detections — empty means no sensitive data found |
latency_ms |
End-to-end request time through the gateway |
credint_hit |
Whether the prompt matched the credential intelligence corpus |
Step 6 — Set up DLP policies
Section titled “Step 6 — Set up DLP policies”DLP policies define what happens when sensitive data is detected. The pipeline runs five tiers:
| Tier | Engine | Latency | What it catches |
|---|---|---|---|
| 0 | TF-IDF | <1 ms | Prompt injection pre-filter — blocks obvious injection attempts |
| 1 | Regex | ~1 ms | Credit cards, SSNs, API keys, emails — 70+ built-in patterns |
| 2 | NER (spaCy) | ~20 ms | Named entities: names, addresses, phone numbers |
| 3 | DeBERTa | ~150 ms | Contextual validation of ambiguous hits (optional, requires GPU) |
| 4 | CredInt | ~5 ms | Credential exposure checks against breach corpus |
Create a policy pack that blocks credit card leakage
Section titled “Create a policy pack that blocks credit card leakage”# Create the policy packcurl -X POST http://localhost:8301/api/v1/admin/policy-packs \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "block-pii", "description": "Block requests containing credit card numbers" }'{ "id": "pack_01GHI789", "name": "block-pii", "status": "inactive"}PACK_ID="pack_01GHI789"
# Add a rule to the packcurl -X POST "http://localhost:8301/api/v1/admin/policy-packs/$PACK_ID/rules" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "block-credit-cards", "condition": { "type": "dlp_match", "entity_type": "CREDIT_CARD" }, "action": "BLOCK" }'
# Activate the packcurl -X POST "http://localhost:8301/api/v1/admin/policy-packs/$PACK_ID/activate" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY"Test the DLP block
Section titled “Test the DLP block”curl http://localhost:8300/v1/chat/completions \ -H "Authorization: Bearer $ARBITEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-4-20250514", "messages": [ { "role": "user", "content": "My card number is 4532-1234-5678-9012. Can you confirm it is valid?" } ] }'Expected response (HTTP 400):
{ "error": { "message": "Request blocked by DLP policy: sensitive data detected in prompt", "type": "dlp_block", "code": "dlp_policy_violation", "request_id": "req_01JKL..." }}The request never reached the model. Verify in the audit log:
curl "http://localhost:8301/api/v1/admin/audit-logs/?limit=1" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" | python3 -m json.tool{ "outcome": "BLOCK", "dlp_findings": [ { "tier": "regex", "type": "CREDIT_CARD", "location": "prompt", "confidence": 1.0, "redacted": false } ]}Try redaction instead of blocking
Section titled “Try redaction instead of blocking”Create a second rule that redacts email addresses rather than blocking:
curl -X POST "http://localhost:8301/api/v1/admin/policy-packs/$PACK_ID/rules" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "redact-emails", "condition": { "type": "dlp_match", "entity_type": "EMAIL_ADDRESS" }, "action": "REDACT" }'Now send a request containing an email:
curl http://localhost:8300/v1/chat/completions \ -H "Authorization: Bearer $ARBITEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-4-20250514", "messages": [ { "role": "user", "content": "Draft a reply to [email protected] about the project status." } ], "max_tokens": 128 }'The request succeeds — but the email address is replaced with [EMAIL_ADDRESS] before reaching the model. The audit log records the redaction:
{ "outcome": "ALLOW", "dlp_findings": [ { "tier": "regex", "type": "EMAIL_ADDRESS", "location": "prompt", "confidence": 1.0, "redacted": true } ]}Step 7 — Configure webhooks
Section titled “Step 7 — Configure webhooks”Webhooks push real-time event notifications to external HTTP endpoints. Set up a webhook to receive DLP alerts.
Create a webhook
Section titled “Create a webhook”curl -X POST http://localhost:8301/api/v1/admin/webhooks/ \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Security Alerts", "url": "https://hooks.example.com/arbitex/events", "events": ["dlp_trigger", "quota_exceeded"], "secret": "your-webhook-shared-secret-here", "enabled": true }'{ "id": "3f8a1b2c-...", "name": "Security Alerts", "url": "https://hooks.example.com/arbitex/events", "events": ["dlp_trigger", "quota_exceeded"], "enabled": true, "created_at": "2026-03-15T10:00:00Z"}Supported event types
Section titled “Supported event types”| Event type | When it fires |
|---|---|
new_conversation |
A user creates a new conversation |
dlp_trigger |
A DLP rule matches content in a message |
quota_exceeded |
A user or group quota is exceeded |
bundle_state_change |
A compliance bundle changes state |
Test the webhook
Section titled “Test the webhook”Send a test delivery to verify connectivity:
WEBHOOK_ID="3f8a1b2c-..."
curl -X POST "http://localhost:8301/api/v1/admin/webhooks/$WEBHOOK_ID/test" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY"{ "success": true, "status_code": 200, "error": null}Verify HMAC signatures
Section titled “Verify HMAC signatures”Every webhook delivery includes an X-Webhook-Signature header — the HMAC-SHA256 of the raw JSON body, computed with your shared secret.
import hashlib, hmac
def verify_signature(body_bytes: bytes, secret: str, header_sig: str) -> bool: expected = hmac.new(secret.encode(), body_bytes, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header_sig)Webhook payload format
Section titled “Webhook payload format”{ "event_type": "dlp_trigger", "payload": { "request_id": "req_01HZ8...", "user_id": "usr_dev-testing", "entity_type": "CREDIT_CARD", "action_taken": "BLOCK", "tier": "regex" }, "timestamp": "2026-03-15T14:22:00Z"}Step 8 — Create a policy rule
Section titled “Step 8 — Create a policy rule”Beyond DLP-specific rules, the policy engine supports a range of condition types for fine-grained control.
Block a specific model
Section titled “Block a specific model”curl -X POST "http://localhost:8301/api/v1/admin/policy-packs/$PACK_ID/rules" \ -H "Authorization: Bearer $ARBITEX_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "block-legacy-models", "condition": { "type": "model_match", "model_id": "gpt-3.5-turbo" }, "action": "BLOCK" }'Available condition types
Section titled “Available condition types”| Condition type | Description |
|---|---|
dlp_match |
Matches when a DLP entity type is detected |
model_match |
Matches a specific model or model pattern |
provider_match |
Matches a provider type |
user_match |
Matches a specific user or group |
content_categories |
Matches content domain/subcategory |
ip_match |
Matches source IP address/range |
time_match |
Matches time-of-day windows |
Available actions
Section titled “Available actions”| Action | Behaviour |
|---|---|
ALLOW |
Permit the request |
BLOCK |
Reject the request with an error |
REDACT |
Replace matched content and continue |
ROUTE_TO |
Forward to a different provider/model |
PROMPT |
Hold for admin approval |
CANCEL |
Silently drop the request |
ALLOW_WITH_OVERRIDE |
Allow but flag for review |
For a complete deep-dive, see the Policy Engine guide.
Step 9 — List available models
Section titled “Step 9 — List available models”Query the models endpoint to see all models available through your configured providers:
curl http://localhost:8300/v1/models \ -H "Authorization: Bearer $ARBITEX_API_KEY"{ "object": "list", "data": [ { "id": "anthropic/claude-sonnet-4-20250514", "object": "model", "provider": "anthropic" }, { "id": "anthropic/claude-haiku-4-5-20251001", "object": "model", "provider": "anthropic" } ]}Use any id value from this list in the model field of your chat completion requests.
Onboarding checklist
Section titled “Onboarding checklist”After completing this guide, verify each component is working:
| Step | Verification |
|---|---|
| Gateway running | GET /health returns "status": "ok" |
| Provider configured | Provider shows "status": "active" |
| API key created | Proxy key starts with arb_live_ |
| Request routed | Chat completion returns a model response |
| Audit log recording | GET /api/v1/admin/audit-logs/?limit=1 returns your request |
| DLP block working | Credit card test returns dlp_policy_violation |
| DLP redact working | Email test returns response with [EMAIL_ADDRESS] |
| Webhook receiving | POST /api/v1/admin/webhooks/{id}/test returns "success": true |
| Policy active | Policy pack shows "status": "active" |
What’s next
Section titled “What’s next”| Next step | Guide |
|---|---|
| Deploy to production (Docker, Kubernetes, air-gap) | Deployment Guide |
| Configure DLP tiers and custom regex patterns | DLP Pipeline Configuration |
| Set up policy packs with approval workflows | Policy Engine |
| Add fallback providers and load balancing | Routing and Failover |
| Connect your SIEM for real-time event forwarding | SIEM Integration |
| Configure user quotas and budget enforcement | Quota Management |
| Review deployment topologies (SaaS, Hybrid, Air-Gap) | Deployment Topologies |
| Monitor webhook deliveries and dead letters | Webhook Operations |
Troubleshooting
Section titled “Troubleshooting”Gateway does not start
Section titled “Gateway does not start”Check Docker logs:
docker compose logs outpostCommon causes:
- Port 8300 or 8301 already in use — change
PROXY_PORT/ADMIN_PORTin.env - Missing
.envfile — runcp .env.example .envfirst
401 Unauthorized on proxy requests
Section titled “401 Unauthorized on proxy requests”Your API key is invalid or you are using the admin key instead of a proxy key. Proxy keys start with arb_live_. Create one via POST /api/v1/admin/api-keys.
503 Service Unavailable
Section titled “503 Service Unavailable”The configured provider returned an error. Check provider credentials and confirm your upstream API key has remaining quota.
400 Bad Request with dlp_policy_violation
Section titled “400 Bad Request with dlp_policy_violation”A DLP policy blocked the request. Check the audit log entry for the dlp_findings array to see which entity type triggered the block.
Wrong model ID format
Section titled “Wrong model ID format”Use the provider/model-id format: anthropic/claude-sonnet-4-20250514, openai/gpt-4o, google/gemini-2.0-flash. Run GET /v1/models to see all available models.
Webhook deliveries not arriving
Section titled “Webhook deliveries not arriving”- Check
GET /api/v1/admin/webhooks/{id}— reviewrecent_deliveriesforlast_errorvalues. - Confirm the webhook is
"enabled": true. - Test connectivity with
POST /api/v1/admin/webhooks/{id}/test. - Verify the target URL is reachable from the gateway and is not a private/loopback IP (blocked by SSRF protection).