{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/api-evangelist/knoxcall/main/json-schema/knoxcall-request-log-schema.json",
  "title": "RequestLog",
  "description": "A record of a proxied API request.\n\nOn `GET /logs` the fields `id`, `request_id`, `tenant_id`, `route_id`,\n`matched_client_id`, `identification_method`, `method`, `status_code`,\n`src_ip`, `environment`, `sandbox` and `ts` are exactly what the Merkle\nanchor commits to, in that order - see `GET /logs/{request_id}/proof`.\nEvery other field is operational detail and is NOT part of the leaf.\n",
  "x-generated": "2026-10-18",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/knoxcall-openapi.json#/components/schemas/RequestLog",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "Row id as a decimal string. This is `api_requests.id`, a bigint - NOT a uuid, which earlier revisions of this schema incorrectly declared. It is a string rather than a JSON number because a JSON number is a double and cannot hold a bigint exactly past 2^53."
    },
    "request_id": {
      "type": "string",
      "format": "uuid",
      "description": "Correlation ID for the proxied request."
    },
    "ts": {
      "type": "string",
      "format": "date-time",
      "description": "Timestamp of the request."
    },
    "src_ip": {
      "type": "string",
      "description": "Source IP address of the caller, host form without a prefix length. Identity tier on `GET /logs`: requires `log:read_identity`, and is ABSENT (not null) when denied."
    },
    "method": {
      "type": "string",
      "description": "HTTP method used."
    },
    "path": {
      "type": "string",
      "description": "Request path."
    },
    "status_code": {
      "type": "integer",
      "description": "HTTP status code returned by the upstream."
    },
    "latency_ms": {
      "type": "integer",
      "description": "Round-trip latency in milliseconds."
    },
    "upstream_host": {
      "type": [
        "string",
        "null"
      ],
      "description": "Upstream host the request was forwarded to."
    },
    "error": {
      "type": [
        "string",
        "null"
      ],
      "description": "Error message if the request failed."
    },
    "environment": {
      "type": [
        "string",
        "null"
      ],
      "description": "Environment the request was resolved against."
    },
    "rate_limited": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Whether this request was rate-limited."
    },
    "signature_valid": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Whether the request signature was valid (if required)."
    },
    "source_ip_country": {
      "type": [
        "string",
        "null"
      ],
      "description": "Country of the source IP (GeoIP lookup)."
    },
    "source_ip_city": {
      "type": [
        "string",
        "null"
      ],
      "description": "City of the source IP (GeoIP lookup)."
    },
    "tenant_id": {
      "type": "string",
      "format": "uuid",
      "description": "Owning tenant. Returned by `GET /logs`; omitted by `GET /routes/{id}/logs`."
    },
    "route_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid"
    },
    "matched_client_id": {
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "description": "Which client the request was attributed to. Kept when that client is later deleted, so it may name a client that no longer exists: it is a committed field of the row's Merkle leaf and is never rewritten. Identity tier on `GET /logs`: requires `log:read_identity`, and is ABSENT (not null) when denied."
    },
    "identification_method": {
      "type": [
        "string",
        "null"
      ],
      "description": "How that attribution was made (e.g. `ip`, `mtls_thumbprint`). Identity tier on `GET /logs`."
    },
    "sandbox": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Live/Test partition. `null` on rows written before the partition existed, which are treated as Live. Null, false and true are three distinct facts and the Merkle anchor keeps them apart."
    },
    "proxy_mode": {
      "type": [
        "string",
        "null"
      ]
    },
    "cursor": {
      "type": "string",
      "description": "Opaque resume token, returned by `GET /logs` only. Pass the last row's value as `cursor` on the next call."
    },
    "client_origin": {
      "type": "string",
      "enum": [
        "direct",
        "sdk_intercept"
      ],
      "description": "How the call arrived at the Route. `sdk_intercept`: a KnoxCall SDK's route-aware interceptor rerouted a third-party SDK's request onto the Route (the SDK sent `x-knoxcall-origin: sdk-intercept`). `direct`: a plain `call()` / bound-route request, and every row written before the marker existed. Informational only - it never influences authentication, routing, rate limits or the response block."
    }
  }
}