> ## Documentation Index
> Fetch the complete documentation index at: https://docs.financialdatasets.rip/llms.txt
> Use this file to discover all available pages before exploring further.

# Historical

> Margins, turnovers, and growth rates computed from the statement matrix, one row per reporting period.

This route returns one row per historical reporting period: margins, turnovers, and growth rates computed from the statement matrix behind [Financial Statements](/datasets/financial-statements/income-statements). It has no market price attached to past periods, so valuation fields are left out. Use [Snapshot](/datasets/financial-metrics/snapshot) instead for current market cap, P/E, and other live valuation multiples.

## What this answers

* How a company's gross margin or net margin has trended over recent fiscal periods.
* How efficiently a company turns over inventory or receivables, by period.

## Coverage

|                |                                                                |
| -------------- | -------------------------------------------------------------- |
| Provider chain | DefiLlama `/equities/v1/statements` and `/equities/v1/filings` |
| Measured cost  | \$0.0012 per call                                              |
| Cache TTL      | 600 seconds, see [Caching](/guides/caching)                    |

## Query rules

`ticker` is required. `period` accepts `annual`, `quarterly`, or `ttm`, and defaults to `annual` on this REST route, unlike the underlying MCP tool's `ttm` default. `limit` defaults to `4` and caps at `100`. The same `report_period*` date filters as the statements routes apply here too.

This route always omits `enterprise_value`, every `price_to_*` ratio, EV multiples, `free_cash_flow_yield`, `peg_ratio`, `return_on_invested_capital`, `currency`, and `filing_datetime`. None of these are sourceable for a past period without a historical market price this server does not have. `ttm` rows also drop filing identity (`accession_number`, `form_type`, `filing_url`, `filing_date`), since a trailing-twelve-month window spans more than one filing. Our own live captures show filing identity absent even on annual rows in practice, the filings join is best effort, not guaranteed on every row.

## Worked example

```bash curl theme={"theme":"css-variables"}
curl -s 'https://financialdatasets.rip/financial-metrics?ticker=AAPL&period=annual&limit=1' \
  -H 'X-API-KEY: <your-api-key>'
```

```json Response theme={"theme":"css-variables"}
{
  "financial_metrics": [
    {
      "ticker": "AAPL",
      "report_period": "2025-09-30",
      "fiscal_period": "FY2025",
      "period": "annual",
      "gross_margin": 0.4865291555900202,
      "operating_margin": 0.3317,
      "net_margin": 0.27618604910212224
    }
  ]
}
```


## OpenAPI

