{
  "openapi": "3.1.0",
  "info": {
    "title": "Median read-only reports API preview",
    "version": "2026-10-09-preview",
    "description": "Preview of six company-scoped, read-only report endpoints. REST access is enabled by Median and requires a company-specific API key with the reports:read scope."
  },
  "servers": [
    {
      "url": "https://api.medianfi.com/api/v1"
    }
  ],
  "tags": [
    {
      "name": "Reports"
    }
  ],
  "paths": {
    "/companies/{companyId}/reports/pl": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getProfitAndLoss",
        "summary": "Profit and loss statement",
        "description": "P&L over a date range. When `startDate` and `endDate` are omitted the latest posted month is returned (backward-compatible with pre-date-range clients); `allTime=true` spans the first through the last posted month. `comparePeriod` optionally returns a side-by-side comparison column; `prior-period` shifts back by the range length (snapped to full months when month-aligned), `prior-year` shifts both dates back 12 months.\n",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/reportEntityId"
          },
          {
            "$ref": "#/components/parameters/reportReportingScopeId"
          },
          {
            "$ref": "#/components/parameters/reportScopeAsOf"
          },
          {
            "$ref": "#/components/parameters/reportStartDate"
          },
          {
            "$ref": "#/components/parameters/reportEndDate"
          },
          {
            "$ref": "#/components/parameters/reportAllTime"
          },
          {
            "$ref": "#/components/parameters/reportComparePeriod"
          },
          {
            "$ref": "#/components/parameters/reportGranularity"
          },
          {
            "$ref": "#/components/parameters/reportClassId"
          },
          {
            "$ref": "#/components/parameters/reportGroupBy"
          },
          {
            "name": "tagId",
            "in": "query",
            "description": "Filter the P&L to posted journal lines associated with any listed accounting tag. Supply one or more tag IDs as repeated or comma-separated values, up to 50. This filter applies to one legal entity and cannot be combined with groupBy=class.",
            "schema": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "maxItems": 50
                }
              ]
            }
          },
          {
            "name": "month",
            "in": "query",
            "deprecated": true,
            "description": "Legacy month param (YYYY-MM or YYYY-MM-DD). Prefer `startDate`/`endDate`.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "P&L payload. Includes legacy numeric/boolean self-check fields and additive `checks` execution evidence for arithmetic and monthly-cache reconciliation. `isConsistent: true` alone does not prove all checks ran. When a comparison is requested, an optional `comparison` object with the same shape plus `mode`, `startDate`, `endDate`, `period` and `hasData` is included on the payload.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/StatementBasisMetadata"
                        },
                        {
                          "$ref": "#/components/schemas/ReportSelfCheckConsistent"
                        },
                        {
                          "$ref": "#/components/schemas/LedgerRevisionStamp"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "startDate": {
                              "type": ["string", "null"],
                              "format": "date"
                            },
                            "endDate": {
                              "type": ["string", "null"],
                              "format": "date"
                            },
                            "revenue": {
                              "type": "array",
                              "description": "Operating revenue rows. Other income is reported in otherIncome, below operating income.",
                              "items": {
                                "type": "object"
                              }
                            },
                            "otherIncome": {
                              "type": "array",
                              "description": "Non-operating income rows (interest, FX gains; detail type `other_income`), `reportSection` `other_income`.",
                              "items": {
                                "type": "object"
                              }
                            },
                            "totalRevenue": {
                              "type": "number",
                              "description": "Operating revenue; excludes other income."
                            },
                            "totalRevenueCents": {
                              "type": "integer"
                            },
                            "totalCogs": {
                              "type": "number"
                            },
                            "totalOpex": {
                              "type": "number"
                            },
                            "totalExpenses": {
                              "type": "number"
                            },
                            "operatingIncome": {
                              "type": "number",
                              "description": "totalRevenue − totalExpenses."
                            },
                            "operatingIncomeCents": {
                              "type": "integer"
                            },
                            "totalOtherIncome": {
                              "type": "number"
                            },
                            "totalOtherIncomeCents": {
                              "type": "integer"
                            },
                            "netIncome": {
                              "type": "number",
                              "description": "operatingIncome + totalOtherIncome."
                            },
                            "netIncomeCents": {
                              "type": "integer"
                            },
                            "comparison": {
                              "$ref": "#/components/schemas/ProfitAndLossComparison"
                            },
                            "classFilter": {
                              "$ref": "#/components/schemas/ReportClassDescriptor"
                            },
                            "tagFilter": {
                              "type": "object",
                              "description": "Present when the P&L was filtered by tagId.",
                              "properties": {
                                "tagIds": {
                                  "type": "array",
                                  "items": {
                                    "type": "string",
                                    "format": "uuid"
                                  }
                                }
                              }
                            },
                            "classGroups": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/ProfitAndLossClassGroup"
                              }
                            },
                            "classReconciliation": {
                              "$ref": "#/components/schemas/ProfitAndLossClassReconciliation"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Active accounting class filter not found"
          },
          "422": {
            "description": "Explicit financial scope is required, or the resolved consolidated report is not yet available"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    },
    "/companies/{companyId}/reports/balance-sheet": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getBalanceSheet",
        "summary": "Balance sheet (assets, liabilities, equity)",
        "description": "Snapshot as-of a single date. `asOfDate` (or `endDate`) selects the point in time; the latest posted month is used when omitted. `comparePeriod=prior-year` compares to the same date 12 months earlier; `prior-period` compares to the previous month-end.\n",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/reportEntityId"
          },
          {
            "$ref": "#/components/parameters/reportReportingScopeId"
          },
          {
            "$ref": "#/components/parameters/reportScopeAsOf"
          },
          {
            "name": "asOfDate",
            "in": "query",
            "description": "As-of date (YYYY-MM-DD). Falls back to `endDate`, then `month`, then latest posted balance.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "description": "Alias for `asOfDate` when the client already models a date range.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/reportComparePeriod"
          },
          {
            "name": "month",
            "in": "query",
            "deprecated": true,
            "description": "Legacy month param (YYYY-MM). Prefer `asOfDate`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Balance sheet payload. Includes additive self-check totals (`totalAssets`, `totalLiabilities`, `totalEquity`, `totalLiabilitiesAndEquity`, `differenceCents`, `isBalanced`). When a comparison is requested, an optional `comparison` object with the same shape plus `mode`, `asOfDate` and `hasData` is included on the payload.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/StatementBasisMetadata"
                        },
                        {
                          "$ref": "#/components/schemas/ReportSelfCheckBalanced"
                        },
                        {
                          "$ref": "#/components/schemas/LedgerRevisionStamp"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "totalAssets": {
                              "type": "number"
                            },
                            "totalLiabilities": {
                              "type": "number"
                            },
                            "totalEquity": {
                              "type": "number"
                            },
                            "totalLiabilitiesAndEquity": {
                              "type": "number"
                            },
                            "comparison": {
                              "$ref": "#/components/schemas/BalanceSheetComparison"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "Explicit financial scope is required, or the resolved consolidated report is not yet available"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    },
    "/companies/{companyId}/reports/cash-flow": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getCashFlow",
        "summary": "Cash flow statement (indirect method)",
        "description": "Cash flow computed via the indirect method. Starts from net income and reconciles by adjusting for non-cash balance sheet movements. The sum of operating + investing + financing always equals the actual cash movement for the period; any unclassified drift is surfaced as \"Other net adjustments\" in operating. Accepts a date range; `comparePeriod` returns a side-by-side comparison column.\n",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/reportEntityId"
          },
          {
            "$ref": "#/components/parameters/reportReportingScopeId"
          },
          {
            "$ref": "#/components/parameters/reportScopeAsOf"
          },
          {
            "$ref": "#/components/parameters/reportStartDate"
          },
          {
            "$ref": "#/components/parameters/reportEndDate"
          },
          {
            "$ref": "#/components/parameters/reportAllTime"
          },
          {
            "$ref": "#/components/parameters/reportComparePeriod"
          },
          {
            "$ref": "#/components/parameters/reportGranularity"
          },
          {
            "$ref": "#/components/parameters/reportClassId"
          },
          {
            "name": "month",
            "in": "query",
            "deprecated": true,
            "description": "Legacy month param (YYYY-MM). Prefer `startDate`/`endDate`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cash flow payload. `checks` independently evaluates exact beginning-plus-statement-movement-equals-ending identity and the one-cent-tolerance ending-cash versus monthly-balance cross-check. Legacy `differenceFromBalanceSheetCents` remains an integer (zero when skipped); legacy `isConsistent` is false for any executed failure and true otherwise, including skipped checks. Optional `comparison` object on the payload when a comparison was requested.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/StatementBasisMetadata"
                        },
                        {
                          "$ref": "#/components/schemas/ReportSelfCheckConsistent"
                        },
                        {
                          "$ref": "#/components/schemas/LedgerRevisionStamp"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "startDate": {
                              "type": ["string", "null"],
                              "format": "date"
                            },
                            "endDate": {
                              "type": ["string", "null"],
                              "format": "date"
                            },
                            "comparison": {
                              "$ref": "#/components/schemas/CashFlowComparison"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Active accounting class filter not found"
          },
          "422": {
            "description": "Explicit financial scope is required, or the resolved consolidated report is not yet available"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    },
    "/companies/{companyId}/reports/general-ledger": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getGeneralLedger",
        "summary": "General ledger (recent posted journal entries)",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/reportEntityId"
          },
          {
            "$ref": "#/components/parameters/reportReportingScopeId"
          },
          {
            "$ref": "#/components/parameters/reportScopeAsOf"
          },
          {
            "$ref": "#/components/parameters/reportStartDate"
          },
          {
            "$ref": "#/components/parameters/reportEndDate"
          },
          {
            "$ref": "#/components/parameters/reportAllTime"
          },
          {
            "$ref": "#/components/parameters/reportClassId"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum journal entries per page. Defaults to 200 and is clamped to 1000.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 200
            }
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "General ledger payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "required": [
                        "currency",
                        "minorUnitExponent",
                        "items",
                        "nextCursor",
                        "hasMore"
                      ],
                      "properties": {
                        "ledgerRevision": {
                          "$ref": "#/components/schemas/LedgerRevision"
                        },
                        "currency": {
                          "type": ["string", "null"],
                          "minLength": 3,
                          "maxLength": 3
                        },
                        "minorUnitExponent": {
                          "type": ["integer", "null"],
                          "enum": [2, null]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "nextCursor": {
                          "type": ["string", "null"]
                        },
                        "hasMore": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "Explicit financial scope is required, or the resolved consolidated report is not yet available"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    },
    "/companies/{companyId}/reports/ar-aging": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getArAging",
        "summary": "Accounts receivable aging report",
        "description": "Returns open invoices grouped by customer and by age bucket (current, 1-30, 31-60, 61-90, 90+) based on due date vs asOfDate. The aggregate and each line item carry currency/minorUnitExponent; mixed, invalid, missing, and non-two-decimal currency authority fails closed with 422 rather than publishing a false total.\n",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "asOfDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AR aging payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "unlinkedAccountantEntries": {
                          "$ref": "#/components/schemas/UnlinkedAccountantEntries"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "MONETARY_AGGREGATE_CURRENCY_UNAVAILABLE"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    },
    "/companies/{companyId}/reports/ap-aging": {
      "parameters": [
        {
          "$ref": "#/components/parameters/companyId"
        }
      ],
      "get": {
        "operationId": "getApAging",
        "summary": "Accounts payable aging report",
        "description": "Returns unpaid bills grouped by vendor and by age bucket (current, 1-30, 31-60, 61-90, 90+) based on due date vs asOfDate. The aggregate and each line item carry currency/minorUnitExponent; mixed, invalid, missing, and non-two-decimal currency authority fails closed with 422 rather than publishing a false total.\n",
        "tags": ["Reports"],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "asOfDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AP aging payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "unlinkedAccountantEntries": {
                          "$ref": "#/components/schemas/UnlinkedAccountantEntries"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "MONETARY_AGGREGATE_CURRENCY_UNAVAILABLE"
          }
        },
        "x-required-median-scope": "reports:read"
      }
    }
  },
  "components": {
    "parameters": {
      "companyId": {
        "name": "companyId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "reportEntityId": {
        "name": "entityId",
        "in": "query",
        "description": "Concrete legal entity to read. Mutually exclusive with `reportingScopeId`/`scopeAsOf`. Omission is accepted only when the actor is authorized to exactly one concrete entity.\n",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "reportReportingScopeId": {
        "name": "reportingScopeId",
        "in": "query",
        "description": "Immutable within-company consolidated reporting-scope identity. Must be supplied with `scopeAsOf` and cannot be combined with `entityId`.\n",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "reportScopeAsOf": {
        "name": "scopeAsOf",
        "in": "query",
        "description": "ISO date used to pin the effective sealed reporting-scope hierarchy version. Must be supplied with `reportingScopeId`.\n",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "reportStartDate": {
        "name": "startDate",
        "in": "query",
        "description": "Inclusive period start (YYYY-MM-DD). Must be supplied together with `endDate`.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "reportEndDate": {
        "name": "endDate",
        "in": "query",
        "description": "Inclusive period end (YYYY-MM-DD). Must be supplied together with `startDate`.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "reportAllTime": {
        "name": "allTime",
        "in": "query",
        "description": "`true` reports from the first through the last posted month on the P&L and cash flow, where omitting `startDate`/`endDate` still returns only the latest posted month, so \"all time\" must be requested by name. The general ledger reads an omitted range as the whole ledger either way. Cannot be combined with `startDate`/`endDate`, `month` or a `comparePeriod` other than `none` (400).\n",
        "schema": {
          "type": "string",
          "enum": ["true"]
        }
      },
      "reportComparePeriod": {
        "name": "comparePeriod",
        "in": "query",
        "description": "Comparison mode. `prior-period` shifts back by the range length (snapped to full calendar months when the range is month-aligned). `prior-year` shifts both dates back 12 months. `none` (default) returns no comparison.\n",
        "schema": {
          "type": "string",
          "enum": ["none", "prior-period", "prior-year"],
          "default": "none"
        }
      },
      "reportGranularity": {
        "name": "granularity",
        "in": "query",
        "description": "Override inferred granularity. Advisory; the service still validates the actual range.",
        "schema": {
          "type": "string",
          "enum": ["month", "quarter", "year"]
        }
      },
      "reportClassId": {
        "name": "classId",
        "in": "query",
        "description": "Optional class filter for class-aware report endpoints. Pass a company-scoped accounting class UUID, or `unassigned` for journal lines with no class. Customer-facing use returns data only for active approved company classes.\n",
        "schema": {
          "type": "string"
        }
      },
      "reportGroupBy": {
        "name": "groupBy",
        "in": "query",
        "description": "Add a grouped class breakdown where supported.",
        "schema": {
          "type": "string",
          "enum": ["class"]
        }
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "StatementBasisMetadata": {
        "type": "object",
        "required": ["configuredBasis", "configuredBasisSource", "reportBasis"],
        "description": "Reports return on an accrual basis. This metadata also reports the company configured accounting basis and its source; it does not change report calculations.",
        "properties": {
          "configuredBasis": {
            "type": "string",
            "enum": ["cash", "accrual", "unknown"]
          },
          "configuredBasisSource": {
            "type": "string",
            "enum": [
              "statement_basis",
              "tax_settings",
              "company",
              "missing",
              "unavailable"
            ]
          },
          "reportBasis": {
            "type": "string",
            "enum": ["accrual", "cash"]
          },
          "fiscalYearEnd": {
            "type": ["string", "null"],
            "pattern": "^[0-9]{2}-[0-9]{2}$",
            "description": "Fiscal year end in MM-DD format, or null when unknown."
          },
          "fiscalYearReview": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": ["unknown"]
              }
            ],
            "description": "True when the configured fiscal year differs from the calendar year and may need review. Unknown when the configured year end could not be determined."
          },
          "fiscalYearReviewReason": {
            "type": ["string", "null"],
            "description": "Reason a fiscal year review is indicated, or null when no review is indicated."
          },
          "cashBasis": {
            "$ref": "#/components/schemas/CashBasisSummary"
          }
        }
      },
      "ReportSelfCheckConsistent": {
        "type": "object",
        "required": ["checks"],
        "description": "Compatibility fields plus explicit check evidence for P&L/cash flow. isConsistent remains boolean: false for any executed failure, true otherwise (including skipped checks). It is not proof that all checks ran. Legacy differences remain integers; skipped differences retain zero. Inspect checks for execution, signed differences, applicability and per-check tolerances.\n",
        "properties": {
          "isConsistent": {
            "type": "boolean"
          },
          "differenceCents": {
            "type": "integer",
            "description": "P&L only. Revenue minus expenses minus net income."
          },
          "cacheDifferenceCents": {
            "type": "integer",
            "description": "P&L primary only. Largest signed journal-minus-cache discrepancy; zero if skipped."
          },
          "differenceFromBalanceSheetCents": {
            "type": "integer",
            "description": "Cash flow only. Ending cash minus monthly-balance cash; zero if skipped."
          },
          "checks": {
            "$ref": "#/components/schemas/ReportChecks"
          }
        }
      },
      "LedgerRevisionStamp": {
        "type": "object",
        "properties": {
          "ledgerRevision": {
            "$ref": "#/components/schemas/LedgerRevision"
          }
        }
      },
      "ProfitAndLossComparison": {
        "type": "object",
        "required": [
          "configuredBasis",
          "configuredBasisSource",
          "reportBasis",
          "checks"
        ],
        "description": "Comparison data returned when comparePeriod is requested. It contains the comparison period, mode, dates, hasData, and the P&L fields for that period.",
        "properties": {
          "configuredBasis": {
            "type": "string",
            "enum": ["cash", "accrual", "unknown"]
          },
          "configuredBasisSource": {
            "type": "string",
            "enum": [
              "statement_basis",
              "tax_settings",
              "company",
              "missing",
              "unavailable"
            ]
          },
          "reportBasis": {
            "type": "string",
            "enum": ["accrual", "cash"]
          },
          "fiscalYearEnd": {
            "type": ["string", "null"],
            "pattern": "^[0-9]{2}-[0-9]{2}$",
            "description": "Fiscal year end in MM-DD format, or null when unknown."
          },
          "fiscalYearReview": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": ["unknown"]
              }
            ],
            "description": "True when the configured fiscal year differs from the calendar year and may need review. Unknown when the configured year end could not be determined."
          },
          "fiscalYearReviewReason": {
            "type": ["string", "null"],
            "description": "Reason a fiscal year review is indicated, or null when no review is indicated."
          },
          "mode": {
            "$ref": "#/components/schemas/ReportComparisonMode"
          },
          "period": {
            "type": "string"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "hasData": {
            "type": "boolean"
          },
          "revenue": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "cogs": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "expenses": {
            "type": "object"
          },
          "otherIncome": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "totalRevenue": {
            "type": "number"
          },
          "totalCogs": {
            "type": "number"
          },
          "totalOpex": {
            "type": "number"
          },
          "totalExpenses": {
            "type": "number"
          },
          "operatingIncome": {
            "type": "number"
          },
          "totalOtherIncome": {
            "type": "number"
          },
          "netIncome": {
            "type": "number"
          },
          "differenceCents": {
            "type": "integer"
          },
          "isConsistent": {
            "type": "boolean"
          },
          "checks": {
            "$ref": "#/components/schemas/ReportChecks"
          }
        }
      },
      "ReportClassDescriptor": {
        "type": "object",
        "required": ["classId", "className", "isUnassigned"],
        "properties": {
          "classId": {
            "type": ["string", "null"],
            "description": "Null represents the explicit Unassigned class bucket."
          },
          "className": {
            "type": "string"
          },
          "isUnassigned": {
            "type": "boolean"
          }
        }
      },
      "ProfitAndLossClassGroup": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ReportClassDescriptor"
          },
          {
            "type": "object",
            "properties": {
              "period": {
                "type": "string"
              },
              "startDate": {
                "type": "string",
                "format": "date"
              },
              "endDate": {
                "type": "string",
                "format": "date"
              },
              "revenue": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "cogs": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "expenses": {
                "type": "object"
              },
              "otherIncome": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "totalRevenue": {
                "type": "number"
              },
              "totalCogs": {
                "type": "number"
              },
              "totalOpex": {
                "type": "number"
              },
              "totalExpenses": {
                "type": "number"
              },
              "operatingIncome": {
                "type": "number"
              },
              "totalOtherIncome": {
                "type": "number"
              },
              "netIncome": {
                "type": "number"
              },
              "differenceCents": {
                "type": "integer"
              },
              "isConsistent": {
                "type": "boolean"
              },
              "hasData": {
                "type": "boolean"
              }
            }
          }
        ]
      },
      "ProfitAndLossClassReconciliation": {
        "type": "object",
        "description": "Reconciles summed class groups to the unfiltered P&L totals for the same company, period, and basis.\n",
        "properties": {
          "totalRevenueCents": {
            "type": "object",
            "additionalProperties": true
          },
          "totalCogsCents": {
            "type": "object",
            "additionalProperties": true
          },
          "totalOpexCents": {
            "type": "object",
            "additionalProperties": true
          },
          "totalExpensesCents": {
            "type": "object",
            "additionalProperties": true
          },
          "netIncomeCents": {
            "type": "object",
            "additionalProperties": true
          },
          "isConsistent": {
            "type": "boolean"
          }
        }
      },
      "ReportSelfCheckBalanced": {
        "type": "object",
        "description": "Non-failing self-check fields surfaced on reports whose correctness is expressed as a balance identity (trial balance, balance sheet). When the underlying GL reconciles, `isBalanced` is true and `differenceCents` is 0. Tolerance is 1 cent.\n",
        "properties": {
          "isBalanced": {
            "type": "boolean"
          },
          "differenceCents": {
            "type": "integer",
            "description": "Signed drift in cents. Positive = debits exceed credits (or assets exceed L+E)."
          }
        }
      },
      "BalanceSheetComparison": {
        "type": "object",
        "required": ["configuredBasis", "configuredBasisSource", "reportBasis"],
        "description": "Comparison data returned when comparePeriod is requested. It contains the comparison period, mode, asOfDate, hasData, and the balance sheet fields for that period.",
        "properties": {
          "configuredBasis": {
            "type": "string",
            "enum": ["cash", "accrual", "unknown"]
          },
          "configuredBasisSource": {
            "type": "string",
            "enum": [
              "statement_basis",
              "tax_settings",
              "company",
              "missing",
              "unavailable"
            ]
          },
          "reportBasis": {
            "type": "string",
            "enum": ["accrual", "cash"]
          },
          "fiscalYearEnd": {
            "type": ["string", "null"],
            "pattern": "^[0-9]{2}-[0-9]{2}$",
            "description": "Fiscal year end in MM-DD format, or null when unknown."
          },
          "fiscalYearReview": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": ["unknown"]
              }
            ],
            "description": "True when the configured fiscal year differs from the calendar year and may need review. Unknown when the configured year end could not be determined."
          },
          "fiscalYearReviewReason": {
            "type": ["string", "null"],
            "description": "Reason a fiscal year review is indicated, or null when no review is indicated."
          },
          "mode": {
            "$ref": "#/components/schemas/ReportComparisonMode"
          },
          "asOfDate": {
            "type": "string",
            "format": "date"
          },
          "hasData": {
            "type": "boolean"
          },
          "assets": {
            "type": "object"
          },
          "liabilities": {
            "type": "object"
          },
          "equity": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "totalAssets": {
            "type": "number"
          },
          "totalLiabilities": {
            "type": "number"
          },
          "totalEquity": {
            "type": "number"
          },
          "totalLiabilitiesAndEquity": {
            "type": "number"
          },
          "differenceCents": {
            "type": "integer"
          },
          "isBalanced": {
            "type": "boolean"
          }
        }
      },
      "CashFlowComparison": {
        "type": "object",
        "required": [
          "configuredBasis",
          "configuredBasisSource",
          "reportBasis",
          "checks"
        ],
        "description": "Comparison data returned when comparePeriod is requested. It contains the comparison period, mode, dates, hasData, and the cash flow fields for that period.",
        "properties": {
          "configuredBasis": {
            "type": "string",
            "enum": ["cash", "accrual", "unknown"]
          },
          "configuredBasisSource": {
            "type": "string",
            "enum": [
              "statement_basis",
              "tax_settings",
              "company",
              "missing",
              "unavailable"
            ]
          },
          "reportBasis": {
            "type": "string",
            "enum": ["accrual", "cash"]
          },
          "fiscalYearEnd": {
            "type": ["string", "null"],
            "pattern": "^[0-9]{2}-[0-9]{2}$",
            "description": "Fiscal year end in MM-DD format, or null when unknown."
          },
          "fiscalYearReview": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": ["unknown"]
              }
            ],
            "description": "True when the configured fiscal year differs from the calendar year and may need review. Unknown when the configured year end could not be determined."
          },
          "fiscalYearReviewReason": {
            "type": ["string", "null"],
            "description": "Reason a fiscal year review is indicated, or null when no review is indicated."
          },
          "mode": {
            "$ref": "#/components/schemas/ReportComparisonMode"
          },
          "period": {
            "type": "string"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "hasData": {
            "type": "boolean"
          },
          "operating": {
            "type": "object",
            "properties": {
              "netIncome": {
                "type": "number"
              },
              "adjustments": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "investing": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "financing": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "beginningCash": {
            "type": "number"
          },
          "endingCash": {
            "type": "number"
          },
          "differenceFromBalanceSheetCents": {
            "type": "integer"
          },
          "isConsistent": {
            "type": "boolean"
          },
          "checks": {
            "$ref": "#/components/schemas/ReportChecks"
          }
        }
      },
      "LedgerRevision": {
        "type": ["string", "null"],
        "pattern": "^lr1_[0-9a-f]{24}$",
        "description": "Opaque token for the posted ledger state used for this report read. Equal tokens indicate the same ledger state. If the value is null, the ledger changed during both read attempts; retry before combining pages or statements. This value is not a credential."
      },
      "UnlinkedAccountantEntries": {
        "type": "object",
        "description": "Posted invoice, bill, or payment entries present in the general ledger but not linked to an aging item. This field is present only when the aging report uses item-based data. count is exact; items is capped at 50, newest first.",
        "required": ["count", "items"],
        "properties": {
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "items": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/UnlinkedAccountantEntry"
            }
          }
        }
      },
      "CashBasisSummary": {
        "type": "object",
        "required": [
          "policyId",
          "policyVersion",
          "reviewedBy",
          "throughDate",
          "scope",
          "droppedEntryCount",
          "retargetedLineCount",
          "retargetedCents",
          "accrualNetIncomeCents",
          "timingMovementCents",
          "evidence",
          "reviewItemCount",
          "reviewItems",
          "basisNotice"
        ],
        "description": "The `cashBasis` block of a statement whose `reportBasis` is `cash`. Present only then; an accrual statement has no such key. Money is integer ledger cents. Cash net income over the scope equals `accrualNetIncomeCents - timingMovementCents`, so the difference from accrual can be re-derived by hand. The view is not a GAAP statement; `basisNotice` says so and clients print it with the statement.\n",
        "properties": {
          "policyId": {
            "type": "string",
            "enum": ["cash-basis-v1"]
          },
          "policyVersion": {
            "type": "integer",
            "minimum": 1,
            "description": "Revision of the rules under this policy id."
          },
          "reviewedBy": {
            "type": "string",
            "description": "Reviewer of the policy the statement was calculated under."
          },
          "throughDate": {
            "type": "string",
            "format": "date",
            "description": "The cash view was built from inception through this date."
          },
          "scope": {
            "$ref": "#/components/schemas/CashBasisScope"
          },
          "droppedEntryCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Entries left out of the cash view because they only move amounts between income or expense and a receivable, payable, deferred-revenue or accrued account. Whole view, inception through `throughDate`."
          },
          "retargetedLineCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Receivable or payable settlement lines moved onto the income or expense accounts of the invoice or bill they settle. Whole view, inception through `throughDate`."
          },
          "retargetedCents": {
            "type": "integer",
            "description": "Sum of the retargeted settlement lines. Whole view, inception through `throughDate`."
          },
          "accrualNetIncomeCents": {
            "type": "integer",
            "description": "Accrual net income over the scope (revenue minus expense, closing entries excluded)."
          },
          "timingMovementCents": {
            "type": "integer",
            "description": "Change over the scope in the debit-positive balance of the receivable, payable, deferred-revenue and accrued accounts, on the accrual ledger."
          },
          "evidence": {
            "type": "object",
            "required": [
              "uncategorizedIncomeCents",
              "uncategorizedExpenseCents",
              "suspenseBalanceCents"
            ],
            "description": "Completeness figures over the scope. They have no basis effect.",
            "properties": {
              "uncategorizedIncomeCents": {
                "type": "integer"
              },
              "uncategorizedExpenseCents": {
                "type": "integer"
              },
              "suspenseBalanceCents": {
                "type": "integer",
                "description": "Balance of suspense and ask-my-client accounts at the scope's end, debit positive."
              }
            }
          },
          "reviewItemCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Review items dated inside the scope, including those beyond the cap on `reviewItems`."
          },
          "reviewItems": {
            "type": "array",
            "maxItems": 50,
            "description": "Kept income or expense lines with no cash, card or clearing line behind them, earliest first. They are identical on both bases and listed for review. No memo or description text.",
            "items": {
              "type": "object",
              "required": [
                "kind",
                "entryId",
                "entryDate",
                "accountCode",
                "amountCents"
              ],
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": ["pnl_against_equity_or_suspense"]
                },
                "entryId": {
                  "type": "string"
                },
                "entryDate": {
                  "type": "string",
                  "format": "date"
                },
                "accountCode": {
                  "type": "string",
                  "description": "Code of the income or expense account."
                },
                "amountCents": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Ledger cents of that line."
                }
              }
            }
          },
          "basisNotice": {
            "type": "string",
            "description": "Fixed wording that the view is not a GAAP statement. Clients print it with the statement instead of composing their own.",
            "enum": [
              "Cash basis. Not a GAAP statement: revenue is recognized when cash is received, not when performance obligations are satisfied (ASC 606)."
            ]
          }
        }
      },
      "ReportChecks": {
        "type": "object",
        "required": ["version", "status", "items"],
        "description": "Summary and detail for report consistency checks. status is PASS when all expected checks pass, FAIL when a check fails, and UNKNOWN when a check cannot establish a result. Each item includes its status, difference, tolerance, and scope.",
        "properties": {
          "version": {
            "type": "integer",
            "const": 1
          },
          "status": {
            "type": "string",
            "enum": ["PASS", "FAIL", "UNKNOWN"]
          },
          "items": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/ReportCheck"
            }
          }
        }
      },
      "ReportComparisonMode": {
        "type": "string",
        "enum": ["none", "prior-period", "prior-year"]
      },
      "ProblemDetail": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "description": "For application errors, a stable UPPER_SNAKE machine code (for example `NOT_FOUND`, `FORECAST_NOT_FOUND`, `VALIDATION_ERROR`). Clients branch on this, never on `detail` wording. A few transport level responses use a short human title instead.\n"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation of the error. Use traceId when contacting Median support."
          },
          "instance": {
            "type": "string"
          },
          "traceId": {
            "type": "string"
          },
          "retryAfter": {
            "type": "integer",
            "minimum": 0,
            "description": "Whole seconds until retry is allowed for a 429 response"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "extensions": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "UnlinkedAccountantEntry": {
        "type": "object",
        "required": [
          "journalEntryId",
          "entryDate",
          "eventType",
          "amountCents",
          "currency"
        ],
        "properties": {
          "journalEntryId": {
            "type": "string",
            "format": "uuid"
          },
          "entryDate": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "type": "string",
            "description": "Stored business-event type, e.g. vendor_bill"
          },
          "amountCents": {
            "type": ["integer", "null"],
            "description": "Total debits in minor units; null when the entry has no single-currency lines."
          },
          "currency": {
            "type": ["string", "null"]
          }
        }
      },
      "CashBasisScope": {
        "type": "object",
        "required": ["kind", "entityId", "startDate", "endDate"],
        "description": "What a cash-basis statement or refusal answers for. `range` (P&L, cash flow, income-statement drill-down) is blocked by a refusal dated inside `startDate`..`endDate`; `as_of` (balance sheet, trial balance, balance-sheet drill-down) by any refusal dated on or before `endDate`.\n",
        "properties": {
          "kind": {
            "type": "string",
            "enum": ["range", "as_of"]
          },
          "entityId": {
            "type": ["string", "null"],
            "description": "Null when the request named no entity."
          },
          "startDate": {
            "type": ["string", "null"],
            "format": "date",
            "description": "Null on `as_of`, and when the request carried no explicit start."
          },
          "endDate": {
            "type": ["string", "null"],
            "format": "date",
            "description": "Range end or as-of date. Null only when the request carried no explicit date."
          }
        }
      },
      "ReportCheck": {
        "type": "object",
        "required": [
          "id",
          "status",
          "reason",
          "differenceCents",
          "toleranceCents",
          "scope"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "pl_arithmetic",
              "pl_cache",
              "cash_flow_identity",
              "cash_flow_balance_sheet"
            ]
          },
          "status": {
            "type": "string",
            "enum": ["PASS", "FAIL", "NOT_CHECKED", "UNKNOWN"]
          },
          "reason": {
            "type": ["string", "null"],
            "description": "Null when a check ran. A reason is provided when a check was skipped or its result is unknown."
          },
          "differenceCents": {
            "type": ["integer", "null"],
            "description": "Signed difference in the report currency minor units. Null when the check did not run."
          },
          "toleranceCents": {
            "type": "integer",
            "minimum": 0,
            "description": "Allowed difference in the report currency minor units."
          },
          "scope": {
            "$ref": "#/components/schemas/ReportCheckScope"
          }
        },
        "oneOf": [
          {
            "required": ["status", "reason", "differenceCents"],
            "properties": {
              "status": {
                "enum": ["PASS", "FAIL"]
              },
              "reason": {
                "type": "null"
              },
              "differenceCents": {
                "type": "integer"
              }
            }
          },
          {
            "required": ["status", "reason", "differenceCents"],
            "properties": {
              "status": {
                "enum": ["NOT_CHECKED", "UNKNOWN"]
              },
              "reason": {
                "type": "string",
                "minLength": 1
              },
              "differenceCents": {
                "type": "null"
              }
            }
          }
        ]
      },
      "ReportCheckScope": {
        "type": "object",
        "required": [
          "companyId",
          "entityId",
          "classId",
          "startDate",
          "endDate",
          "source"
        ],
        "properties": {
          "companyId": {
            "type": "string"
          },
          "entityId": {
            "type": ["string", "null"]
          },
          "classId": {
            "type": ["string", "null"],
            "description": "Null means no class filter; the literal unassigned selector is retained as a string."
          },
          "startDate": {
            "type": ["string", "null"],
            "format": "date"
          },
          "endDate": {
            "type": ["string", "null"],
            "format": "date"
          },
          "source": {
            "type": "string",
            "enum": ["journal_entries", "monthly_balances", "issued_snapshot"],
            "description": "Identifies the data source used for the individual check."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Request failed validation",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetail"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetail"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Company-scoped key provisioned by Median. This preview uses the reports:read scope."
      }
    }
  }
}
