Skip to content

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.

  • 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

Clone the Outpost repository and start the stack:

Terminal window
git clone https://github.com/arbitex/outpost.git
cd outpost
cp .env.example .env
docker compose up -d

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

Terminal window
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.


Add your AI provider credentials so the gateway can route requests.

Terminal window
# Set your gateway admin key from .env
export ARBITEX_ADMIN_KEY="<arb_admin_...>"
# Add an Anthropic provider
curl -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.

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.


Create an API key that your applications will use to authenticate with the gateway:

Terminal window
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"
}
Terminal window
export ARBITEX_API_KEY="<arb_live_...>"

The gateway accepts requests in the standard OpenAI chat completions format. Use provider/model-id in the model field.

Terminal window
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
}'

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

Every request is recorded in the tamper-evident audit log. Retrieve the most recent entry:

Terminal window
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

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”
Terminal window
# Create the policy pack
curl -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"
}
Terminal window
PACK_ID="pack_01GHI789"
# Add a rule to the pack
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-credit-cards",
"condition": {
"type": "dlp_match",
"entity_type": "CREDIT_CARD"
},
"action": "BLOCK"
}'
# Activate the pack
curl -X POST "http://localhost:8301/api/v1/admin/policy-packs/$PACK_ID/activate" \
-H "Authorization: Bearer $ARBITEX_ADMIN_KEY"
Terminal window
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:

Terminal window
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
}
]
}

Create a second rule that redacts email addresses rather than blocking:

Terminal window
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:

Terminal window
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
}
]
}

Webhooks push real-time event notifications to external HTTP endpoints. Set up a webhook to receive DLP alerts.

Terminal window
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"
}
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

Send a test delivery to verify connectivity:

Terminal window
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
}

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)
{
"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"
}

Beyond DLP-specific rules, the policy engine supports a range of condition types for fine-grained control.

Terminal window
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"
}'
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
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.


Query the models endpoint to see all models available through your configured providers:

Terminal window
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.


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"

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

Check Docker logs:

Terminal window
docker compose logs outpost

Common causes:

  • Port 8300 or 8301 already in use — change PROXY_PORT / ADMIN_PORT in .env
  • Missing .env file — run cp .env.example .env first

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.

The configured provider returned an error. Check provider credentials and confirm your upstream API key has remaining quota.

A DLP policy blocked the request. Check the audit log entry for the dlp_findings array to see which entity type triggered the block.

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.

  1. Check GET /api/v1/admin/webhooks/{id} — review recent_deliveries for last_error values.
  2. Confirm the webhook is "enabled": true.
  3. Test connectivity with POST /api/v1/admin/webhooks/{id}/test.
  4. Verify the target URL is reachable from the gateway and is not a private/loopback IP (blocked by SSRF protection).