Skip to content

Using Response Headers and Body Signals

In addition to the standard RFC 6749 response, logi attaches signals that your RP server can use to debug and monitor without ever opening the console. This page shows you how to capture those signals at the middleware layer and stream them automatically into your server logs and observability systems.

Signal catalog

Body fields (4xx responses)

FieldAlways presentPurpose
errorMachine-readable code (RFC 6749 §5.2)
error_descriptionOne-line description
error_uriDocs anchor for this code
request_idPer responseThe same key as the console's request_logs

Response headers (all responses)

HeaderWhenPurpose
X-Logi-Request-IdAlways on 4xxTrace even when the body is empty (HEAD, or a failure rendering the error)
X-Logi-Console-UrlOn 4xx when the RP authenticates successfullyA direct link to this request in the console's request_logs
X-Logi-Scope-DriftOn a token 200 response when drift was recorded within the last 7 daysDetects an unregistered scope — for immediate alerting. Under the default block policy, a drift request is itself rejected with invalid_scope; this header applies to apps explicitly set to log_only/alert with ongoing drift, or as an echo of rejected/recorded drift history

Pattern 1: Auto-log every 4xx response

Node.js / Express + axios

js
import axios from 'axios'

const logiClient = axios.create({ baseURL: 'https://api.1pass.dev' })

logiClient.interceptors.response.use(
  (res) => res,
  (err) => {
    if (err.response?.status >= 400 && err.response?.status < 500) {
      const { data, headers, status } = err.response
      console.warn('[logi] 4xx', {
        status,
        error: data?.error,
        error_description: data?.error_description,
        error_uri: data?.error_uri,
        request_id: data?.request_id || headers['x-logi-request-id'],
        console_url: headers['x-logi-console-url'], // an operator can click it right away
      })
    }
    return Promise.reject(err)
  }
)

Rails + Faraday

ruby
class LogiResponseLogger < Faraday::Middleware
  def on_complete(env)
    return unless env.status >= 400 && env.status < 500

    body = JSON.parse(env.body) rescue {}
    Rails.logger.warn(
      "[logi] 4xx",
      status: env.status,
      error: body["error"],
      error_description: body["error_description"],
      error_uri: body["error_uri"],
      request_id: body["request_id"] || env.response_headers["x-logi-request-id"],
      console_url: env.response_headers["x-logi-console-url"]
    )
  end
end

Python + requests

python
import logging, requests

def log_logi_4xx(resp: requests.Response):
    if not (400 <= resp.status_code < 500):
        return
    body = resp.json() if "json" in resp.headers.get("content-type", "") else {}
    logging.warning(
        "[logi] 4xx",
        extra={
            "status": resp.status_code,
            "error": body.get("error"),
            "error_description": body.get("error_description"),
            "error_uri": body.get("error_uri"),
            "request_id": body.get("request_id") or resp.headers.get("X-Logi-Request-Id"),
            "console_url": resp.headers.get("X-Logi-Console-Url"),
        },
    )

If you tie request_id to your RP's distributed tracing key, you can follow a single trace in your own dashboard all the way from "this user's payment failure → logi token issuance failure."

js
import * as Sentry from '@sentry/node'

logiClient.interceptors.response.use(undefined, (err) => {
  if (err.response?.status >= 400) {
    const reqId = err.response.data?.request_id ||
                  err.response.headers['x-logi-request-id']
    Sentry.withScope((scope) => {
      scope.setTag('logi.request_id', reqId)
      scope.setTag('logi.error', err.response.data?.error)
      scope.setExtra('logi.console_url', err.response.headers['x-logi-console-url'])
      Sentry.captureException(err)
    })
  }
  return Promise.reject(err)
})

With this in place, you can jump from a Sentry issue page to the logi console in one click.

Pattern 3: Scope drift alerts (200 responses)

The default scope drift policy is block — a request containing an unregistered scope is rejected with invalid_scope (Scope drift policy). The X-Logi-Scope-Drift header is attached to successful token responses in two cases: when the app is explicitly set to the log_only/alert policy and drift is ongoing (dropped, then proceeding), or as an echo of the drift history recorded within the last 7 days (including block rejections) — the missing scopes arrive as a comma-separated list.

js
logiClient.interceptors.response.use((res) => {
  const drift = res.headers['x-logi-scope-drift']
  if (drift) {
    console.warn('[logi] scope drift detected:', drift.split(','),
                 '— update the registered scopes in the console.')
  }
  return res
})

For the 7 days the drift history stays alive, this signal is attached to every token response, so even if you forget to update a scope before deploying, an alert appears automatically in your server logs.

Pattern 4: A one-line message to show the user

error_description can mix Korean and English and is technical. When showing it to users, we recommend that you maintain a mapping table from the error code to a user-facing message in your RP:

ts
const USER_FACING: Record<string, string> = {
  invalid_grant: 'Your login expired. Please try again.',
  access_denied: 'Login was cancelled.',
  rate_limited: 'Too many requests. Please try again in a moment.',
  invalid_scope: "We can't log you in right now because of an app configuration issue.",
}

function userFacingMessage(error: string) {
  return USER_FACING[error] ?? 'Please try again in a moment.'
}

Send the technical information (error, request_id, console_url) to your server logs and show the user only a friendly one-liner — separating the two this way keeps users from ever seeing something like a stack trace.

Operations checklist

  • [ ] On a 4xx, the RP server records all three of error, error_description, and request_id at the INFO level
  • [ ] The X-Logi-Console-Url header is included in alerting messages (a one-click jump for the operator)
  • [ ] When the X-Logi-Scope-Drift header appears, the RP's monitoring system fires a medium-priority alert
  • [ ] request_id is mapped to the same field as the RP's trace ID, or at minimum printed on the same line
  • [ ] User-facing messages are limited to text that has passed through the error code mapping table

Security note

The extra signals in the response headers and body are not PII — they contain only the request_id, error code, and console URL. They never include user identifiers (email, sub), token values, or secrets, so they are safe to leave in ordinary logs.

References

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