````yaml api-reference/openapi.json GET /financial-metrics
openapi: 3.1.0
info:
  title: Monid Finance API
  version: 1.0.0
  summary: A Financial Datasets-compatible finance REST API backed by Monid providers.
  description: >-
    US equities fundamentals, prices, filings, news, insider trades, and a stock
    screener, generated faithfully from this repository's Go source
    (go/httpapi/rest.go, go/service, go/fd). This server is drop-in compatible
    with the Financial Datasets REST and MCP interface: the same route shapes,
    the same query parameters, and response objects whose field names and field
    order match the Financial Datasets contract, captured 2026-09-04. It is an
    independent, Monid-backed implementation; it is not affiliated with or
    endorsed by Financial Datasets, and no Financial Datasets data or outputs
    are used.


    **Bring your own Monid API key.** Every request carries the caller's own key
    in the `X-API-KEY` header. Usage bills that caller's own Monid wallet; this
    server never stores or logs the key. Get a key at https://monid.ai.


    **Coverage.** All 54 Financial Datasets REST paths are registered. all 54
    return data; most call live Monid providers, and a handful (the static
    catalogs and the accept-universe coverage lists; see each operation's
    description) never call Monid at all. Every registered path answers from a
    live source; there are no not_implemented stubs. /kpi/metrics/sectors
    returns the screener provider's own industry taxonomy and
    /index-funds/tickers the upstream fund universe, both scraped through Monid
    on demand. See docs/compatibility.md for every deliberate deviation and
    docs/openapi-notes.md for how this document was derived.


    **Errors and soft failures.** Every failure — real HTTP error and zero-cost
    soft failure alike — uses the two-key body `{"error": <code>, "message":
    <detail>}`. Some routes answer `not_found` with HTTP 200 rather than a 4xx
    status (see each operation's description).


    **Pagination.** List routes return a wrapped envelope (e.g.
    `{"income_statements": [...], "next_page_url": "..."}`) with opaque,
    server-minted base64url `cursor` tokens. Pages hold 10 records (100 for
    /prices). `next_page_url` is an absolute URL on this deployment's own host,
    present only when more records remain.


    **Ticker coverage lists are accept-universes, not data-coverage claims.**
    The eight `*/tickers` routes (e.g. /company/facts/tickers,
    /kpi/metrics/tickers) all answer from the same ~3,227-ticker US equity
    catalog: the tickers a route will ACCEPT as input, never a promise that
    every one of them has data for that particular dataset - this server has not
    measured per-dataset coverage. Their own `limit` (default 1000, max 5000)
    and `cursor` query parameters page through that shared universe
    independently of every other route's pagination convention above.
  contact:
    url: https://docs.financialdatasets.rip
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://financialdatasets.rip
    description: Production (Fly.io).
security:
  - ApiKeyAuth: []
tags:
  - name: Financial statements
    description: Income statements, balance sheets, cash flow statements.
  - name: Financial metrics
    description: Historical financial metrics and the live snapshot.
  - name: Earnings
    description: Earnings records composed from SEC filing events.
  - name: Filings
    description: SEC filing index and extracted filing sections.
  - name: Prices
    description: Historical OHLCV prices and the live price snapshot.
  - name: News
    description: Ticker-matched news.
  - name: Insider trades
    description: SEC Form 4 insider trading transactions.
  - name: Screener
    description: Stock screener and its executable filter catalog.
  - name: Company
    description: Company facts.
  - name: KPIs
    description: >-
      KPI-taxonomy metrics, forward guidance, and non-GAAP metrics extracted
      from SEC filings.
  - name: Macroeconomics
    description: Central bank policy interest rates.
  - name: Index funds
    description: ETF / index-fund holdings.
  - name: Institutional holdings
    description: SEC Form 13F institutional-holdings rows.
  - name: Ownership
    description: Insider, beneficial (13D/13G) and activist ownership state.
  - name: Not implemented
    description: >-
      Routes registered for Financial Datasets route parity; always a zero-cost
      not_implemented stub today.
paths:
  /financial-metrics:
    get:
      tags:
        - Financial metrics
      summary: Get historical financial metrics
      description: >-
        Fetches historical financial-metrics rows (margins, turnovers, growth
        rates, per-share figures) computed from the statement matrix, one row
        per reporting period. Valuation fields that need a historical market
        price (enterprise_value, price_to_* ratios, EV multiples,
        free_cash_flow_yield, peg_ratio, return_on_invested_capital) plus
        currency and filing_datetime are never sourced and are always omitted;
        margins/ROE/ROA use ending balances, turnovers use average balances. ttm
        rows also omit accession_number, form_type, filing_url, and filing_date.


        period default note: the underlying get_financial_metrics MCP tool's own
        JSON Schema default is "ttm". This REST route always fills a period
        value before calling that tool, so an omitted `period` query parameter
        defaults to "annual" here instead.
      operationId: get_financial_metrics
      parameters:
        - name: ticker
          in: query
          required: true
          schema:
            type: string
          description: >-
            Uppercase-normalized ticker symbol (1-20
            letters/digits/dots/hyphens).
        - name: period
          in: query
          required: false
          schema:
            type: string
            enum:
              - annual
              - quarterly
              - ttm
            default: annual
          description: >-
            Reporting period. REST-level default is "annual"; the tool/FD-schema
            default is "ttm".
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 4
          description: 'Number of records to retrieve (default: 4, max: 100).'
        - name: report_period
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Exact report_period date filter (YYYY-MM-DD).
        - name: report_period_gte
          in: query
          required: false
          schema:
            type: string
            format: date
          description: report_period >= this date (YYYY-MM-DD, inclusive).
        - name: report_period_lte
          in: query
          required: false
          schema:
            type: string
            format: date
          description: report_period <= this date (YYYY-MM-DD, inclusive).
        - name: report_period_gt
          in: query
          required: false
          schema:
            type: string
            format: date
          description: report_period > this date (YYYY-MM-DD, exclusive).
        - name: report_period_lt
          in: query
          required: false
          schema:
            type: string
            format: date
          description: report_period < this date (YYYY-MM-DD, exclusive).
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: >-
            Opaque base64url pagination cursor from a previous response's
            next_page_url. Omit for the first page.
      responses:
        '200':
          description: >-
            A page of financial-metrics rows, newest report_period first.
            Identity fields (ticker, report_period, fiscal_period, period) and
            margins (gross_margin, operating_margin, net_margin) are real values
            captured from our own live API
            (docs/our-live-samples/income_statements.json for identity,
            docs/our-live-samples/metrics_snapshot.json for the margin ratios,
            which are identical for FY2025). Filing-identity fields
            (accession_number, form_type, filing_url, filing_date) and every
            valuation field are omitted: our own live capture did not have them
            populated for this row, so they are not asserted here (see
            docs/openapi-notes.md).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinancialMetricsResponse'
              examples:
                example:
                  value:
                    financial_metrics:
                      - ticker: AAPL
                        report_period: '2025-09-30'
                        fiscal_period: FY2025
                        period: annual
                        gross_margin: 0.4865291555900202
                        operating_margin: 0.3317
                        net_margin: 0.27618604910212224
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '502':
          $ref: '#/components/responses/BadGateway'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
components:
  schemas:
    FinancialMetricsResponse:
      type: object
      title: FinancialMetricsResponse
      description: >-
        Wrapped list envelope: {"financial_metrics": [...], "next_page_url":
        "..."}. Page size is 10 records (100 for /prices).
      properties:
        financial_metrics:
          type: array
          items:
            $ref: '#/components/schemas/FinancialMetric'
        next_page_url:
          type: string
          format: uri
          description: >-
            Absolute URL for the next page, present only when more records
            remain. Built from an opaque base64url cursor over this deployment's
            own host (never a fixed facade base).
      required:
        - financial_metrics
    FinancialMetric:
      type: object
      title: FinancialMetric
      description: >-
        One historical financial-metrics record. This response is built
        dynamically in Go (service.orderMetricsRecord) rather than from a fixed
        struct, but it follows the fixed key order in service.metricsKeyOrder,
        reproduced here as the property order. Valuation fields that require a
        historical market price (enterprise_value, price_to_* ratios, EV
        multiples, free_cash_flow_yield, peg_ratio, return_on_invested_capital)
        plus currency and filing_datetime are never sourced by the validated
        route and are always omitted. ttm rows additionally omit
        accession_number, form_type, filing_url, and filing_date. Every field is
        a pointer with omitempty in the Go source: unsourced values are omitted
        from the response, never nulled or fabricated.
      properties:
        ticker:
          type: string
          description: Uppercase ticker symbol.
        report_period:
          type: string
          description: Reporting period end date (YYYY-MM-DD).
          format: date
        fiscal_period:
          type: string
          description: Fiscal-period label. Omitted for ttm rows.
        period:
          type: string
          description: 'Echoes the requested period: annual, quarterly, or ttm.'
        currency:
          type: string
          description: Not sourced by the validated route; always omitted today.
        accession_number:
          type: string
          description: >-
            SEC EDGAR accession number of the source filing. Omitted for ttm
            rows.
        form_type:
          type: string
          description: SEC form type of the source filing. Omitted for ttm rows.
        filing_url:
          type: string
          description: SEC EDGAR filing index URL. Omitted for ttm rows.
          format: uri
        filing_date:
          type: string
          description: Date the source filing was filed. Omitted for ttm rows.
          format: date
        filing_datetime:
          type: string
          description: Not sourced by the validated route; always omitted today.
          format: date-time
        enterprise_value:
          type: number
          description: >-
            Not sourced by the validated route: historical per-period market
            values are not sourceable without fabricating data. Always omitted
            today.
        price_to_earnings_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        price_to_book_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        price_to_sales_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        enterprise_value_to_ebitda_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        enterprise_value_to_revenue_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        free_cash_flow_yield:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market
            price). Always omitted today.
        peg_ratio:
          type: number
          description: >-
            Not sourced by the validated route (requires historical market price
            and consensus growth). Always omitted today.
        gross_margin:
          type: number
          description: Gross margin, computed from the statement matrix.
        operating_margin:
          type: number
          description: Operating margin, computed from the statement matrix.
        net_margin:
          type: number
          description: Net margin, computed from the statement matrix.
        return_on_equity:
          type: number
          description: Return on equity. Uses ending balances, not period averages.
        return_on_assets:
          type: number
          description: Return on assets. Uses ending balances, not period averages.
        return_on_invested_capital:
          type: number
          description: Not sourced by the validated route. Always omitted today.
        asset_turnover:
          type: number
          description: Asset turnover. Uses average balances.
        inventory_turnover:
          type: number
          description: Inventory turnover. Uses average balances.
        receivables_turnover:
          type: number
          description: Receivables turnover. Uses average balances.
        days_sales_outstanding:
          type: number
          description: Days sales outstanding.
        operating_cycle:
          type: number
          description: Operating cycle, in days.
        working_capital_turnover:
          type: number
          description: Working capital turnover.
        current_ratio:
          type: number
          description: Current ratio.
        quick_ratio:
          type: number
          description: Quick ratio.
        cash_ratio:
          type: number
          description: Cash ratio.
        operating_cash_flow_ratio:
          type: number
          description: Operating cash flow ratio.
        debt_to_equity:
          type: number
          description: Debt-to-equity ratio.
        debt_to_assets:
          type: number
          description: Debt-to-assets ratio.
        interest_coverage:
          type: number
          description: >-
            Interest coverage ratio. Omitted when interest expense is not
            itemized.
        revenue_growth:
          type: number
          description: Year-over-year revenue growth.
        earnings_growth:
          type: number
          description: Year-over-year earnings growth.
        book_value_growth:
          type: number
          description: Year-over-year book value growth.
        earnings_per_share_growth:
          type: number
          description: Year-over-year EPS growth.
        free_cash_flow_growth:
          type: number
          description: Year-over-year free cash flow growth.
        operating_income_growth:
          type: number
          description: Year-over-year operating income growth.
        ebitda_growth:
          type: number
          description: Year-over-year EBITDA growth.
        payout_ratio:
          type: number
          description: Dividend payout ratio.
        earnings_per_share:
          type: number
          description: Basic earnings per share.
        book_value_per_share:
          type: number
          description: Book value per share.
        free_cash_flow_per_share:
          type: number
          description: Free cash flow per share.
    ErrorResponse:
      type: object
      title: ErrorResponse
      description: >-
        Uniform error/soft-failure body: {"error": <code>, "message": <detail>}.
        Used both for real HTTP error statuses and for 200 OK
        "not_found"/"not_implemented" soft failures that this API never bills
        for.
      properties:
        error:
          type: string
          description: >-
            Machine-readable error code, e.g. bad_request, unauthorized,
            payment_required, rate_limited, upstream_error,
            upstream_schema_changed, upstream_timeout, unsupported,
            invalid_cursor, not_found, not_implemented.
        message:
          type: string
          description: Human-readable detail.
      required:
        - error
        - message
  responses:
    BadRequest:
      description: >-
        400 Bad Request. Validation failed before any paid Monid call was made
        (bad_request), or the request is well-formed but names a capability this
        server deliberately rejects (unsupported), e.g. as_reported=true or a
        non-USD currency.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            bad_request:
              summary: Validation failure
              value:
                error: bad_request
                message: ticker must be 1-20 letters, digits, dots, or hyphens
            unsupported:
              summary: Deliberately rejected capability
              value:
                error: unsupported
                message: >-
                  as_reported is not supported by the Monid-backed server;
                  as_reported=True cannot be answered honestly.
            invalid_cursor:
              summary: Malformed pagination cursor
              value:
                error: invalid_cursor
                message: cursor is not a valid opaque pagination token
    Unauthorized:
      description: >-
        401 Unauthorized. The X-API-KEY header was missing, empty, or contained
        control characters, or (if the operator configured an allowlist) the key
        is not on it. This check runs before any Monid call.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            unauthorized:
              value:
                error: unauthorized
                message: Missing or invalid API key.
    PaymentRequired:
      description: >-
        402 Payment Required. The caller's own Monid wallet could not cover the
        call (Monid returned a blocked/insufficient-funds result).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            payment_required:
              value:
                error: payment_required
                message: 'Monid run blocked: insufficient wallet balance.'
    TooManyRequests:
      description: >-
        429 Too Many Requests. Per-API-key token-bucket rate limit exceeded
        (default 60/min per key; 10/min for keyless demo traffic, where
        enabled).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            rate_limited:
              value:
                error: rate_limited
                message: Rate limit exceeded. Retry shortly.
    BadGateway:
      description: >-
        502 Bad Gateway. The upstream Monid provider call failed for a reason
        other than auth/funds/timeout (upstream_error), or its payload no longer
        matches the shape this server parses (upstream_schema_changed).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            upstream_error:
              summary: Generic upstream failure
              value:
                error: upstream_error
                message: provider returned HTTP 500
            upstream_schema_changed:
              summary: Upstream payload shape drifted
              value:
                error: upstream_schema_changed
                message: provider payload is not valid JSON
    GatewayTimeout:
      description: 504 Gateway Timeout. The upstream Monid provider call timed out.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            upstream_timeout:
              value:
                error: upstream_timeout
                message: provider request timed out
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: >-
        The caller's own Monid API key (get one at
        https://monid.ai?fpr=dhruv-15136b). Passed straight through to Monid on
        every call, so usage bills the caller's own wallet — this is never a
        shared server-side credential, and the server never logs or stores it.
        Missing, empty, or malformed values answer 401 unauthorized before any
        paid call is attempted.

````