Skip to content

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 ​

http
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):

http
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-Timestamp and the server time is ≤ ±5 minutes.
  • HMAC-SHA256("<timestamp>.<body>", pak_secret) matches the signature.

Sample (Node.js):

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 ​

jsonc
{
  "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 ​

FieldTypeValidation
event_idUUID v4 stringMatches the RFC 4122 format, unique per application
client_idstringMatches the client_id of the application the PAK was issued for (400 on mismatch)
toolstring1–128 chars, alphanumeric and ._-:
scope_usedstringMatches the OauthScope NAME_PATTERN (if absent, logged but not enforced)
statusenum stringOne of ok / error / denied / hitl_pending / unknown
started_atISO 8601 UTCWithin ±1 hour of the current time (clock skew tolerance)
duration_msinteger ≥ 0At most 600000 (10 minutes)
metadatajsonb16KB or less, must not contain PII (RP's responsibility)

Responses ​

Success ​

HTTPSituationBody
202 AcceptedNew ingestion succeeded{"event_id":"...","status":"accepted"}
200 OKSame (app, event_id) is a duplicate — ignored{"event_id":"...","status":"duplicate"}

Errors ​

HTTPerror codeMeaning
400invalid_payloadMissing required field / format mismatch
400client_id_mismatchbody client_id ≠ the PAK's owning application
401invalid_pakPAK signature/hash mismatch, or rotated
401invalid_signatureHMAC strict mode is on but the signature is missing/mismatched
401expired_timestampHMAC strict mode is on but the timestamp is outside ±5 minutes
403panopticon_disabledPanopticon is disabled for the application
429rate_limitedPer-PAK throttle exceeded (default 1000/min)
503service_unavailableA transient logi-side outage — the RP should buffer locally and resend

Error response format:

json
{
  "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-After header.
  • 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.

jsonc
{
  "event_id": "…",
  "tool": "memorize_fact",
  "status": "ok",                       // ok | error | unknown
  "metadata": {
    "approval_ref": "<approval request_uuid>"
  }
}

Contract ​

  • approval_ref must be the request_uuid of an approval consumed by the same application for the same tool. 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 status is ok / error / unknown. denied / hitl_pending are not executions and are not recorded.
  • A trace without approval_ref is not attached to any approval (logi does not write an audit record per trace). However, an ok trace 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_id is ignored per the idempotency rule. It cannot change the outcome.
  • If the outcome is settled later, report again with a new event_id + the same approval_ref. Each report adds one outcome record; existing records are never modified. Records for the same approval are grouped by subject_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_ref must 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_encrypted column 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 ​

Identity가 제품의 신뢰를 만듭니다.