Trace API
POST /panopticon/trace — the RP (the application) self-reports the result of a tool call it handled to logi's Panopticon.
Each trace is the smallest unit of record for "what the RP reports an agent did, with what permission, and when" — an RP-reported record. The PAK only authenticates the application sending the request; it does not verify field values such as scope_used or user_sub themselves — those are self-reported data the RP submits. For moments where you need a verified agent identity and non-repudiation, use Agent Approval Gate — it authenticates the agent/request with an Ed25519 signature, captures the human's non-repudiable approval with a WebAuthn signature, and records every decision in its WORM audit log.
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 | unknown
"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 / unknown |
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.
Reporting execution outcomes (metadata.approval_ref)
If you ran a tool after consuming a HITL approval, include the approval identifier in that execution's trace. logi appends the report to the approval record, and the user sees the outcome in the agent activity history in the app.
{
"event_id": "…",
"tool": "memorize_fact",
"status": "ok", // ok | error | unknown
"metadata": {
"approval_ref": "<approval request_uuid>"
}
}Contract
approval_refmust be therequest_uuidof an approval consumed by the same application for the sametool. If it points to another application's approval or an approval for a different tool, no outcome record is written.- If it points to an approval that was never consumed, no outcome record is written either. A consume is logi's server-observed record that the approval was used. It is not evidence that the execution happened.
- An outcome record is written only when
statusisok/error/unknown.denied/hitl_pendingare not executions and are not recorded. - A trace without
approval_refis not attached to any approval (logi does not write an audit record per trace). However, anoktrace of a HITL-required tool with no valid approval evidence is recorded as a policy violation.
Outcomes are RP self-reports
Outcome records are stored with evidence_source = rp_reported. logi carries over the status the RP sent as-is; it does not check whether the tool actually ran or whether the outcome is correct. The user's screen labels it as an "RP report". The record is attributed to the user of the approval, not to the RP-supplied user_sub.
What unknown means, and re-reporting
unknown= a final report that the tool ran but its outcome is not known (for example, the response was lost after a timeout). It is not a failure, so it is not counted in error aggregates.- Resending the same
event_idis ignored per the idempotency rule. It cannot change the outcome. - If the outcome is settled later, report again with a new
event_id+ the sameapproval_ref. Each report adds one outcome record; existing records are never modified. Records for the same approval are grouped bysubject_ref(=approval_ref) in the response. - Each approval gets at most 3 outcome records (the first report + 2 re-reports). Reports beyond that are still ingested as traces, but no outcome record is written.
- An outcome report must reach logi within 24 hours of the consume. Later reports are still ingested as traces, but no outcome record is written.
approval_refmust be a UUID-formatted string. Any other shape, such as an array or an object, is treated as no approval reference.
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