Outpost Multi-Org Mode
By default, a single Arbitex Hybrid Outpost instance serves one organization. Multi-org mode unlocks the ability to serve multiple organizations from a single outpost fleet — each with its own policy fetch cycle, rate limit buckets, circuit breaker, and audit log stream. Organizations running in the same outpost remain fully isolated from one another at the data and control planes.
This guide covers the multi-org architecture, per-org isolation mechanisms, rate limiting, the org management API, fleet health aggregation, and the migration path from single-org to multi-org.
Multi-Org Overview
Section titled “Multi-Org Overview”Single-Org vs. Multi-Org
Section titled “Single-Org vs. Multi-Org”In single-org mode (the default), the outpost registers with exactly one organization on the Arbitex Platform at startup. All policy fetch cycles, DLP scans, audit events, and rate limits belong to that one org. This is the appropriate mode for organizations that deploy their own dedicated outpost fleet.
In multi-org mode, a single outpost process simultaneously serves up to MAX_ORGS_PER_OUTPOST organizations. Each organization is independently enrolled via the Platform, and the outpost maintains separate state for each — policy cache, circuit breaker, rate limit counters, and audit log routing. One outpost fleet can therefore cover multiple customer organizations without any cross-org data leakage.
Primary Use Cases
Section titled “Primary Use Cases”MSP (Managed Service Provider) deployments — An MSP running Arbitex for multiple customer organizations can deploy a single shared outpost fleet rather than one fleet per customer. Each customer organization enrolls independently via the Platform and receives the same isolation guarantees as if it had its own dedicated outpost. The MSP’s operations team monitors the entire fleet from a single health endpoint.
Multi-subsidiary on-premises deployments — A large enterprise that manages distinct subsidiary organizations under one Arbitex account can consolidate their on-premises outpost footprint. Each subsidiary retains independent policy control, audit logs, and rate limits through the Platform, while sharing the underlying compute.
Environment Variables
Section titled “Environment Variables”| Variable | Default | Description |
|---|---|---|
MULTI_ORG_MODE |
false |
Set to true to enable multi-org mode. When false, the outpost operates in single-org mode and ignores all other multi-org configuration. |
MAX_ORGS_PER_OUTPOST |
10 |
Maximum number of organizations this outpost will accept registrations for. Hard maximum is 50. Enrollment requests that would exceed this limit are rejected by the outpost with a 409 Conflict response. |
When you set MULTI_ORG_MODE=true, the outpost enters multi-org initialization at startup. If the outpost was previously running in single-org mode, the previously registered organization is automatically re-registered as the first org in the multi-org registry. No policy data is lost during the transition — see Migration from Single-Org to Multi-Org for the full step-by-step checklist.
Per-Org Policy Isolation
Section titled “Per-Org Policy Isolation”Policy management in multi-org mode is fully independent per organization. Each enrolled org has its own policy fetch cycle, its own policy cache, and its own circuit breaker that governs what happens when the Platform becomes temporarily unreachable for that org.
Independent Policy Fetch Cycles
Section titled “Independent Policy Fetch Cycles”The outpost runs a separate policy refresh goroutine for each enrolled organization. The refresh interval is configured globally via ORG_POLICY_REFRESH_INTERVAL and applies to all orgs. Each org’s refresh cycle starts from its enrollment timestamp, so orgs enrolled at different times naturally have staggered refresh schedules — this prevents thundering-herd behavior against the Platform API when many orgs refresh simultaneously.
On each refresh cycle, the outpost contacts the Platform’s policy endpoint scoped to that org’s org_id. The Platform returns the current policy version and DLP ruleset. If the returned version matches the cached version, the outpost skips deserialization and retains the existing in-memory policy.
Per-Org Circuit Breaker
Section titled “Per-Org Circuit Breaker”Each org has an independent circuit breaker that protects against cascading failures when the Platform is unreachable for a specific org. The circuit breaker operates in three states:
- Closed (normal) — Policy refresh calls are attempted on schedule. All requests are processed with the most recently fetched policy.
- Open (tripped) — The outpost has seen
ORG_CIRCUIT_BREAKER_THRESHOLDconsecutive failures for this org. Policy refresh calls are suspended forORG_CIRCUIT_BREAKER_RESETseconds. The outpost continues serving requests for this org using the last-known policy. The org’s status in the management API showscircuit_open. - Half-open (probing) — After the reset window expires, the circuit breaker allows one trial policy fetch. If the fetch succeeds, the circuit closes. If it fails, the circuit re-opens for another reset window.
Critically, a circuit break for one org has no effect on other orgs. If org A’s policy fetch fails repeatedly and its circuit opens, orgs B, C, and D continue refreshing their policies and serving requests normally. This isolation is the core safety guarantee of per-org circuit breaking.
Per-Org Audit Log Streams
Section titled “Per-Org Audit Log Streams”All audit events generated by the outpost — DLP scan results, policy decisions, rate limit events — are tagged with the originating org_id before being written to the audit log stream. In multi-org mode, the outpost maintains separate log partitions per org:
- Events from different orgs never intermingle in the same log partition.
- SIEM integrations that consume the outpost’s audit stream can filter by
org_idto scope ingestion to a specific organization. - Log retention policies set on the Platform per org are respected independently.
Isolation Environment Variables
Section titled “Isolation Environment Variables”| Variable | Default | Description |
|---|---|---|
MULTI_ORG_MODE |
false |
Enable multi-org mode. |
MAX_ORGS_PER_OUTPOST |
10 |
Maximum enrolled orgs (hard cap: 50). |
ORG_POLICY_REFRESH_INTERVAL |
60s |
How frequently each org’s policy is refreshed from the Platform. Accepts Go duration strings (e.g. 30s, 2m). |
ORG_CIRCUIT_BREAKER_THRESHOLD |
5 |
Number of consecutive policy fetch failures that trigger a circuit break for a given org. |
ORG_CIRCUIT_BREAKER_RESET |
300s |
Duration the circuit breaker remains open before transitioning to half-open for a trial fetch. Accepts Go duration strings. |
Per-Org Rate Limiting
Section titled “Per-Org Rate Limiting”Each enrolled organization has its own independent rate limit bucket. Rate limits are enforced at the outpost before a request reaches the DLP pipeline, and they are scoped strictly to the originating org. A high-volume org that approaches or exceeds its rate limit has no effect on the request throughput available to other orgs on the same outpost.
How Rate Limiting Works
Section titled “How Rate Limiting Works”When a request arrives, the outpost identifies the org from the request’s authentication token (JWT claim org_id). It then checks the token bucket for that org:
- If the bucket has tokens available, the request is admitted and one token is consumed.
- If the bucket is empty and the burst allowance (
ORG_RATE_LIMIT_BURST) has been exhausted, the outpost returns a429 Too Many Requestsresponse immediately without forwarding the request to the DLP pipeline.
The token bucket refills at ORG_RATE_LIMIT_RPS tokens per second, up to the ORG_RATE_LIMIT_BURST maximum. This means an org that has been idle can briefly sustain throughput up to ORG_RATE_LIMIT_BURST requests before being throttled back to ORG_RATE_LIMIT_RPS.
Rate Limit Response Headers
Section titled “Rate Limit Response Headers”When an org’s requests are admitted, the outpost includes the following headers in every response, scoped to that org’s bucket:
| Header | Description |
|---|---|
X-RateLimit-Limit |
The configured ORG_RATE_LIMIT_RPS for this org. |
X-RateLimit-Remaining |
Tokens remaining in this org’s bucket at the time of this response. |
X-RateLimit-Reset |
Unix timestamp (seconds) when the bucket will next be fully replenished. |
When a 429 is returned, the response additionally includes:
| Header | Description |
|---|---|
Retry-After |
Seconds until the org’s bucket will have at least one token available. |
All four headers are scoped to the requesting org. Clients from different orgs sharing the same outpost endpoint will see different values for X-RateLimit-Remaining reflecting their own org’s consumption, not a shared counter.
Rate Limiting Environment Variables
Section titled “Rate Limiting Environment Variables”| Variable | Default | Description |
|---|---|---|
ORG_RATE_LIMIT_RPS |
100 |
Sustained request rate allowed per org, in requests per second. Applies uniformly to all enrolled orgs. |
ORG_RATE_LIMIT_BURST |
200 |
Maximum burst size for each org’s token bucket. Must be greater than or equal to ORG_RATE_LIMIT_RPS. |
Org Management API
Section titled “Org Management API”The outpost exposes an admin API for inspecting the state of all enrolled organizations and their policy fetch status. All admin API endpoints require a valid Authorization: Bearer <ADMIN_API_TOKEN> header.
List All Enrolled Orgs
Section titled “List All Enrolled Orgs”GET /admin/api/orgs
Returns the list of all organizations currently enrolled with this outpost, along with their operational status.
Response schema — OrgListResponse:
| Field | Type | Description |
|---|---|---|
mode |
string | Outpost operating mode: single or multi. |
orgs |
array | Array of org status objects (see below). |
Per-org fields:
| Field | Type | Description |
|---|---|---|
org_id |
string | Unique organization identifier assigned by the Arbitex Platform. |
org_name |
string | Human-readable organization display name. |
status |
string | Current operational status: active, suspended, or circuit_open. |
policy_loaded |
boolean | Whether a valid policy has been successfully fetched and is in memory for this org. |
last_policy_fetch |
string | ISO 8601 UTC timestamp of the most recent successful policy fetch for this org, or null if no policy has been fetched yet. |
Example:
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ https://outpost.internal:9090/admin/api/orgs | jq .Example response:
{ "mode": "multi", "orgs": [ { "org_id": "org_01HXYZ1234ABCDEF", "org_name": "Acme Corp", "status": "active", "policy_loaded": true, "last_policy_fetch": "2026-03-15T14:28:01Z" }, { "org_id": "org_01HXYZ5678GHIJKL", "org_name": "Globex Industries", "status": "circuit_open", "policy_loaded": true, "last_policy_fetch": "2026-03-15T13:55:42Z" }, { "org_id": "org_01HXYZ9012MNOPQR", "org_name": "Initech LLC", "status": "active", "policy_loaded": true, "last_policy_fetch": "2026-03-15T14:27:18Z" } ]}In this example, Globex Industries has a tripped circuit breaker. The outpost is still serving requests for Globex using the last-known policy (note policy_loaded: true), but policy refresh calls have been suspended until the reset window expires. Acme and Initech are unaffected.
Get Policy Status for a Specific Org
Section titled “Get Policy Status for a Specific Org”GET /admin/api/orgs/{org_id}/policy-status
Returns detailed policy fetch and cache statistics for a single enrolled organization.
Response schema — PolicyStatusResponse:
| Field | Type | Description |
|---|---|---|
org_id |
string | Organization identifier. |
policy_loaded |
boolean | Whether a valid policy is currently in memory. |
last_fetched |
string | ISO 8601 UTC timestamp of the last successful policy fetch, or null. |
policy_version |
string | Opaque version string of the currently loaded policy, as returned by the Platform. null if no policy is loaded. |
cache_hits |
integer | Number of times a policy fetch returned the same version as the cached policy (no re-deserialization was required). Reset on outpost restart. |
cache_misses |
integer | Number of times a policy fetch returned a new version, triggering a full policy reload. Reset on outpost restart. |
circuit_breaker_state |
string | Current circuit breaker state: closed, open, or half_open. |
Example:
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "https://outpost.internal:9090/admin/api/orgs/org_01HXYZ5678GHIJKL/policy-status" | jq .Example response:
{ "org_id": "org_01HXYZ5678GHIJKL", "policy_loaded": true, "last_fetched": "2026-03-15T13:55:42Z", "policy_version": "v2026.03.15-r4", "cache_hits": 142, "cache_misses": 3, "circuit_breaker_state": "open"}Error response — org not found (404):
{ "error": "org_not_found", "message": "No organization with id 'org_01HXYZ5678GHIJKL' is enrolled with this outpost."}Health Aggregation for Fleet Monitoring
Section titled “Health Aggregation for Fleet Monitoring”The outpost provides a fleet-level health summary endpoint that aggregates the state of all enrolled orgs into a single status signal. This endpoint is designed for use by monitoring systems, load balancers, and alerting pipelines that need a single health check URL for the entire outpost.
Health Summary Endpoint
Section titled “Health Summary Endpoint”GET /admin/api/health/summary
Returns an aggregated health status for the outpost and all enrolled organizations.
Response schema — HealthSummaryResponse:
| Field | Type | Description |
|---|---|---|
status |
string | Aggregated health status: healthy, degraded, or unhealthy. See status rules below. |
uptime_seconds |
integer | Seconds since the outpost process started. |
version |
string | Outpost software version string. |
mode |
string | Operating mode: single or multi. |
active_orgs |
integer | Number of enrolled orgs with status: active. |
inflight_requests |
integer | Number of requests currently being processed across all orgs. |
cache_hit_rate |
float | Ratio of policy cache hits to total policy fetch attempts, across all orgs, since last restart. Range 0.0 to 1.0. |
circuit_breaker_open |
integer | Number of orgs whose circuit breaker is currently in the open or half_open state. |
Status determination rules:
| Condition | Reported Status |
|---|---|
| No orgs have an open circuit breaker | healthy |
| At least one org has an open circuit breaker, but fewer than half of enrolled orgs | degraded |
| Half or more of enrolled orgs have an open circuit breaker | unhealthy |
Example:
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ https://outpost.internal:9090/admin/api/health/summary | jq .Example response — degraded (one of three orgs has an open circuit breaker):
{ "status": "degraded", "uptime_seconds": 86402, "version": "1.14.2", "mode": "multi", "active_orgs": 2, "inflight_requests": 14, "cache_hit_rate": 0.979, "circuit_breaker_open": 1}Integrating Health Aggregation with Monitoring
Section titled “Integrating Health Aggregation with Monitoring”The /admin/api/health/summary endpoint is intended to be scraped by your monitoring platform on a short interval (30–60 seconds is typical). Recommended alert thresholds:
- Alert
warningwhenstatus == "degraded"for more than 10 minutes (indicates a persistent Platform connectivity issue for at least one org). - Alert
criticalwhenstatus == "unhealthy"immediately (majority of orgs are running on stale policies). - Alert on
circuit_breaker_open > 0with a 5-minute sustained window to catch transient blips without paging. - Alert when
cache_hit_rate < 0.90over a 1-hour window, which may indicate unexpected policy churn or Platform-side policy edit activity.
If you are using Prometheus, you can scrape the outpost’s /metrics endpoint (if METRICS_ENABLED=true) to obtain these values as Prometheus gauge metrics, which enables richer alerting via PromQL. See the Prometheus Alerts guide for pre-built alert rules that cover multi-org scenarios.
Migration from Single-Org to Multi-Org
Section titled “Migration from Single-Org to Multi-Org”Migrating an existing single-org outpost deployment to multi-org mode requires a restart but is otherwise low-risk. The existing org is automatically preserved as the first enrolled org in the multi-org registry. Follow this checklist to complete the migration safely.
Migration Checklist
Section titled “Migration Checklist”Step 1 — Back up your current configuration.
Before making any changes, export your current environment configuration to a file stored outside the outpost container.
# For Docker deployments: export the current env filecp /etc/arbitex-outpost/outpost.env /etc/arbitex-outpost/outpost.env.bak-$(date +%Y%m%d)For Kubernetes deployments, capture your current ConfigMap and Secret values:
kubectl get configmap arbitex-outpost-config -n arbitex -o yaml > outpost-config-backup.yamlkubectl get secret arbitex-outpost-secrets -n arbitex -o yaml > outpost-secrets-backup.yamlStep 2 — Set MULTI_ORG_MODE=true.
Add or update the following variables in your outpost environment configuration:
MULTI_ORG_MODE=trueMAX_ORGS_PER_OUTPOST=10ORG_POLICY_REFRESH_INTERVAL=60sORG_CIRCUIT_BREAKER_THRESHOLD=5ORG_CIRCUIT_BREAKER_RESET=300sORG_RATE_LIMIT_RPS=100ORG_RATE_LIMIT_BURST=200Adjust MAX_ORGS_PER_OUTPOST to reflect the number of organizations you intend to serve. Do not set this higher than you need — the outpost allocates per-org goroutines and state at startup for the registered orgs, and MAX_ORGS_PER_OUTPOST acts as a hard ceiling enforced at enrollment time.
Step 3 — Restart the outpost.
# Docker Composedocker compose restart arbitex-outpost
# Kuberneteskubectl rollout restart deployment/arbitex-outpost -n arbitexkubectl rollout status deployment/arbitex-outpost -n arbitexWatch the outpost startup logs for the following messages, which confirm successful multi-org initialization:
INFO multi_org: mode enabled max_orgs=10INFO multi_org: auto-registered existing org org_id=org_01HXYZ1234ABCDEF org_name="Acme Corp"INFO policy: refresh started org_id=org_01HXYZ1234ABCDEF interval=60sINFO outpost: ready version=1.14.2 mode=multiIf you see mode=single in the ready log line, the environment variable was not picked up correctly. Verify that MULTI_ORG_MODE=true is in the correct scope for your deployment (environment variable, not a config file the outpost has not reloaded).
Step 4 — Verify the existing org is registered.
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ https://outpost.internal:9090/admin/api/orgs | jq '.orgs[] | {org_id, org_name, status, policy_loaded}'You should see your original organization with status: active and policy_loaded: true. Confirm last_policy_fetch is recent (within the last ORG_POLICY_REFRESH_INTERVAL).
Step 5 — Enroll additional organizations via the Platform.
In the Arbitex Cloud Portal, navigate to Settings → Outpost Fleet for each organization you want to add to this outpost. Select Enroll in existing outpost and target this outpost by its fleet ID. The Platform will push the enrollment to the outpost, which registers the new org and begins its policy fetch cycle.
For each newly enrolled org, confirm enrollment via the API:
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "https://outpost.internal:9090/admin/api/orgs/org_01HXYZ5678GHIJKL/policy-status" | jq .Wait until policy_loaded: true before directing traffic from that org’s users to this outpost.
Step 6 — Update monitoring dashboards.
After migration, update your monitoring infrastructure to account for the new multi-org health model:
- Replace single-org health checks pointing to
/admin/api/healthwith the fleet-level/admin/api/health/summary. - Update Grafana dashboards to display per-org circuit breaker state and policy staleness (see Grafana Dashboards).
- Add alerts for
circuit_breaker_open > 0as described in Health Aggregation for Fleet Monitoring. - If you are forwarding audit logs to a SIEM, update your log parsing rules to capture the
org_idfield that is now present on all audit events.
Summary and Next Steps
Section titled “Summary and Next Steps”Multi-org mode turns a single Arbitex Hybrid Outpost fleet into a shared DLP infrastructure layer capable of serving multiple organizations simultaneously. Policy isolation, circuit breaking, rate limiting, and audit log partitioning are all scoped per org, so no single organization’s issues can affect others sharing the same outpost.
Key operational takeaways:
- Set
MULTI_ORG_MODE=trueandMAX_ORGS_PER_OUTPOSTbefore enrolling additional orgs via the Platform. - Monitor
/admin/api/health/summaryfor fleet-level health status and alert oncircuit_breaker_open > 0. - Use
/admin/api/orgs/{org_id}/policy-statusto diagnose policy staleness for a specific org when issues are reported. - Plan a maintenance window for the restart required when enabling multi-org mode.
Related guides:
- Outpost Plugin System — plugin hooks, webhook emitter, and custom entity redaction patterns
- Outpost Health Monitoring — detailed health check configuration and alerting
- Prometheus Alerts — pre-built alert rules including multi-org circuit breaker alerts
- Grafana Dashboards — dashboard templates with per-org drill-down panels
- Outpost Security Hardening — admin API access controls, mTLS, and network isolation
- Outpost Performance Tuning — concurrency, memory, and connection pool tuning for high-org-count deployments
- Outpost Administration — budget enforcement, CredInt, health monitoring, JWT validation, PVC recovery, security hardening, SIEM direct integration