---
title: "Year-over-year headline comparison"
method: GET
path: "/internal/api/clients/{client_identifier}/year-over-year"
tags: ["CLIENT"]
---

# Year-over-year headline comparison

`GET /internal/api/clients/{client_identifier}/year-over-year`

## Request

- Base URL: `https://api.signalshq.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.signalshq.io/internal/api/clients/{client_identifier}/year-over-year`
- Auth: OAuth 2
  - Password
  - Token URL: `/auth/login`

This year against the prior year, line by line, in the 1040's own order. The same digest the assistant narrates, so the two can never disagree.

## Path parameters

- `client_identifier` string, required

## Query parameters

- `tax_year` integer, required: The engagement year being prepared

## Response `200`

Successful Response

- YoyDigestResponse: The comparison table, in the 1040's own order.
  - `prior_year_available` boolean
  - `current_year` integer
  - `prior_year` integer: The year actually compared against — not always `current_year - 1`. A client whose most recent return is 2022 is compared against 2022, and the table must say so.
  - `headlines` YoyHeadlineRow[]
    - `label` string, required
    - `line_key` string, required
    - `prior_value` string, nullable
    - `current_value` string, nullable
    - `delta` string, nullable
    - `pct_change` number, nullable: A FRACTION, not a percentage: 0.079 is +7.9%.
    - `direction` string: up | down | flat | new | removed | prior_not_read | prior_inputs_not_read | prior_not_computable. The last three each name a DIFFERENT reason the comparison could not be made, and none of them may collapse into `new` — `new` asserts this year introduced the figure, which is a claim about the client's return. * `prior_not_read` — the extractor never found the figure. * `prior_inputs_not_read` — a DERIVED row whose inputs were not read. The row was never a line on the return, so 'we did not find it there' describes a search nobody could have made; `derived_missing_inputs` names the figures that actually were not read. * `prior_not_computable` — a derived row whose inputs WERE read and which still cannot be worked out, because a zero or negative prior AGI makes the ratio meaningless. Telling a preparer we did not read a figure whose inputs we can cite pages for sends them to check a page that would show them exactly what we already have.
    - `prior_present` boolean: Whether last year's figure was actually read. False means the row shows no delta because we do not have the number, not because nothing changed.
    - `current_present` boolean
    - `prior_source_page` integer, nullable: The page of the filed return the prior figure was read from. Present only for an as-filed basis; an engine-computed prior has no page and must not pretend to.
    - `low_confidence` boolean: The prior value came from an extraction flagged for review. The row should hedge rather than assert.
    - `derived` boolean: This row is CALCULATED from other rows rather than transcribed from a line on the return. It changes what an absence means: telling a preparer we did not find the effective rate on the return is a false statement about a figure that was never a line on it to find. Sourced from DERIVED_CONCEPTS so a client selects on data rather than string-matching a line key.
    - `derived_missing_inputs` string[]: For a derived row that could not be compared: the human names of the inputs that were required and not read, so the explanation names the missing figure rather than gesturing at the row.
    - `prior_value_source` string: `extracted` | `corrected` — where THIS row's prior figure came from: the machine, or a person. `prior_source` on the digest cannot answer this. It describes how the EXTRACTION was performed and is one value for the whole table; provenance after a correction is per row, and a preparer deciding whether to check a figure against the paper needs to know which of the two this one is.
    - `prior_override` FigureOverrideOut: One correction to the firm's read of a prior-year return. Not the return, which is a historical fact nobody may change, and not the extraction, which stays on record as what the machine saw. This is the third thing: a workpaper assertion, attributed and reversible.
      - `override_id` string, uuid, required
      - `line_key` string, required
      - `value_text` string, required: The corrected figure, as TEXT. Money never becomes a float here for the same reason it does not in the basis: these numbers exist to agree with a filed return line by line.
      - `extracted_value_text` string, nullable: What the MACHINE read, kept beside the correction for ever and carried unchanged across a re-correction. None where it read nothing at all — a figure supplied for a line the extractor never found is an ORIGINAL entry, not a correction of one, and a reviewer in year three needs to tell those apart.
      - `reason` string, nullable: Why, from a closed list. Required only where the extraction was CONFIDENT and still wrong; a line already hedged, or never read, needs none — the run has already said it was unsure.
      - `observed_source_page` integer, nullable
      - `corrected_by` string, required
      - `corrected_at` string, date-time, required
      - `superseded_at` string, date-time, nullable: This correction was replaced by a later one on the same line. Null on a live correction. Present only on the history read. Superseded rows stay on the record because 'somebody corrected this twice, and here is what they said the first time' is what a reviewer asks in year three, and a live-only view cannot answer it.
      - `revoked_at` string, date-time, nullable: This correction was withdrawn and the line fell back to what the extraction read. The row is kept rather than deleted: that it was made and then undone is part of the account.
      - `revoked_by` string, nullable: WHO withdrew it, which is not always who made it. The history drawer pairs a name with the timestamp of the LATEST event on the row, so without this it printed the original corrector beside the moment somebody else withdrew their correction — a change attributed to the wrong person on the one screen that exists to attribute changes. Recorded on the row since the withdrawal statement was written; never read back until now.
      - `superseded_by` string, nullable: WHO replaced it. Same reasoning as `revoked_by`. Taken from `updated_by`, and only on a row that was actually superseded — on a live row that column is just the creator, and reporting it as a replacement would invent an event.
    - `prior_extraction_disagrees` string, nullable: A LATER extraction read this line differently from the standing correction. The correction still wins and is what this row shows. Surfaced rather than resolved. Silently taking the machine's newer answer over a preparer's typed one is the failure that ends their use of the feature: they type six figures at 11pm, a re-run lands on Tuesday, and the work is gone with nothing on screen to say so.
  - `summary` string
  - `prior_source` string, nullable: Where the prior figures came from, and HOW: `as_filed:TEXT_LAYER` is a read of the return's text layer, `as_filed:SCRIBE` a full extraction, `engine_computed` a year we prepared ourselves. The tier is part of it because a preparer decides whether to check a figure against the paper on exactly that.
  - `prior_unavailable_reason` string, nullable: WHY there is no comparison, when there is none. `no_return` — nothing uploaded. `never_read` — a return is on file and its figures were never read, because the run predates headline capture; nothing is wrong with the file. `unreadable` — we read it and could not get the figures off it, and the run recorded a refusal saying which guard fired. The middle one exists because without it the panel diagnoses an old-flow return as a bad scan and tells a preparer to replace a file that is perfectly good.
  - `current_computed_at` string, date-time, nullable: When this year's figures were last calculated by the engine — the newest `calculated_at` across the headline lines. The prior column already says where it came from; without this the current column says nothing at all, and a preparer cannot tell a figure computed a minute ago from one computed before the last three questionnaire answers.
  - `current_stale` boolean: At least one headline line is flagged dirty: the engine's answer no longer reflects the inputs. Dirtiness is tracked PER LINE, so this is an any() over the headline set rather than a property of the return — a return can be clean everywhere the table does not look.
  - `compared_count` integer: How many headline lines were actually compared against the prior year. With `not_compared_count` this is an exhaustive partition of `headlines` — the two always sum to its length, asserted at the source. Defined as a partition rather than a filter because a filter silently drops a direction added later, and the symptom is not an exception but a printed count whose numbers stop accounting for each other on a document somebody has signed.
  - `not_compared_count` integer: How many could not be compared, for any of the three reasons the direction vocabulary distinguishes.
  - `stale_line_keys` string[]: Which headline concepts are dirty, so the table can mark the affected rows rather than hedging the whole comparison. A banner over thirteen rows when one moved teaches a preparer to ignore it.
  - `corrected_count` integer: How many prior figures a person has corrected. Counted from the overrides actually applied to THIS digest, never from what a client happens to be holding — a second preparer's correction has to show up here for the first preparer on the next read.
  - `correction_consequences` CorrectionConsequenceOut[]: What a correction invalidated downstream and we did NOT fix. Most prior figures are compared and feed nothing. A few are inputs to the year being prepared — prior AGI sets the §6654 safe harbour and signs the e-file; capital-loss, NOL and AMT-credit carryforwards rest on prior figures. Correcting one of those leaves something downstream we have not touched. Named rather than applied, deliberately. Writing through would overwrite a figure a client supplied or a preparer has already reasoned about; staying quiet would leave a confident screen sitting on a stale carryforward. Naming it is the only move honest to both — and it is empty for the rows that merely compare, because hedging every correction teaches a preparer to stop reading the warning.
    - `line_key` string, required: The corrected line that caused it.
    - `label` string, required: What is now out of date, in the preparer's language.
    - `detail` string, required: What specifically is wrong, and why.
    - `question_code` string, nullable: The questionnaire code to open, when the consequence has one. None where the consequence is real but has no single field — the e-file signature AGI is not a questionnaire answer.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/signalshq/apis/signals-api.md) - [All operations](https://skmtc.dev/signalshq/apis/signals-api/llms.txt) - [OpenAPI document](https://skmtc.dev/signalshq/apis/signals-api/revisions/2c22af3b5576?raw)
