{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:usage:v0",
  "title": "CEDE usage, v0",
  "description": "What an account has used, in the two units SPEC.md section 6 bills in: per object and per model run. Returned by GET /usage. This is the platform's own record and the only thing an invoice is derived from — a holder reading this document and an invoice for the same period must see the same quantities, exactly. It describes computation performed, never a transaction: Cede holds no money and is party to nothing (SPEC.md section 4).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "account_id",
    "plan",
    "status",
    "units",
    "total_units",
    "billed_units",
    "free_units_remaining",
    "events"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
      "description": "Version of this usage schema the payload conforms to."
    },
    "account_id": {
      "type": "string",
      "pattern": "^acct_[0-9a-f]{24}$",
      "description": "The account this usage belongs to."
    },
    "plan": {
      "enum": ["sandbox"],
      "description": "The commercial plan the usage was recorded under (SPEC.md section 6). Further plans arrive by schema version bump, never by silent widening."
    },
    "status": {
      "enum": ["active", "cancelled"],
      "description": "Whether the account is still metered. A cancelled account records no further usage and the billable verbs refuse it with 402 account_cancelled."
    },
    "units": {
      "type": "object",
      "additionalProperties": false,
      "required": ["object", "model_run"],
      "description": "Units used, by kind. These are the quantities an invoice for this account carries.",
      "properties": {
        "object": {
          "type": "integer",
          "minimum": 0,
          "description": "Canonical risk objects made — the objects an ingest run produced, and each structure variant cut from one. Storing an object the caller already had (POST /objects) is not among them: no normalisation and no model ran, so nothing is billed for it."
        },
        "model_run": {
          "type": "integer",
          "minimum": 0,
          "description": "Model runs performed — a price, a backtest, or a run of a registry model. All three run a model over an object, and SPEC.md section 6 bills them alike."
        }
      }
    },
    "total_units": {
      "type": "integer",
      "minimum": 0,
      "description": "Every unit above, summed. The number an invoice's line quantities must total."
    },
    "billed_units": {
      "type": "integer",
      "minimum": 0,
      "description": "How many of those units an invoice already carries. The difference is what the next settlement will invoice."
    },
    "free_units_remaining": {
      "type": "integer",
      "minimum": 0,
      "description": "Units left in the free sandbox allowance (SPEC.md section 6). Allowance-covered usage is metered and invoiced like any other, at a zero unit amount, so the quantities are the usage whether it was paid for or not."
    },
    "events": {
      "type": "array",
      "description": "Every metered call, oldest first. One event per call: polling a job or retrying an idempotent request never adds a second.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "verb", "unit", "subject", "quantity", "recorded_at"],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^use_[0-9a-f]{1,32}$",
            "description": "Identity of the metering event."
          },
          "verb": {
            "type": "string",
            "minLength": 1,
            "description": "What was called: the verb of SPEC.md section 3, or the route that created the object."
          },
          "unit": {
            "enum": ["object", "model_run"],
            "description": "Which billable unit this call produced."
          },
          "subject": {
            "type": "string",
            "minLength": 1,
            "description": "The object, job or artifact this event is about."
          },
          "quantity": {
            "type": "integer",
            "minimum": 0,
            "description": "How many units this one call produced. Usually 1, and more when a single call mints more than one: an ingest of a file holding a hundred objects is one call carrying a quantity of a hundred. The quantities here are what an invoice's line quantities total, so a reader reconciling the two sums this member and never counts rows."
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the call was metered, RFC 3339 in UTC."
          },
          "invoice_id": {
            "type": "string",
            "minLength": 1,
            "description": "The invoice that carries this event, once one does. Absent while the usage is unbilled."
          }
        }
      }
    },
    "invoices": {
      "type": "array",
      "description": "Invoices issued for this account by this deployment, oldest first. Absent when billing is dark (no Stripe key configured), in which case usage is metered and readable here and nothing is sent anywhere.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "status", "quantities"],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "description": "The invoice's identity at the payment processor."
          },
          "status": {
            "type": "string",
            "minLength": 1,
            "description": "The invoice's status as the processor reported it when it was issued."
          },
          "quantities": {
            "type": "object",
            "additionalProperties": {"type": "integer", "minimum": 0},
            "description": "Units this invoice carries, by kind. Equal to the usage it settled, exactly."
          }
        }
      }
    }
  }
}
