Trace API
POST /panopticon/trace — the RP reports the result of a tool call to logi's Panopticon.
Authentication
Panopticon uses a separate credential (the Panopticon PAK). It does not reuse the user's OAuth token (JWT) for telemetry, which avoids audience confusion.
Simple mode (default) ✓ Implemented
Authorization: Bearer pano_pak_...It verifies only that the PAK hash matches. This is the default for every application. (See PakAuthenticator#authenticate! — based on the panopticon_pak_secret_encrypted column.)
HMAC strict mode 🔬 (roadmap)
Not implemented — design spec
The require_trace_hmac flag, pak_secret issuance, and X-Panopticon-Signature / X-Panopticon-Timestamp header verification this section covers do not exist in the codebase yet (audited 2026-05-15). The OauthApplication model has no such columns or methods, and Panopticon::TracesController has no HMAC branch or timestamp-tolerance check.
If you want defense against body tampering, work around it with PAK rotation + HTTPS pinning + an RP-side local buffer hash. For inquiries about the HMAC strict mode implementation, reach us at support@1pass.dev.
When an application's require_trace_hmac flag is on, these additional headers are required (planned):
Authorization: Bearer pano_pak_...
X-Panopticon-Signature: <hex(HMAC-SHA256("<timestamp>.<body>", pak_secret))>
X-Panopticon-Timestamp: <unix epoch seconds>The canonical string is "<timestamp>.<body>" (timestamp + dot + raw body). Verification:
- The difference between
X-Panopticon-Timestampand the server time is ≤ ±5 minutes. HMAC-SHA256("<timestamp>.<body>", pak_secret)matches the signature.
Sample (Node.js):
import crypto from "node:crypto";
const ts = Math.floor(Date.now() / 1000);
const body = JSON.stringify(payload);
const sig = crypto
.createHmac("sha256", PAK_SECRET)
.update(`${ts}.${body}`)
.digest("hex");
fetch("https://api.1pass.dev/panopticon/trace", {
method: "POST",
headers: {
"Authorization": `Bearer ${PANOPTICON_PAK}`,
"X-Panopticon-Signature": sig,
"X-Panopticon-Timestamp": String(ts),
"Content-Type": "application/json",
},
body,
});Payload schema
{
"event_id": "550e8400-e29b-41d4-a716-446655440000", // REQUIRED, RP-generated UUID v4
"client_id": "logi_f31b6962aef3", // REQUIRED, for verification (must match the PAK)
"tool": "memorize_fact", // REQUIRED, the name of the tool called
"scope_used": "agent:memory.write", // RECOMMENDED, the value used from the JWT scope claim
"status": "ok", // REQUIRED, ok | error | denied | hitl_pending
"started_at": "2026-05-09T10:11:12Z", // REQUIRED, ISO 8601 UTC
"duration_ms": 42, // OPTIONAL, tool execution time
"error_code": null, // RECOMMENDED when status=error
"user_sub": "user_xyz", // OPTIONAL, RP self-reported — logi does not verify it
"metadata": { // OPTIONAL, free-form jsonb field (no PII)
"model": "claude-sonnet-4-6",
"session_id": "..."
}
}Field constraints
| Field | Type | Validation |
|---|---|---|
event_id | UUID v4 string | Matches the RFC 4122 format, unique per application |
client_id | string | Matches the client_id of the application the PAK was issued for (400 on mismatch) |
tool | string | 1–128 chars, alphanumeric and ._-: |
scope_used | string | Matches the OauthScope NAME_PATTERN (if absent, logged but not enforced) |
status | enum string | One of ok / error / denied / hitl_pending |
started_at | ISO 8601 UTC | Within ±1 hour of the current time (clock skew tolerance) |
duration_ms | integer ≥ 0 | At most 600000 (10 minutes) |
metadata | jsonb | 16KB or less, must not contain PII (RP's responsibility) |
Responses
Success
| HTTP | Situation | Body |
|---|---|---|
| 202 Accepted | New ingestion succeeded | {"event_id":"...","status":"accepted"} |
| 200 OK | Same (app, event_id) is a duplicate — ignored | {"event_id":"...","status":"duplicate"} |
Errors
| HTTP | error code | Meaning |
|---|---|---|
| 400 | invalid_payload | Missing required field / format mismatch |
| 400 | client_id_mismatch | body client_id ≠ the PAK's owning application |
| 401 | invalid_pak | PAK signature/hash mismatch, or rotated |
| 401 | invalid_signature | HMAC strict mode is on but the signature is missing/mismatched |
| 401 | expired_timestamp | HMAC strict mode is on but the timestamp is outside ±5 minutes |
| 403 | panopticon_disabled | Panopticon is disabled for the application |
| 429 | rate_limited | Per-PAK throttle exceeded (default 1000/min) |
| 503 | service_unavailable | A transient logi-side outage — the RP should buffer locally and resend |
Error response format:
{
"error": "invalid_payload",
"error_description": "event_id is missing",
"documentation_url": "https://docs.1pass.dev/panopticon/trace-api"
}Rate limit
- Default: 1000 traces/minute, per PAK (rack-attack throttle).
- Per-application override:
OauthApplication#trace_rate_limit_override🔬 — a design spec, no column yet (2026-05-15). All applications share the same default. - On exceeding it: 429 + a
Retry-Afterheader. - No quota enforcement in the beta — the rate limit is only a per-second throttle; the monthly limit is shown but not enforced.
Idempotency and resending
On a logi outage or network error, the RP should buffer traces locally and, after recovery, resend with the same event_id. The server:
- On finding the same (
oauth_application_id,event_id), returns 200 OK +status:"duplicate". - Does not double-count in the usage aggregates.
The user_sub trust model and its limits
user_sub is an RP self-reported field. logi does not verify this value beyond PAK verification. If a wrong value comes in:
- The Panopticon console's user filter becomes inaccurate.
- A HITL approval may be delivered to the wrong user.
→ The RP must populate user_sub only with a verified user identifier from its own database.
Defending against payload tampering
- HMAC mode: defends against body tampering — 🔬 design stage (see the HMAC strict mode roadmap above). For now, work around it with TLS + PAK rotation + an RP-side local hash.
- PAK rotation ✓ Implemented: on suspected exposure, rotate immediately in the console. After rotation, the old PAK is invalid (the
panopticon_pak_secret_encryptedcolumn is updated). - PII masking: rather than putting raw user input into
metadata, summarize or hash it (the RP's responsibility — the logi server does not verify it).
Next
- Policy Configuration — the AI Guard policies that sit on top of the trace data