Integration Patterns
Arcan supports four integration patterns for external services.
Pattern A: REST API (Primary)
Every external service integrates via the REST API with an API token:
# Create an API token
arcan token create --name "ci-pipeline" --ttl 90d --capabilities "secrets:read,leases:create"
# Use it
curl -H "Authorization: Bearer arc_xxx" https://arcan.example.com/api/v1/realms/myapp/secrets
Token Structure
- Prefix:
arc_(API tokens),oas_(OIDC/K8s sessions) - Token stored as SHA-256 hash in database (not plaintext)
- Each token has a name, TTL, and capability list
- Capabilities are a subset of the issuing user's RBAC permissions
- Tokens can be revoked individually or all-at-once
Rate Limiting
- Per-token: 100 req/s (configurable per token)
- Per-IP (unauthenticated): 10 req/s
- Rate limit headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
Pattern B: Client SDKs (Convenience)
Thin wrappers around the REST API. Official SDKs:
Phase 1: Go SDK (dogfood -- we use it)
Phase 2: TypeScript (largest community)
Phase 3: Python (data/ML pipelines)
SDKs are generated from an OpenAPI spec maintained alongside the API source code.
Go SDK
client := arcan.NewClient("https://arcan.example.com", arcan.WithToken("arc_xxx"))
secret, err := client.KV.Get(ctx, "DATABASE_URL", arcan.InRealm("myapp"))
creds, err := client.Lease.Create(ctx, "postgres", "readonly", arcan.WithTTL(1*time.Hour))
defer client.Lease.Revoke(ctx, creds.LeaseID)
TypeScript SDK
const client = new ArcanClient({ url: 'https://arcan.example.com', token: 'arc_xxx' });
const secret = await client.kv.get('DATABASE_URL', { realm: 'myapp' });
const creds = await client.lease.create('postgres', 'readonly', { ttl: '1h' });
Python SDK
client = ArcanClient(url="https://arcan.example.com", token="arc_xxx")
secret = client.kv.get("DATABASE_URL", realm="myapp")
creds = client.lease.create("postgres", "readonly", ttl="1h")
Pattern C: Webhooks (Event-Driven)
Arcan pushes events to external services via the audit webhook sink:
{
"event": "lease.expiring",
"timestamp": "2026-04-01T12:00:00Z",
"request_id": "req_xxx",
"realm": "myapp",
"data": {
"lease_id": "uuid",
"engine": "postgres",
"role": "readonly",
"expires_at": "2026-04-01T13:00:00Z",
"ttl_remaining_seconds": 3600
}
}
Event Categories
secret.*-- Secret changes (set, deleted, rotated)lease.*-- Lease lifecycle (created, renewed, expiring, expired, revoked)policy.*-- Policy changes (role created, binding changed)engine.*-- Engine lifecycle (bootstrapped, root rotated, health changed)auth.*-- Auth events (login, token created, token revoked)plugin.*-- Plugin lifecycle (installed, upgraded, crashed, removed)
Delivery Rules
- Retry: 3 attempts with exponential backoff (1s, 5s, 30s)
- Timeout: 10s per attempt
- Payload signature: HMAC-SHA256 in
X-Arcan-Signatureheader - Dead letter: failed events logged to audit table with delivery error
Pattern D: Agent Mode (Future)
arcan agent --realm myapp --inject DATABASE_URL,API_KEY --refresh 5m
Runs as a sidecar/daemon:
- Fetches secrets on startup, writes to tmpfs file or environment
- Auto-refreshes before TTL expiry
- Auto-renews dynamic credential leases
- Emits health status for container orchestrator probes
The core architecture must not preclude this. The API and token system already support it. The agent is a consumer of the REST API, not a new integration pattern.