{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/immix/json-schema/immix-credential-row-schema.json",
  "title": "CredentialRow",
  "description": "A credential as the API serves it: the `CredentialUpdated` record, the row extras (`globalSequence`; the deprecated `unscaled`, never present since 0.3.3) and the credential's `health` — the one shape its reads and its commands answer.",
  "x-generated": "2026-10-29",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/immix-rest-openapi.json#/components/schemas/CredentialRow",
  "type": "object",
  "properties": {
    "credentialId": {
      "type": "integer",
      "description": "Sticky entity key."
    },
    "version": {
      "type": "string",
      "pattern": "^-?[0-9]+$",
      "description": "Monotonic per entity. int64 as a decimal JSON string"
    },
    "orgId": {
      "type": "integer",
      "description": "Owner-stamped."
    },
    "status": {
      "$ref": "#/$defs/CredentialUpdatedStatus",
      "description": "UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
    },
    "providerKind": {
      "$ref": "#/$defs/CredentialUpdatedProviderKind",
      "description": "UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
    },
    "exchangeId": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The refdata exchange for an EXCHANGE-kind credential — carried, not resolved here; a credential that can trade must name one (MISSING_VENUE); 0 = no venue."
    },
    "canReadBalances": {
      "type": "boolean",
      "description": "Whether the key may read balances. a boolean; absent reads false"
    },
    "canExecuteTransfers": {
      "type": "boolean",
      "description": "Whether the key may execute transfers. a boolean; absent reads false"
    },
    "canServeAddresses": {
      "type": "boolean",
      "description": "Whether the key may serve deposit addresses. a boolean; absent reads false"
    },
    "canTrade": {
      "type": "boolean",
      "description": "Capability boolean: an order routes through its account's trading pipe — the account's designated tradingCredentialId, else its own credential — which must be ACTIVE and carry canTrade. Any number of trading credentials may share one (org × venue) — one venue key per sub-account. a boolean; absent reads false"
    },
    "keyCreatedAtNs": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^-?[0-9]+$",
      "description": "Key metadata: when the venue key was minted; 0 = not stated. int64 as a decimal JSON string"
    },
    "rotateByNs": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^-?[0-9]+$",
      "description": "Governance deadline for rotation; a breach is an ops alert; 0 = none. int64 as a decimal JSON string"
    },
    "pendingByUserId": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The user whose proposal is pending, so an approver is checked to be someone else; 0 when nothing is pending."
    },
    "pendingTransition": {
      "$ref": "#/$defs/CredentialUpdatedPendingTransition",
      "description": "ACTIVATE, AMEND (capabilities/secretRef), REACTIVATE, REVOKE or ROTATE; NONE otherwise. NONE is the unset sentinel: outputs only — as input, omit the field instead; explicit NONE refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as NONE, never as an error; as input an unknown name refuses 400 — the parse is strict"
    },
    "observedPermissions": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/CredentialUpdatedObservedPermissionsItem"
      },
      "description": "The observed axis: the key scopes the venue stated to the probing connector, parsed by name; empty until a probe answers. a set (SBE bit-set) served as the array of its set choice names in declared order; empty = none set, never null. The choice set is append-only: a consumer READING this field skips an unknown name, never errors; as input an unknown or repeated name refuses 400 — the parse is strict"
    },
    "observedAllowlist": {
      "$ref": "#/$defs/CredentialUpdatedObservedAllowlist",
      "description": "The probe's allowlist verdict against the cluster's egress set; UNKNOWN until a probe answers. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
    },
    "probeOutcome": {
      "$ref": "#/$defs/CredentialUpdatedProbeOutcome",
      "description": "UNKNOWN = never probed; NONE = the venue's key-restrictions read answered; else the probe's own failure in the credential or gate band. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
    },
    "probedAtNs": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^-?[0-9]+$",
      "description": "Consensus time of the probe report this row records — owner-stamped; 0 = never probed. int64 as a decimal JSON string"
    },
    "observedKeyCreatedAtNs": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^-?[0-9]+$",
      "description": "The key creation time the venue stated to the probe; 0 = the venue states none, or never probed. int64 as a decimal JSON string"
    },
    "scopeUnobserved": {
      "type": "boolean",
      "description": "TRUE = the probe answered but its venue serves no read of the key's own scope (Binance Demo Mode has no /sapi), so observedPermissions is empty because nothing stated it — the approval's scope edges are not applied, and the row says so. FALSE (the fail-safe decode, and every pre-v8 frame) = the scope, as observed, is the venue's word and is judged. a boolean; absent reads false"
    },
    "name": {
      "type": "string",
      "description": "Natural key — per-org unique among live credentials."
    },
    "secretRef": {
      "type": "string",
      "description": "LOGICAL credential name (okx-account-prod) — never a store path or provider URI; resolution happens only at the owning connector's edge."
    },
    "venueKeyId": {
      "type": "string",
      "description": "The venue's identifier for the key — metadata, not material."
    },
    "keyFingerprint": {
      "type": "string",
      "description": "The credential's fingerprint as the intake computed it — fp- and the first 16 hex characters of SHA-256 over the canonical payload — the pin a connector adopts the material at: enough to prove a cutover, never enough to sign. Empty on a row seeded before the intake."
    },
    "appGroup": {
      "type": "string",
      "description": "The connector app group serving this credential (desk 5-as-amended, okx-oe D-E17): the assignment rides the row — one group per credential by construction, empty = unassigned (no connector serves it; fail-safe). Replaces the compile-time CredentialAssignments table."
    },
    "observedKeyId": {
      "type": "string",
      "description": "The venue-facing key identifier the probing connector resolved (the public half, never the secret); empty until a probe answers."
    },
    "observedKeyFingerprint": {
      "type": "string",
      "description": "The fingerprint of the material the probe adopted — compare with keyFingerprint, the intake's pin; empty until a probe answers."
    },
    "venueAccountRef": {
      "type": "string",
      "description": "The venue's account identifier behind the key (OKX uid); empty when the venue states none or no probe answered."
    },
    "permissionDetail": {
      "type": "string",
      "description": "The venue's own permission string, verbatim (not a secret); empty until a probe answers."
    },
    "allowlistDetail": {
      "type": "string",
      "description": "The venue's own allowlist, verbatim (IP addresses are not secrets); empty when none or no probe answered."
    },
    "probeDetail": {
      "type": "string",
      "description": "Redacted diagnostic text for a non-NONE probe outcome; empty-not-null."
    },
    "globalSequence": {
      "$ref": "#/$defs/GlobalSequenceValue"
    },
    "unscaled": {
      "type": "boolean",
      "description": "Never present since 0.3.3: every money value on this API states its own scale on the wire — a policy amount or cap since 0.3.3, an order's and an execution's since 0.3.2, a balance's since 0.3.1 — so a row is never served raw, whether or not this member holds the asset or instrument it references. Kept, deprecated, so a client generated from 0.3.2 still compiles; it goes at the next major.",
      "deprecated": true
    },
    "health": {
      "oneOf": [
        {
          "$ref": "#/$defs/ConnectionHealth"
        },
        {
          "type": "null"
        }
      ],
      "description": "The latest report from the connector serving this credential, or null when no connector has reported on it. It is observed, not decided: it changes without the row's `version` moving, and it reads the same on both views. Judge it by `reportedAtNs`: a serving connector restates its report on a heartbeat, so a time that stops advancing means nothing is reporting on the credential any more."
    }
  },
  "required": [
    "credentialId",
    "version",
    "orgId",
    "status",
    "providerKind",
    "pendingTransition",
    "name",
    "secretRef",
    "venueKeyId",
    "keyFingerprint",
    "globalSequence",
    "health"
  ],
  "$defs": {
    "ConnectionHealth": {
      "type": "object",
      "properties": {
        "credentialId": {
          "type": "integer",
          "description": "The credential the report is about."
        },
        "status": {
          "$ref": "#/$defs/ConnectionHealthStatus",
          "description": "Whether a connector is serving the credential: CONNECTED serves, DEGRADED serves impaired, DOWN does not serve, dialing and redialing included. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
        },
        "reason": {
          "$ref": "#/$defs/ConnectionHealthReason",
          "description": "Why the status is what it is; NONE when nothing is wrong. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict"
        },
        "sinceNs": {
          "type": "string",
          "pattern": "^-?[0-9]+$",
          "description": "When the current status and reason began, on the connector's own clock. int64 as a decimal JSON string"
        },
        "venueRoundTripNs": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^-?[0-9]+$",
          "description": "The latest round trip to the venue the connector measured; 0 = not measured. int64 as a decimal JSON string"
        },
        "transitionAtNs": {
          "type": "string",
          "pattern": "^-?[0-9]+$",
          "description": "Platform time the current status began. A restatement leaves it, and so does a change of reason alone, so the time since it is how long the credential has been in this status. int64 as a decimal JSON string"
        },
        "reportedAtNs": {
          "type": "string",
          "pattern": "^-?[0-9]+$",
          "description": "Platform time of the latest report. A connector serving the credential restates its report on a heartbeat, so a time that stops advancing means nothing is reporting on it any more. int64 as a decimal JSON string"
        },
        "detail": {
          "type": "string",
          "description": "What the connector or the venue said, for people: display it, do not parse it. Empty when there is nothing to add."
        }
      },
      "required": [
        "credentialId",
        "status",
        "reason",
        "sinceNs",
        "transitionAtNs",
        "reportedAtNs",
        "detail"
      ],
      "title": "ConnectionHealth"
    },
    "ConnectionHealthReason": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "NONE",
        "AUTH_FAILED",
        "IP_NOT_WHITELISTED",
        "PERMISSION_DENIED",
        "RATE_LIMITED",
        "VENUE_UNREACHABLE",
        "STARTUP",
        "RECONNECT",
        "HEARTBEAT_TIMEOUT",
        "VENUE_MAINTENANCE",
        "BACKPRESSURE",
        "RESYNC",
        "SHUTDOWN",
        "FAILOVER",
        "LAGGING",
        "RECOVERED",
        "INVALID_KEY",
        "CLOCK_SKEW",
        "CONFIG_ERROR",
        "STREAM_STALE",
        "VENUE_UNAVAILABLE"
      ],
      "description": "Why the status is what it is; NONE when nothing is wrong. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "ConnectionHealthReason"
    },
    "ConnectionHealthStatus": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "CONNECTED",
        "DEGRADED",
        "DOWN"
      ],
      "description": "Whether a connector is serving the credential: CONNECTED serves, DEGRADED serves impaired, DOWN does not serve, dialing and redialing included. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "ConnectionHealthStatus"
    },
    "CredentialUpdatedObservedAllowlist": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "NONE",
        "COVERS",
        "MISSES"
      ],
      "description": "The probe's allowlist verdict against the cluster's egress set; UNKNOWN until a probe answers. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "CredentialUpdatedObservedAllowlist"
    },
    "CredentialUpdatedObservedPermissionsItem": {
      "type": "string",
      "enum": [
        "read",
        "trade",
        "withdraw"
      ],
      "title": "CredentialUpdatedObservedPermissionsItem"
    },
    "CredentialUpdatedPendingTransition": {
      "type": "string",
      "enum": [
        "NONE",
        "ACTIVATE",
        "AMEND",
        "REACTIVATE",
        "UNFREEZE",
        "RETIRE",
        "REVOKE",
        "ROTATE",
        "RESUME_TRADING"
      ],
      "description": "ACTIVATE, AMEND (capabilities/secretRef), REACTIVATE, REVOKE or ROTATE; NONE otherwise. NONE is the unset sentinel: outputs only — as input, omit the field instead; explicit NONE refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as NONE, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "CredentialUpdatedPendingTransition"
    },
    "CredentialUpdatedProbeOutcome": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "NONE",
        "AUTH_FAILED",
        "IP_NOT_WHITELISTED",
        "PERMISSION_DENIED",
        "RATE_LIMITED",
        "VENUE_UNREACHABLE",
        "STARTUP",
        "RECONNECT",
        "HEARTBEAT_TIMEOUT",
        "VENUE_MAINTENANCE",
        "BACKPRESSURE",
        "RESYNC",
        "SHUTDOWN",
        "FAILOVER",
        "LAGGING",
        "RECOVERED",
        "INVALID_KEY",
        "CLOCK_SKEW",
        "CONFIG_ERROR",
        "STREAM_STALE",
        "VENUE_UNAVAILABLE"
      ],
      "description": "UNKNOWN = never probed; NONE = the venue's key-restrictions read answered; else the probe's own failure in the credential or gate band. UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "CredentialUpdatedProbeOutcome"
    },
    "CredentialUpdatedProviderKind": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "EXCHANGE",
        "CUSTODIAN",
        "WALLET_PROVIDER",
        "CHAIN_RPC",
        "BANK"
      ],
      "description": "UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "CredentialUpdatedProviderKind"
    },
    "CredentialUpdatedStatus": {
      "type": "string",
      "enum": [
        "UNKNOWN",
        "DRAFT",
        "PENDING_APPROVAL",
        "ACTIVE",
        "SUSPENDED",
        "REVOKED"
      ],
      "description": "UNKNOWN is the unset sentinel: outputs only — as input, omit the field instead; explicit UNKNOWN refuses 400. The value set is append-only: a consumer READING this field treats an unknown name as UNKNOWN, never as an error; as input an unknown name refuses 400 — the parse is strict",
      "title": "CredentialUpdatedStatus"
    },
    "GlobalSequenceValue": {
      "type": "string",
      "pattern": "^[0-9]+$",
      "description": "The position as a decimal string — the last change this member had applied, orders, balance readings and algo instances included.",
      "title": "GlobalSequenceValue"
    }
  }
}