{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/spicrawl-com/json-schema/spicrawl-com-session-schema.json",
  "title": "Session",
  "description": "A session as every session endpoint reports it. Never contains cookies.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/spicrawl-com-openapi.yaml#/components/schemas/Session",
  "type": "object",
  "required": [
    "id",
    "project_id",
    "status",
    "sticky_key",
    "proxy",
    "created_at",
    "last_used_at",
    "expires_at",
    "usage_count",
    "context"
  ],
  "properties": {
    "id": {
      "type": "string",
      "description": "Session id (26-character ULID). Pass it as `session_id` on `/v1/scrape`."
    },
    "project_id": {
      "type": "string",
      "description": "Project the session belongs to (the creating key's project)."
    },
    "engine": {
      "$ref": "#/$defs/SessionEngine"
    },
    "status": {
      "type": "string",
      "enum": [
        "active",
        "released",
        "expired"
      ],
      "description": "Derived from the clock, not a stored flag. `active` sessions can be used; `released` and `expired` ones have no context and cannot be revived. Create a new session."
    },
    "sticky_key": {
      "type": "string",
      "description": "Exit pin in use. The lowercase session id when you did not set one; empty string when `rotate_ip` was set."
    },
    "proxy": {
      "$ref": "#/$defs/SessionProxy"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "Creation time, RFC 3339 UTC."
    },
    "last_used_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "Last time a successful scrape used the session. Null means never used."
    },
    "expires_at": {
      "type": "string",
      "format": "date-time",
      "description": "Current expiry. Each successful scrape moves it to that time + the session's `ttl_seconds` (never earlier than it was, never past `hard_expires_at`). After it passes the context is purged."
    },
    "hard_expires_at": {
      "type": "string",
      "format": "date-time",
      "description": "Fixed ceiling set at creation from the organization's maximum TTL. Plan a re-login before this time. Omitted only for legacy rows."
    },
    "released_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the session was released. Present only on released sessions."
    },
    "usage_count": {
      "type": "integer",
      "minimum": 0,
      "description": "Number of successful scrapes that have used the session. Failed scrapes and `/context` reads do not count."
    },
    "context": {
      "oneOf": [
        {
          "$ref": "#/$defs/SessionContextSummary"
        },
        {
          "type": "null"
        }
      ],
      "description": "Size summary of the stored context. Null once the session is not active (the context has been purged)."
    },
    "retired": {
      "type": "string",
      "enum": [
        "expired",
        "usage_exhausted",
        "explicit"
      ],
      "description": "Why a worker took this session out of service. Absent while usable. A retired session should be replaced."
    },
    "domain_scores": {
      "type": "object",
      "description": "Per-domain block evidence the workers maintain. Omitted when empty.",
      "additionalProperties": {
        "$ref": "#/$defs/SessionDomainScore"
      }
    },
    "fingerprint": {
      "type": "object",
      "description": "Browser fingerprint, as stored. Omitted when none was set."
    },
    "lease": {
      "$ref": "#/$defs/SessionLease"
    },
    "warnings": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Decisions made on your behalf at creation, e.g. a TTL reduced to the organization maximum. Only on the create response; omitted when empty."
    }
  },
  "$defs": {
    "SessionContextSummary": {
      "type": "object",
      "required": [
        "schema_version",
        "cookies",
        "origins",
        "bytes"
      ],
      "description": "Non-secret counts describing the stored context.",
      "properties": {
        "schema_version": {
          "type": "integer",
          "description": "Stored context format version. Currently 1."
        },
        "cookies": {
          "type": "integer",
          "minimum": 0,
          "description": "Number of cookies stored."
        },
        "origins": {
          "type": "integer",
          "minimum": 0,
          "description": "Distinct origins across local storage, session storage and IndexedDB."
        },
        "bytes": {
          "type": "integer",
          "minimum": 0,
          "maximum": 1048576,
          "description": "Serialised plaintext size in bytes. The limit is 1048576."
        }
      }
    },
    "SessionDomainScore": {
      "type": "object",
      "required": [
        "score",
        "good",
        "bad",
        "last_updated",
        "retired"
      ],
      "description": "One domain's standing with the session. At a score of 3 or with `retired` true, the session is treated as blocked for that domain.",
      "properties": {
        "score": {
          "type": "number",
          "minimum": 0,
          "description": "Error score; rises on failures, decays on successes. Blocked at 3."
        },
        "good": {
          "type": "integer",
          "minimum": 0,
          "description": "Successful requests counted."
        },
        "bad": {
          "type": "integer",
          "minimum": 0,
          "description": "Failed requests counted."
        },
        "last_updated": {
          "type": "integer",
          "format": "int64",
          "description": "Last update in Unix milliseconds."
        },
        "retired": {
          "type": "boolean",
          "description": "Hard, sticky block for this domain (e.g. a 403 wall). Does not decay; use a new session for this domain."
        }
      }
    },
    "SessionEngine": {
      "type": "string",
      "enum": [
        "fetch",
        "js",
        "browser"
      ],
      "description": "The rendering tier a session is bound to for its lifetime (`fetch` is HTTP only, `js` is JavaScript rendering, `browser` is full-browser rendering). Chosen automatically when the session is created unless the create names one."
    },
    "SessionLease": {
      "type": "object",
      "required": [
        "expires_at"
      ],
      "description": "Present only while a render holds the single-writer lease. While present, other scrapes, release and delete get 409 `ERR::SESSION::BUSY`.",
      "properties": {
        "holder": {
          "type": "string",
          "description": "Identifier of the worker holding the lease. Diagnostic only."
        },
        "expires_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the lease lapses at the latest. Retry after this time."
        }
      }
    },
    "SessionProxy": {
      "type": "object",
      "required": [
        "tier"
      ],
      "description": "The session's exit intent. Not an endpoint or IP.",
      "properties": {
        "tier": {
          "type": "string",
          "enum": [
            "none",
            "datacenter",
            "residential"
          ],
          "description": "Exit tier. `residential` when created with `premium_proxy`, otherwise `datacenter`. `none` is reserved and not produced by the create endpoint."
        },
        "country": {
          "type": "string",
          "description": "Lowercase alpha-2 exit country. Omitted when no country was requested."
        },
        "region_pool": {
          "type": "string",
          "description": "Regional pool requested at creation. Omitted when none."
        }
      }
    }
  }
}