---
title: "One card of the report (Officina, Economico)"
method: GET
path: "/v1/reports/{widget}"
tags: ["Reports"]
---

# One card of the report (Officina, Economico)

`GET /v1/reports/{widget}`

## Request

- Base URL: `https://api.officina.it`
- URL: `https://api.officina.it/v1/reports/{widget}`
- Auth: HTTP bearer

Returns one card of Report v2 — the data the web Report page renders
on its Officina and Economico tabs, computed by the same code. One
request per card.

Period: `preset` or `from`/`to` (no params = last 12 months; on the
mobile app the sticky `report` filter shared with the web). `grain`
picks the chart buckets (default weeks up to 62 days, months above).

A card with nothing to show for the company or the period answers
`visible: false` and `data: null` — hide it. Amounts are euro, IVA
excluded unless the field says otherwise; a `null` figure is not
meaningful (no previous period, below the minimum count, permission
missing). Percentages are 0–100.

Permissions: cards with amounts also need `prices.read`;
`quote-funnel` needs `quotes.read`, `top-customers-fatturato` also
`customers.read`, the time cards `settings_company.read`; otherwise
403. In `receivables` debtor names need `customers.read`
(`debtors_shown`).

The cards kept from the first report (`worksheet-status`,
`top-services`, `worksheet-margin-chart`, `worksheet-margins`,
`sales-chart`, `outstanding-worksheets`) keep their
`/v1/report/{widget}` data shapes.

## Query parameters

- `preset` 'rel30' | 'rel90' | 'rel365' | 'year' | 'Q1' | 'Q2' | 'Q3' | 'Q4' | 'month' | 'custom' | 'all'
- `from` string, date
- `to` string, date
- `grain` 'week' | 'month'
- `limit` integer
- `metric` 'eur' | 'pct'
- `direction` 'best' | 'worst'

## Response `200`

The card (visible or hidden).

