Authentication

Every agent-scope endpoint authenticates with a single bearer token: the agent's API key.

Authorization: Bearer jg_a_3f8a9c1e2d4b5e7f8a9c1e2d4b5e7f8a9c1e2d4b

The key resolves to exactly one agent. The agent's id, owner, scopes (read, trade, transfer, admin), and test_mode flag are loaded on every call — there is no session, no refresh, no expiry.

Key format

Prefix Meaning
jg_a_… Live per-agent key.
jg_a_test_… Test-mode per-agent key. Trading and transfers run against simulators.
jw_… Legacy per-agent key. Still validates; never minted by current code paths.

The token is 32 random bytes hex-encoded after the prefix. Treat it as a password — the SHA-256 hash is what the server stores; the raw token is only seen at mint time.

Sending the request

curl https://api.juglans.ai/api/me \
  -H "Authorization: Bearer jg_a_3f8a9c1..."

In juglans-lang, the key lives in juglans.toml:

[ai.providers.juglans]
api_key = "jg_a_3f8a9c1..."
api_base = "https://api.juglans.ai/api/llm"
model = "juglans/juglans-test"

The lib injects the header automatically on every chat() call.

Audit logging

Every agent-authenticated request is logged. The audit_agent_request middleware (crates/jg-server/src/middleware/audit.rs) wraps the entire agent_routes group as a tower layer:

  1. Reads the Authorization header.
  2. After the handler runs, captures the response status.
  3. Asynchronously resolves the key to an agent and writes one row to audit_log:
{
  "method": "POST",
  "path": "/api/orders",
  "status": 200
}

The logging is fire-and-forget — it never blocks or fails the response. Owners read these via GET /api/agents/{id}/audit (JWT-side, see the platform chapter).

A bearer token that doesn't start with jg_a_ / jg_a_test_ / jw_ is treated as a JWT and skipped — JWT calls are audited elsewhere.

Errors

All errors carry a flat envelope: {"error": "<message>"}. There is no code field — the HTTP status plus the message string is what you get.

Status When Example message
401 Unauthorized Missing Authorization header, malformed bearer, unknown key, or revoked key. "Invalid API key"
403 Forbidden Key lacks the scope a handler requires (e.g. calling POST /api/orders with a read-only key). "Insufficient scope: required \"Trade\""
403 Forbidden Agent is frozen. "Agent is frozen: <uuid>" — unfreezing is owner-only via PATCH /api/agents/{id}.

Rotating a key

There is no in-place rotation. To rotate:

  1. Mint a new key for the same agent — POST /api/agents/{id}/keys (owner JWT).
  2. Update your client to use the new key.
  3. Revoke the old key — currently done from the agent profile in the UI, or by deleting the api_key_id row server-side.

Both keys remain valid until the old one is revoked, so there's no downtime window.

Scope summary

Scope Reads required Writes required
none /api/me (any valid jg_a_* key)
read /api/limits, /api/memory, /api/orders (list), /api/positions, /api/transfers (list), /api/exchanges/.../account
trade /api/orders (place/cancel), /api/polymarket/setup
transfer /api/transfers, /api/bridge/quote, /api/bridge/execute
admin grants all scopes implicitly

The default key minted at agent creation has ["read", "trade", "transfer", "admin"]. Reduce scope explicitly when minting from your own tooling if you need a narrower key.