API reference
API overview
Two surfaces, two credentials, one convention. Base URLs, authentication, status codes, error shapes, pagination and rate limits — everything common to every Arcus endpoint.
Base URL#
https://your-arcus-host/v1All endpoints are under /v1. The one exception is the health probe:
curl -sS https://your-arcus-host/health{ "status": "ok", "timestamp": "2026-08-24T09:14:02.881Z" }Unauthenticated, intended for load balancers and uptime monitors. It reports that the process is serving requests — not that PostgreSQL and Redis are healthy. For that, a platform operator has /v1/admin/platform/health.
Two surfaces#
| Gateway | Control plane | |
|---|---|---|
| Endpoint | /v1/dispatch | /v1/admin/* |
| Caller | Your agents | You, or your backend |
| Credential | Arcus API key | Clerk session token |
| Header | Authorization: Bearer ark_live_… | Authorization: Bearer <clerk-session-token> |
There is exactly one gateway endpoint. An API key authenticates nothing else — it cannot read logs, list keys, change a policy, disable a guardrail or revoke itself. See Authentication & keys.
Do not put a Clerk session token in an agent
An agent holding a control-plane credential can rewrite the policies that govern it. The whole architecture rests on the agent holding only a dispatch credential.
Content type#
Every request with a body is JSON:
Content-Type: application/jsonResponses are always JSON, including errors. There is no form encoding, no XML, and no multipart/form-data on any endpoint.
Status codes#
| Code | Meaning |
|---|---|
200 | Read succeeded |
201 | Resource created |
202 | Dispatch accepted — read queued and status |
400 | Malformed request body; the response names the field |
401 | Missing, malformed, unknown or revoked credential; or an identity mismatch |
403 | Authenticated but not permitted — policy denial, tier limit, or insufficient role |
404 | No such resource, or no account |
409 | Conflict — an abuse signal already claimed by another operator |
422 | Semantically rejected — DLP or a guardrail refused the payload |
429 | Rate-limited or quarantined; Retry-After is set |
500 | Server error |
The distinction between 403 and 422 is deliberate: 403 means you may not reach this destination, 422 means this content may not be sent. Same refusal from the agent's point of view, very different fix.
`202` is not a delivery confirmation
Three outcomes return 202: accepted and queued (queued: true), held for approval (queued: false, status: "PENDING_APPROVAL"), and accepted-but-unqueueable when Redis is down. Branch on queued and status, never on the HTTP code alone.
Error shape#
Every error carries error as a readable sentence. Enforcement refusals add status; tier limits add code.
{ "error": "Payload blocked by DLP", "status": "BLOCKED_BY_DLP", "transferLogId": "clx…" }{
"error": "Free accounts can keep 2 parameter rules enabled",
"code": "PARAMETER_RULE_LIMIT_REACHED",
"tier": "free",
"limit": 2,
"activeRules": 2,
"upgradeAvailable": true,
"requiredTier": "pro"
}Validation errors name the field and the expectation:
{ "error": "greater_than needs a numeric threshold, e.g. { \"value\": 1000 }" }status values map 1:1 to TransferStatus — BLOCKED_BY_POLICY, BLOCKED_BY_DLP, BLOCKED_BY_PARAMETER, THREAT_DETECTED, PENDING_APPROVAL. Branch on those rather than on error strings, which are written for humans and may be reworded.
Where a refusal produced a record, transferLogId is present — so an agent can reference the decision without needing control-plane access.
Pagination#
List endpoints use cursor pagination:
curl -sS "$ARCUS_URL/v1/admin/logs?limit=100" -H "$AUTH"{ "logs": [ /* newest first */ ], "nextCursor": "clx8m2p4k0032abcd" }Pass nextCursor as cursor for the following page; null means the end. Cursors are stable while new rows arrive and do not get more expensive as you page back — unlike an offset.
limit accepts 1–200 and defaults to 50 on every paginated endpoint. Audit events page by sequence number rather than id; everything else pages by id.
Rate limits#
The only rate limit Arcus enforces is the per-agent dispatch limit: 60 dispatches per 60 seconds per (tenant, agent), breach quarantines the agent for 300 seconds. 429 with Retry-After. See Threat detection.
Control-plane endpoints are not rate-limited. They are behind a browser session, and the expensive reads are memoised server-side.
Monthly request figures are advisory
Plan pages list indicative monthly volumes per tier (10,000 / 250,000 / 2,000,000). These are not enforced — no endpoint rejects a request for exceeding a monthly count, and nothing meters you toward a cutoff. The only hard numeric limits are active keys and enabled guardrail rules. See Pricing & plans.
Tier limits and capabilities#
Two mechanisms.
Numeric limits — active keys (2 / 10 / 50) and enabled guardrail rules (2 / unlimited / unlimited). Exceeding one returns 403 with a code, your tier, the limit, the current count and requiredTier.
Capabilities — named permissions like audit.verify, policy.custom, dlp.configure, reports. A missing capability returns 403 naming what would grant it, or degrades the response: DLP refusals on Free include detailAvailableOn: "pro" rather than failing.
Enforcement never depends on tier. All five gates run on every request on every plan.
CORS#
The dashboard origin is the only browser origin permitted, configured server-side. There is no wildcard CORS policy, which means you cannot call the Arcus API from arbitrary browser JavaScript — deliberately. An ark_live_ key in a browser is a published key. Dispatch from your server.
Idempotency#
Arcus does not implement idempotency keys. A repeated dispatch is a second dispatch: a second transfer log row, a second audit event, and a second delivery.
Two consequences to design around:
- Make your receiving endpoint idempotent. The delivery worker retries up to 3 times on non-2xx, so your endpoint must tolerate seeing the same body more than once. Put a deduplication key in
dataand check it on arrival. - Do not retry a refusal.
401,403and422are decisions, not transient failures. Retrying produces more refusals and more audit events. Only429(afterRetry-After) and5xxare worth retrying.
Timeouts#
| Boundary | Value |
|---|---|
| Enqueue after a decision | 2 s — exceeded means the dispatch is recorded FAILED |
| Delivery attempt to your target | 10 s per attempt |
| Delivery attempts | 3, exponential backoff from 1 s |
Set a client timeout of 15 seconds or so on your dispatch call. The synchronous portion is a Redis read, an indexed Postgres read, an in-process payload walk and one transaction — it does not wait for your target.
Versioning#
The /v1 prefix is the version. Additive changes — new fields, new event types, new endpoints — happen within v1. Removing a field or changing the meaning of a status code would require /v2.
Write clients that ignore unknown fields. New keys will appear in responses.
A minimal client#
import os
import requests
class Arcus:
"""Dispatch client. Holds only an agent credential — no control-plane access."""
def __init__(self, agent: str):
self.agent = agent
self.url = os.environ["ARCUS_URL"].rstrip("/")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {os.environ['ARCUS_KEY']}",
"Content-Type": "application/json",
})
def dispatch(self, target: str, message: str, data: dict | None = None, to: str | None = None):
body = {"target": target, "from": self.agent, "message": message, "data": data or {}}
if to:
body["to"] = to
r = self.session.post(f"{self.url}/v1/dispatch", json=body, timeout=15)
payload = r.json()
if r.status_code == 202:
if payload.get("status") == "PENDING_APPROVAL":
return {"held": True, "id": payload["transferLogId"]}
return {"held": False, "id": payload["transferLogId"],
"flagged": payload.get("flagged", False)}
if r.status_code == 429: # quarantined — honour Retry-After
raise RuntimeError(f"quarantined for {r.headers.get('Retry-After', '?')}s")
# 401 identity, 403 policy, 422 DLP or guardrail. All are decisions, not retryable.
raise RuntimeError(f"refused {r.status_code}: {payload.get('error')} "
f"({payload.get('status', 'n/a')})")Endpoint index#
Gateway — full reference
| /v1/dispatch | Send a governed request |
Control plane — full reference
| Area | Endpoints |
|---|---|
| Keys | /v1/admin/keys /v1/admin/keys /v1/admin/keys/:id |
| Policies | /v1/admin/policies /v1/admin/policies /v1/admin/policies/:id |
| Guardrails | /v1/admin/parameter-rules /v1/admin/parameter-rules /v1/admin/parameter-rules/:id /v1/admin/parameter-rules/:id /v1/admin/baselines |
| Approvals | /v1/admin/messages/pending /v1/admin/messages/:id/approve /v1/admin/messages/:id/reject |
| Logs | /v1/admin/logs /v1/admin/stats /v1/admin/analytics/overview |
| Audit | /v1/admin/audit/events /v1/admin/audit/verify |
| Compliance | /v1/admin/compliance/export /v1/admin/compliance/report |
| Threats | /v1/admin/threats /v1/admin/threats/:agent /v1/admin/threats/:agent/release |
| Billing | /v1/admin/subscription /v1/admin/subscription/checkout /v1/admin/subscription/manage /v1/admin/subscription/sync |
| Tenants | /v1/admin/tenants /v1/admin/tenants/:id/tier |
Platform operator — full reference. Cross-tenant, restricted to an Clerk-side operator role or a server-only email allow-list.