- ReportV2Envelope: One report card. `visible: false` (with `data: null`) is the hidden state: nothing to show for this company or period. `data` depends on `widget`: see the `ReportV2*` schemas (cards kept from the first report use the `/v1/report/{widget}` shapes; `calendar-heatmap` is `ReportV2CalendarHeatmap`).
  - `object` 'report_widget', required
  - `widget` string, required
  - `tab` 'officina' | 'economico', required
  - `preset` string, required: The applied date filter preset.
  - `from` string, date, required
  - `to` string, date, required
  - `grain` 'week' | 'month', required
  - `visible` boolean, required
  - `data` union, required: The card's data, or null when hidden.
    - ReportV2OfficinaKpis: `officina-kpis`. `lavorato` null without prices.read, `quotes` null without quotes.read.
      - `lavorato` object, nullable: Non-draft worksheets by worksheet date, IVA excluded. Not the fatturato.
        - `total` number
        - `labour` number
        - `parts` number
        - `previous_total` number
        - `delta_percent` number, nullable: Change against the previous value, percent; null when the previous value is not positive.
      - `closed_worksheets` object: Worksheets completed or delivered in the period, by close date.
        - `count` integer
        - `previous_count` integer
        - `delta_percent` number, nullable: Change against the previous value, percent; null when the previous value is not positive.
        - `open_now` integer
      - `quotes` object, nullable: Quotes created in the period. The rate needs 10 quotes, the change in points 10 in the previous period too.
        - `created` integer
        - `accepted` integer
        - `previous_created` integer
        - `previous_accepted` integer
        - `acceptance_rate` number, nullable
        - `rate_delta_points` number, nullable
      - `customers` object
        - `served` integer
        - `new` integer: First worksheet ever in the period.
    - ReportV2LavoratoChart: `lavorato-chart`. `buckets_with_work` 0 = empty period, 1 = no comparison yet.
      - `grain` 'week' | 'month'
      - `buckets_with_work` integer
      - `buckets` object[]
        - `start` string, date
        - `labour` number
        - `parts` number
    - ReportV2Categories: `categories`: the 5 biggest work categories, the rest in `other_count`. A worksheet with several categories counts in each; `categorised` counts it once.
      - `categories` object[]
        - `name` string
        - `count` integer
      - `other_count` integer
      - `categorised` integer
      - `uncategorised` integer
    - ReportV2QuoteFunnel: `quote-funnel`: quotes created in the period and how far they went. `enough` false below 10 quotes.
      - `enough` boolean
      - `created` integer
      - `delivered` integer
      - `accepted` integer
      - `converted` integer
      - `waiting` integer
    - ReportV2CustomersChart: `customers-chart`: hidden when nobody was served. Returning: already had a worksheet before that bucket.
      - `served` integer
      - `new` integer
      - `returning` integer
      - `returning_percent` number
      - `grain` 'week' | 'month'
      - `buckets` object[]
        - `start` string, date
        - `returning` integer
        - `new` integer
    - ReportV2Duration: `duration`: days from worksheet date to delivery. `count` 0 = nothing delivered yet (a hint, not hidden).
      - `count` integer
      - `median_days` number
      - `buckets` object[]
        - `label` string
        - `count` integer
    - ReportV2CalendarHeatmap: `calendar-heatmap`: appointments per weekday and hour. `days[0]` is Monday; `days[d][0]` is 8:00 … `days[d][11]` 19:00.
      - `days` array[]
        - integer[]
    - ReportV2TimeKpis: `time-kpis` (marcatempo; hidden unless the company tracks time on a plan that includes it). `state`: never_tracked, no_closed, low (under 10 closed worksheets with time: KPIs only) or full.
      - `state` 'never_tracked' | 'no_closed' | 'low' | 'full'
      - `worksheets_with_time` integer
      - `worked_minutes` number
      - `billed_minutes` number
      - `flat_lines` integer: "A corpo" labour lines, not measurable.
      - `resa_percent` number, nullable: Billed ÷ worked.
      - `resa_tone` 'good' | 'warn' | 'danger'
      - `revenue_per_worked_hour` number, nullable: Null without prices.read.
    - ReportV2TimeWeeks: `time-weeks`: hours per week by close date (state full only).
      - `weeks` object[]
        - `start` string, date
        - `worked_hours` number
        - `billed_hours` number
    - ReportV2TimePeople: `time-people`: each person's share of the worked hours; hidden with fewer than two people.
      - `people` object[]
        - `name` string
        - `worksheets` integer
        - `worked_minutes` number
        - `share_percent` number
    - ReportV2TimeCats: `time-cats`: resa per work category; `resa_percent` null when billed only "a corpo".
      - `categories` object[]
        - `name` string
        - `worksheets` integer
        - `worked_minutes` number
        - `billed_minutes` number
        - `avg_worked_minutes` number
        - `measurable` boolean
        - `resa_percent` number, nullable
        - `resa_tone` 'good' | 'warn' | 'danger'
    - ReportV2TimeCheck: `time-check`: worksheets with resa under 85%, biggest gap first.
      - `worksheets` object[]
        - `id` string, uuid
        - `number` string: YYYY-NNNN
        - `plate` string
        - `category` string
        - `worked_minutes` number
        - `billed_minutes` number
    - ReportV2EconomicoKpis: `economico-kpis`. Without `ever_issued` the fatturato is "—", not 0. `incassato` is IVA included, every payment of the period (also on uninvoiced worksheets); compared with a year before. `margine.state`: expenses_disabled, not_enough_costs (costs in under 2/3 of the months, or nothing issued) or shown. `receivables`: open invoices today, IVA included, not following the period.
      - `ever_issued` boolean
      - `fatturato` object
        - `total` number
        - `previous_total` number
        - `delta_percent` number, nullable: Change against the previous value, percent; null when the previous value is not positive.
        - `documents` integer
      - `incassato` object
        - `total` number
        - `payments` integer
        - `last_year_total` number
        - `last_year_payments` integer
        - `delta_percent` number, nullable: Change against the previous value, percent; null when the previous value is not positive.
      - `margine` object
        - `state` 'expenses_disabled' | 'not_enough_costs' | 'shown'
        - `value` number, nullable
        - `percent_of_fatturato` number, nullable
      - `receivables` object
        - `total` number
        - `invoices` integer
        - `overdue` number
    - ReportV2FatturatoChart: `fatturato-chart`: hidden without any issued document ever. `spese`/`margine` null unless `with_costs`.
      - `grain` 'week' | 'month'
      - `with_costs` boolean
      - `expenses_enabled` boolean
      - `any_documents` boolean
      - `buckets` object[]
        - `start` string, date
        - `fatturato` number
        - `spese` number, nullable
        - `margine` number, nullable
    - ReportV2ToInvoice: `to-invoice`: completed or delivered worksheets of the period without an issued invoice or receipt, IVA excluded.
      - `count` integer
      - `total` number
    - ReportV2FatturatoSources: `fatturato-sources`: by document type (credit notes negative) and, with vehicle sales or rentals, by source.
      - `invoices` number
      - `receipts` number
      - `credit_notes` number
      - `by_source` object, nullable
        - `worksheets` number
        - `vehicle_sales` number
        - `rentals` number
    - ReportV2IncassatoChart: `incassato-chart`: payments by payment date, IVA included, against a year before. Hidden without any payment ever; `total` 0 = empty period.
      - `grain` 'week' | 'month'
      - `total` number
      - `last_year_total` number
      - `buckets` object[]
        - `start` string, date
        - `current` number
        - `last_year` number
    - ReportV2PaymentMethods: `payment-methods`: the 4 biggest groups, the rest as `other`. Hidden when nothing was cashed.
      - `total` number
      - `methods` object[]
        - `key` 'cash' | 'bank' | 'card' | 'cheque' | 'riba' | 'unspecified' | 'other'
        - `label` string
        - `amount` number
        - `share_percent` number
    - ReportV2Receivables: `receivables`: open invoices today by days past due (the first bucket is not yet due). Does not follow the period. Debtors only with customers.read (`debtors_shown`).
      - `total` number
      - `overdue` number
      - `invoices` integer
      - `aging` object[]
        - `label` string
        - `invoices` integer
        - `amount` number
      - `debtors_shown` boolean
      - `debtors` object[]
        - `customer_id` string, uuid
        - `name` string
        - `invoices` integer
        - `amount` number
        - `oldest_days` integer
    - ReportV2ExpenseCategories: `expense-categories`: hidden when the expenses module is off; the 8 biggest categories, `total` over all. Empty list = no expenses yet.
      - `total` number
      - `categories` object[]
        - `name` string
        - `expenses` integer
        - `amount` number
    - ReportV2Composition: `composition`: what the fatturato is made of; parts with no amount left out.
      - `total` number
      - `parts` object[]
        - `key` 'labour' | 'parts' | 'vehicles' | 'receipts_only'
        - `label` string
        - `amount` number
        - `percent` number
    - ReportV2TopCustomers: `top-customers-fatturato`: the 6 biggest customers by fatturato imponibile.
      - `customers` object[]
        - `id` string, uuid
        - `name` string
        - `documents` integer
        - `amount` number
    - ReportWorksheetStatus: Data of widget `worksheet-status`.
      - `draft` integer, required
      - `waiting` integer, required
      - `started` integer, required
      - `completed` integer, required
      - `delivered` integer, required
      - `completion_rate` number, required
    - ReportRankedList: Data of the ranked-list widgets (`top-customers`, `top-services`, `worksheet-margins`). `amount` carries the sort metric; `bar_width` is server-normalized 0-100. The margins widget also fills `sublabel` (document number) and `percent`.
      - `rows` object[], required
        - `id` string, required
        - `label` string, required
        - `sublabel` string
        - `amount` number, required
        - `percent` number
        - `bar_width` number, required
    - ReportChart: Data of the stacked-chart widgets (`sales-chart`, `expenses-chart`, `worksheet-margin-chart`). `x_axis` entries are YYYY-MM-DD bucket starts (daily ≤31-day periods, else monthly); each series' `values` align with `x_axis`.
      - `x_axis` string[], required
      - `series` object[], required
        - `key` string, required
        - `values` number[], required
    - ReportOutstandingWorksheets: Data of widget `outstanding-worksheets`.
      - `worksheets` object[], required
        - `worksheet_id` string, required
        - `title` string, required
        - `customer_name` string, required
        - `outstanding` number, required
        - `total` number, required
        - `bar_width` number, required: Server-normalized bar length, 0-100.

## Other responses

- `401`: Missing, malformed, expired, or wrong-audience bearer token.
- `402`: The company has no active subscription, or its plan does not include API access. API access is included in the Business and Illimitato plans.
- `403`: The authenticated user lacks the required permission.
- `404`: Resource does not exist OR belongs to a different company. The two cases are intentionally indistinguishable.
- `422`: A request field is missing or invalid (fast-fail).
- `500`: Unhandled server error. Detail is logged, not exposed.

---

[API](https://skmtc.dev/officina/apis/officina-it-api.md) · [All operations](https://skmtc.dev/officina/apis/officina-it-api/llms.txt) · [OpenAPI document](https://skmtc.dev/officina/apis/officina-it-api/revisions/7da3fccfe551?raw)
