---
title: "Old-vs-new text for one change"
method: GET
path: "/api/v1/watches/{watch_id}/changes/{change_id}/diff"
tags: ["Board Watches"]
---

# Old-vs-new text for one change

`GET /api/v1/watches/{watch_id}/changes/{change_id}/diff`

## Request

- Base URL: `https://api.vaquill.ai`
- URL: `https://api.vaquill.ai/api/v1/watches/{watch_id}/changes/{change_id}/diff`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/vaquill/apis/vaquill-developer-api/docs/api/v1/watches/:watch_id/changes/:change_id/diff/get.ts> (a function with typed inputs; its response is checked against the schema)

The section's text before and after this specific change, when available (see `hasDiff` on the change list above).

How many sides a change has depends on its kind, and each is a complete answer:

- `amended` -- both sides. This is the only kind where one side alone is not a partial answer but no answer.
- `added` -- after side only. There is no before side by definition, and the full text of a newly published section is precisely what the notification is for.
- `removed` -- before side only, the last text we held.

A missing side is not an error: `hasBefore`/`hasAfter` say which text is actually present, and `diffAvailable` (with `unavailableReason` when false) says whether there is a diff at all, so a caller can render a graceful 'diff unavailable' state for older changes or a snapshot that failed to capture, instead of treating null as a failure.

**Cost**: 4 credits per call (see `/api-credits/pricing`). This is the only metered route in the alerts family: every other board and watch endpoint, including the `/changes` list this reads from, is free.

You are charged only for a diff that answers the question its kind asks, and `creditsConsumed` always reports what was actually billed. Refunded in full (`creditsConsumed: 0`): an `amended` change missing either side, an `amended` change whose two sides resolve to identical text (a capture artifact on our side, not a change on the publisher's), and any change resolving no text at all. A one-sided `added` or `removed` diff is the complete answer and is charged normally.

## Path parameters

- `watch_id` string, required
- `change_id` integer, required

## Headers

- `Idempotency-Key` string, nullable: Optional. 16 to 255 visible ASCII characters (a UUIDv4 is ideal). A retry with the same key and the same request replays the original 2xx response for 24 hours without charging again. The same key with a different request is rejected. Scope is the API key, method, path and body. The stored response body is a query-derived cache controlled by the account's data retention setting cacheQueryDerivedData: when it is false the body is never stored and a retry answers 409 idempotency_replay_unavailable, without charging again.

## Response `200`

The change's before/after text (either side may be absent).

- ChangeDiffEnvelope
  - `data` ChangeDiffResponse, required: Old-vs-new text for one change, as WHOLE DOCUMENTS. ``beforeText``/``afterText`` are each the complete text of one version of the section, not the single chunk that happened to be recorded as that side's representative. Diff them directly. ``changeKind: "added"`` carries ``afterText`` with no ``beforeText``: there is nothing to compare a newly published document against, and its full text is the substance of the notification.
    - `changeId` integer, required
    - `changeKind` string, required
    - `beforeText` string, nullable
    - `afterText` string, nullable
    - `hasBefore` boolean, required
    - `hasAfter` boolean, required
    - `diffAvailable` boolean, required: True when this response answers the question its `changeKind` asks: both sides for `amended`, the after side for `added`, the before side for `removed`. False means there is NO diff to show (and the call is refunded); `unavailableReason` says why. A side we could not retrieve is never replaced by an invented one.
    - `unavailableReason` 'neither_side_available' | 'before_text_unavailable' | 'after_text_unavailable' | 'sides_identical', nullable: Why `diffAvailable` is false. Null when it is true.
    - `before` DocumentSideResponse: Provenance for one side of a diff. Exists so a reader can tell a complete document from a partially reassembled one. A section is stored as overlapping chunks and has to be welded back together to be diffed; when a chunk is missing, the text is still worth showing but is no longer safe to read as the document in full.
      - `chunkCount` integer, required
      - `expectedChunks` integer, nullable
      - `wordCount` integer, required
      - `expectedWords` integer, nullable
      - `isComplete` boolean, required
      - `versionSha1` string, nullable
      - `reconstructedFromCurrent` boolean
      - `reconstructedChars` integer
    - `after` DocumentSideResponse: Provenance for one side of a diff. Exists so a reader can tell a complete document from a partially reassembled one. A section is stored as overlapping chunks and has to be welded back together to be diffed; when a chunk is missing, the text is still worth showing but is no longer safe to read as the document in full.
      - `chunkCount` integer, required
      - `expectedChunks` integer, nullable
      - `wordCount` integer, required
      - `expectedWords` integer, nullable
      - `isComplete` boolean, required
      - `versionSha1` string, nullable
      - `reconstructedFromCurrent` boolean
      - `reconstructedChars` integer
    - `metadata` object
  - `meta` ResponseMeta, required: The `meta` block every board-watch route returns beside `data`.
    - `processingTimeMs` number, required
    - `creditsConsumed` number, required
    - `cursor` integer, nullable: Change feeds only: the HIGHEST change `id` on the page, null on an empty page. Send it back as `sinceId`.
    - `hasMore` boolean, nullable: Change feeds only: true when the page filled to `limit`.

## Other responses

- `401`: Invalid or missing API key.
- `402`: Insufficient API credits.
- `403`: API key lacks `research:read` scope.
- `404`: Watch not found, or the change is not covered by this watch.
- `409`: Idempotency conflict. `idempotency_in_progress`: the original request with this key is still running (retry after `Retry-After`). `idempotency_replay_unavailable`: it completed but its response was too large to replay; nothing is charged again, so check the credit balance instead of retrying.
- `422`: Validation error: `detail` plus one `errors` entry per rejected field. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different request.
- `429`: Rate limit exceeded.

---

[API](https://skmtc.dev/vaquill/apis/vaquill-developer-api.md) - [All operations](https://skmtc.dev/vaquill/apis/vaquill-developer-api/llms.txt) - [OpenAPI document](https://skmtc.dev/vaquill/apis/vaquill-developer-api/revisions/fbac9b87775e?raw)
