Skip to content

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.


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.

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.

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.


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.

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.

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_THRESHOLD consecutive failures for this org. Policy refresh calls are suspended for ORG_CIRCUIT_BREAKER_RESET seconds. The outpost continues serving requests for this org using the last-known policy. The org’s status in the management API shows circuit_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.

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_id to scope ingestion to a specific organization.
  • Log retention policies set on the Platform per org are respected independently.
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.

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.

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 a 429 Too Many Requests response 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.

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.

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.

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.

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:

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

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

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.

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:

Terminal window
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 warning when status == "degraded" for more than 10 minutes (indicates a persistent Platform connectivity issue for at least one org).
  • Alert critical when status == "unhealthy" immediately (majority of orgs are running on stale policies).
  • Alert on circuit_breaker_open > 0 with a 5-minute sustained window to catch transient blips without paging.
  • Alert when cache_hit_rate < 0.90 over 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.


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.

Step 1 — Back up your current configuration.

Before making any changes, export your current environment configuration to a file stored outside the outpost container.

Terminal window
# For Docker deployments: export the current env file
cp /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:

Terminal window
kubectl get configmap arbitex-outpost-config -n arbitex -o yaml > outpost-config-backup.yaml
kubectl get secret arbitex-outpost-secrets -n arbitex -o yaml > outpost-secrets-backup.yaml

Step 2 — Set MULTI_ORG_MODE=true.

Add or update the following variables in your outpost environment configuration:

Terminal window
MULTI_ORG_MODE=true
MAX_ORGS_PER_OUTPOST=10
ORG_POLICY_REFRESH_INTERVAL=60s
ORG_CIRCUIT_BREAKER_THRESHOLD=5
ORG_CIRCUIT_BREAKER_RESET=300s
ORG_RATE_LIMIT_RPS=100
ORG_RATE_LIMIT_BURST=200

Adjust 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.

Terminal window
# Docker Compose
docker compose restart arbitex-outpost
# Kubernetes
kubectl rollout restart deployment/arbitex-outpost -n arbitex
kubectl rollout status deployment/arbitex-outpost -n arbitex

Watch the outpost startup logs for the following messages, which confirm successful multi-org initialization:

INFO multi_org: mode enabled max_orgs=10
INFO multi_org: auto-registered existing org org_id=org_01HXYZ1234ABCDEF org_name="Acme Corp"
INFO policy: refresh started org_id=org_01HXYZ1234ABCDEF interval=60s
INFO outpost: ready version=1.14.2 mode=multi

If 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.

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

Terminal window
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/health with 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 > 0 as 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_id field that is now present on all audit events.

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=true and MAX_ORGS_PER_OUTPOST before enrolling additional orgs via the Platform.
  • Monitor /admin/api/health/summary for fleet-level health status and alert on circuit_breaker_open > 0.
  • Use /admin/api/orgs/{org_id}/policy-status to 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: