Self-Hosted AI Agent Control Plane for Enterprises

Aberon HTTP API Reference

Base URL: http://<host>:8080
All API endpoints: http://<host>:8080/api/
Auth: Authorization: Bearer <token> (JWT or API key)
Content-Type: application/json
Response envelope:

{"success": true, "data": ..., "error": null, "meta": null}

Authentication

Login (get JWT token)

POST /api/auth/login
{"email": "admin@aberon.internal", "password": "your-password"}

Returns: {"token": "eyJ...", "user": {"id": "...", "email": "...", "role": "admin"}}

curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@aberon.internal","password":"Admin123!"}'

Create API key

POST /api/auth/keys
Authorization: Bearer <jwt>
{
  "name": "my-agent-key",
  "scopes": ["agent:write", "trace:write", "guardrail:check"],
  "data_access": "full"
}

Scopes:

Scope Description
agent:write Register agents, send heartbeats
trace:write Create traces and spans
guardrail:check Run guardrail checks

data_access:

Value What the key can read
full All span content (default)
redacted Content replaced with [REDACTED]
metadata_only Only metrics, no content

Returns: {"key": "aberon_sk_...", "key_id": "...", "prefix": "aberon_sk_xxxx"}save the key, shown only once

curl -X POST http://localhost:8080/api/auth/keys \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"name":"agent-key","scopes":["agent:write","trace:write","guardrail:check"]}'

List API keys

GET /api/auth/keys
Authorization: Bearer <jwt>

Revoke API key

DELETE /api/auth/keys/{key_id}
Authorization: Bearer <jwt>

Agents

Register agent

POST /api/agents
Authorization: Bearer <api_key>
{
  "name": "my-llm-agent",
  "model": "qwen-2.5-72b",
  "framework": "custom",
  "project": "my-project",
  "environment": "production",
  "capture_mode": "full",
  "external_ref": "my-agent-v1",
  "parent_agent_id": null
}

capture_mode:

Value Stored in DB
full All prompts and outputs
redacted Text replaced with [REDACTED]
metadata_only Only token counts, no content

Returns: {"id": "uuid", "name": "...", "capture_mode": "full", ...}

curl -X POST http://localhost:8080/api/agents \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-agent","model":"qwen-2.5-72b","capture_mode":"full"}'

Heartbeat (keep agent online)

POST /api/agents/{agent_id}/heartbeat
Authorization: Bearer <api_key>

Body: {} (empty)

Call every 30 seconds to keep agent status = active. Missing heartbeats for 5+ minutes → agent moves to offline.

curl -X POST http://localhost:8080/api/agents/$AGENT_ID/heartbeat \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

List agents

GET /api/agents
Authorization: Bearer <jwt_or_api_key>

Get agent

GET /api/agents/{agent_id}
Authorization: Bearer <jwt_or_api_key>

Traces

Create trace

POST /api/traces
Authorization: Bearer <api_key>
{
  "trace_id": "unique-trace-id-hex-or-uuid",
  "agent_id": "uuid-of-agent",
  "parent_trace_id": null,
  "metadata": {"task": "contract-review"}
}

trace_id — client-generated, any unique string (e.g. uuid4().hex)

TRACE_ID=$(python3 -c "import uuid; print(uuid.uuid4().hex)")
curl -X POST http://localhost:8080/api/traces \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"trace_id\":\"$TRACE_ID\",\"agent_id\":\"$AGENT_ID\"}"

Add span to trace

POST /api/traces/{trace_id}/spans
Authorization: Bearer <api_key>
{
  "span_id": "unique-span-id",
  "parent_span_id": null,
  "name": "llm_call",
  "kind": "llm",
  "input_data": {"prompt": "..."},
  "output_data": {"response": "..."},
  "tokens_in": 1500,
  "tokens_out": 800,
  "latency_ms": 4200,
  "status": "ok",
  "started_at": "2026-04-07T10:00:00.000000+00:00",
  "completed_at": "2026-04-07T10:00:04.200000+00:00"
}

kind values: llm, tool, guardrail, reasoning, other
status values: ok, error

Complete / update trace

PATCH /api/traces/{trace_id}
Authorization: Bearer <api_key>
{
  "status": "completed",
  "total_tokens": 2300,
  "input_tokens": 1500,
  "output_tokens": 800,
  "cost_usd": 0.0023,
  "latency_ms": 4200
}

status values: running, completed, error

List traces

GET /api/traces?agent_id=uuid&status=completed&page=1&per_page=20
Authorization: Bearer <jwt_or_api_key>

Query params: agent_id, status (running/completed/error), from, to, root_only, has_error, page, per_page

Get trace detail (with span tree)

GET /api/traces/{trace_id}
Authorization: Bearer <jwt_or_api_key>

Returns full trace with nested spans. Content filtered by API key data_access level.


Guardrails

Check guardrails (MUST call before risky actions)

POST /api/guardrails/check
Authorization: Bearer <api_key>
{
  "agent_id": "uuid",
  "trace_id": "current-trace-id",
  "input_text": "text to check",
  "tool_name": "deploy_config",
  "current_cost_usd": 0.15,
  "context": {}
}

Returns:

{
  "allowed": true,
  "blocked_by": [],
  "requires_approval": null,
  "sanitized_text": null,
  "latency_ms": 12.5
}

Result handling:

if result["allowed"]:
    proceed()                          # all policies passed
elif result["blocked_by"]:
    stop()                             # blocked by policy
elif result["requires_approval"]:
    wait_for_approval(result["requires_approval"]["approval_id"])

If sanitized_text is not null — use it instead of original (PII was redacted).

curl -X POST http://localhost:8080/api/guardrails/check \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"agent_id\":\"$AGENT_ID\",\"trace_id\":\"$TRACE_ID\",\"input_text\":\"some text\",\"tool_name\":\"deploy_config\"}"

Approvals

Poll approval status

GET /api/approvals/{approval_id}
Authorization: Bearer <jwt_or_api_key>

Returns:

{
  "id": "uuid",
  "status": "pending",
  "tool_name": "deploy_config",
  "timeout_at": "2026-04-07T10:05:00+00:00"
}

status values: pendingapproved / denied / expired

Poll every 3-5 seconds until status is not pending.

# Poll until decided
while true; do
  STATUS=$(curl -s http://localhost:8080/api/approvals/$APPROVAL_ID \
    -H "Authorization: Bearer $JWT" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['status'])")
  echo "Status: $STATUS"
  [ "$STATUS" != "pending" ] && break
  sleep 3
done

Decide (approve or deny)

POST /api/approvals/{approval_id}/decide
Authorization: Bearer <jwt>   (admin or agent_owner only)
{"decision": "approve", "reason": "Reviewed and approved"}

decision values: approve | deny

List approvals

GET /api/approvals?status=pending&page=1&per_page=20
Authorization: Bearer <jwt_or_api_key>

Query params: status, agent_id, page, per_page


Policies

Create policy

POST /api/policies
Authorization: Bearer <jwt>  (admin only)

PII detection policy:

{
  "name": "block-pii",
  "type": "pii",
  "config": {
    "action": "block",
    "entities": ["EMAIL_ADDRESS", "PHONE_NUMBER", "PERSON"]
  },
  "is_active": true,
  "priority": 0
}

Cost limit policy:

{
  "name": "cost-limit-50usd",
  "type": "cost_limit",
  "config": {
    "max_cost_usd": 50.0,
    "period": "run"
  },
  "is_active": true,
  "priority": 0
}

Tool restriction (require human approval):

{
  "name": "approve-deployments",
  "type": "tool_restriction",
  "config": {
    "tools": ["deploy_config", "write_to_switch", "execute_sql"],
    "action": "require_approval",
    "approval_timeout_seconds": 300
  },
  "target_agent_id": "uuid-of-agent",
  "apply_to_children": true,
  "is_active": true,
  "priority": 10
}

Leave target_agent_id as null for a global policy (applies to all agents).

List policies

GET /api/policies
Authorization: Bearer <jwt_or_api_key>

Audit Log

Search audit entries

GET /api/audit?event_type=guardrail.blocked&page=1&per_page=50
Authorization: Bearer <jwt>

Query params: event_type, agent_id, from_time, to_time, page, per_page

Event types: agent.created, agent.heartbeat_missed, trace.created, guardrail.blocked, guardrail.approval_requested, approval.created, approval.decided, approval.expired, policy.created, policy.updated, api_key.created, user.login

Verify audit chain integrity

GET /api/audit/verify
Authorization: Bearer <jwt>

Returns: {"valid": true, "entries_checked": 245, "message": "All 245 entries verified"}

Export audit log

GET /api/audit/export?from_time=2026-04-01T00:00:00Z&to_time=2026-04-08T00:00:00Z
Authorization: Bearer <jwt>

Returns JSON array of all audit entries in chronological order.


Health

GET /health    → {"status": "ok"}
GET /ready     → {"status": "ready", "db": true}

No authentication required.


What JWT vs API Key Can Do

Action API key JWT (login)
Register agents ✅ (agent:write)
Send heartbeats ✅ (agent:write)
Create traces / spans ✅ (trace:write)
Check guardrails ✅ (guardrail:check)
Read traces ✅ (filtered by data_access) ✅ (always full)
Create / edit policies ✅ admin only
Create API keys ✅ admin only
Approve / deny approvals ✅ admin / agent_owner
Manage users ✅ admin only

For an AI orchestrator that needs to do everything — login with email+password to get a JWT:

# Get JWT (valid 24 hours)
JWT=$(curl -s -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@aberon.internal","password":"Admin123!"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])")

# Use JWT for everything: policies, approvals, keys, agents
curl -X POST http://localhost:8080/api/policies \
  -H "Authorization: Bearer $JWT" ...

JWT expires in 24 hours — re-login to get a new one.


Full Agent Flow Example

Complete example: register agent, check guardrails, create trace, add span, complete trace.

BASE=http://localhost:8080

# 1. Login
JWT=$(curl -s -X POST $BASE/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@aberon.internal","password":"Admin123!"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])")

# 2. Create API key
API_KEY=$(curl -s -X POST $BASE/api/auth/keys \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-key","scopes":["agent:write","trace:write","guardrail:check"]}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['key'])")

# 3. Register agent
AGENT_ID=$(curl -s -X POST $BASE/api/agents \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-agent","model":"qwen-2.5-72b","capture_mode":"full"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")

# 4. Check guardrails before action
RESULT=$(curl -s -X POST $BASE/api/guardrails/check \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"agent_id\":\"$AGENT_ID\",\"trace_id\":\"trace-001\",\"input_text\":\"some input\",\"tool_name\":\"deploy_config\"}")
ALLOWED=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['allowed'])")
echo "Allowed: $ALLOWED"

# 5. Create trace
TRACE_ID=$(python3 -c "import uuid; print(uuid.uuid4().hex)")
curl -s -X POST $BASE/api/traces \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"trace_id\":\"$TRACE_ID\",\"agent_id\":\"$AGENT_ID\"}"

# 6. Add span
NOW=$(python3 -c "from datetime import datetime,timezone; print(datetime.now(timezone.utc).isoformat())")
curl -s -X POST $BASE/api/traces/$TRACE_ID/spans \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"span_id\":\"span-001\",\"name\":\"llm_call\",\"kind\":\"llm\",\"tokens_in\":1500,\"tokens_out\":800,\"status\":\"ok\",\"started_at\":\"$NOW\",\"completed_at\":\"$NOW\"}"

# 7. Complete trace
curl -s -X PATCH $BASE/api/traces/$TRACE_ID \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"completed","total_tokens":2300,"cost_usd":0.0023,"latency_ms":4200}'

# 8. Heartbeat (call every 30 seconds in background)
curl -s -X POST $BASE/api/agents/$AGENT_ID/heartbeat \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

echo "Done! View trace at: $BASE (login and go to Traces)"

Error Responses

HTTP Code Meaning
400 Bad request — invalid input
401 Missing or invalid auth token
403 Insufficient permissions or missing scope
404 Resource not found
409 Conflict — e.g. deciding an already-decided approval
422 Validation error — check request body
429 Rate limit exceeded (default: 600 req/min)
500 Internal server error — check backend logs

Error format:

{"success": false, "data": null, "error": {"code": "...", "message": "..."}, "meta": null}

Guardrail results (200, not errors):
Blocked / approval required are business results, not errors — always HTTP 200 with success: true.


Aberon API v0.1 — SDK: aberon.ai/docs/sdk