Skip to main content

Unified Diff & Git Patch Fixture API

MOCK DATA

Unified diff fixtures whose hunk headers actually reconcile — correct @@ line counts, shared context lines, and rename, mode and binary forms.

Generated test data for fixtures and development

Base URL
/api/diffs
Capabilities
2 routes Seedable
Last updated
July 29, 2026

GET /api/diffs

Each record is one changed file: its paths and modes, the summed insertions/deletions, the structured hunks, and the rendered patch derived from them.

Live requestRuns against the public API
No key required
GET/api/diffs

Request parameters

Exact number of hunks per modified or renamed file. Added and deleted files always have exactly one whole-file hunk; mode-only and binary changes have none.

Unchanged context lines on each side of every change group, like `git diff -U<n>`. 0 reproduces `-U0` output, where hunks contain only changed lines and empty ranges appear as `-12,0`.

Change type to emit. Any value other than 'mixed' is a hard guarantee — every file in the response has that exact type. 'mixed' weights modifications highest.

Drives plausible file paths, extensions and line content. Omit to vary the language per file. Binary files keep asset paths (.png, .zip…) and carry no line content, so `changeType=binary` ignores this parameter entirely and warns when it is set.

Earliest change date, inclusive. YYYY-MM-DD (= midnight UTC) or a full ISO 8601 timestamp. Defaults to 90 days before today (UTC).

Latest change date, inclusive. Must be ≥ `from`. Defaults to today at UTC midnight; the resolved window is echoed in `meta.params`.

Advanced response options7 options

How many records to generate (1–100).

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Response format: json envelope, ndjson (one record per line) or csv.

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/diffs"
const res = await fetch("https://randomapi.dev/api/diffs");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/diffs").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/diffs"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 6
hunksPerFile int

Exact number of hunks per modified or renamed file. Added and deleted files always have exactly one whole-file hunk; mode-only and binary changes have none.

default: 2
allowed: 1 – 6
example: hunksPerFile=3
contextLines int

Unchanged context lines on each side of every change group, like `git diff -U<n>`. 0 reproduces `-U0` output, where hunks contain only changed lines and empty ranges appear as `-12,0`.

default: 3
allowed: 0 – 5
example: contextLines=0
changeType enum

Change type to emit. Any value other than 'mixed' is a hard guarantee — every file in the response has that exact type. 'mixed' weights modifications highest.

default: mixed
allowed: mixed | modify | add | delete | rename | mode | binary
example: changeType=rename
language enum

Drives plausible file paths, extensions and line content. Omit to vary the language per file. Binary files keep asset paths (.png, .zip…) and carry no line content, so `changeType=binary` ignores this parameter entirely and warns when it is set.

allowed: javascript | typescript | python | go | ruby | java | json | markdown
example: language=typescript
from date

Earliest change date, inclusive. YYYY-MM-DD (= midnight UTC) or a full ISO 8601 timestamp. Defaults to 90 days before today (UTC).

example: from=2026-01-01
to date

Latest change date, inclusive. Must be ≥ `from`. Defaults to today at UTC midnight; the resolved window is echoed in `meta.params`.

example: to=2026-03-31
Universal parameters Shared response and formatting options 7
count int

How many records to generate (1–100).

default: 10
allowed: 1 – 100
example: count=3
seed int

Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).

example: seed=42
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=path,oldPath
exclude list

Return all fields except these (comma-separated).

example: exclude=patch
format enum

Response format: json envelope, ndjson (one record per line) or csv.

default: json
allowed: json | ndjson | csv
example: format=csv
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 10
path string

Post-image path (the `b/` side). For a deletion this is the path that was removed.

example: src/lib/cache.js

oldPath string nullable

Pre-image path (the `a/` side); `null` for an added file. Differs from `path` only for a rename.

example: src/lib/cache.js

changeType string

modify | add | delete | rename | mode | binary.

example: modify

oldMode string nullable

Six-digit octal pre-image mode (100644 regular, 100755 executable); `null` for an added file.

example: 100644

newMode string nullable

Six-digit octal post-image mode; `null` for a deleted file. Differs from `oldMode` only for a mode change.

example: 100644

insertions integer

Added lines, summed from the hunks. Always 0 for mode-only and binary changes.

example: 2

deletions integer

Removed lines, summed from the hunks. Always 0 for mode-only and binary changes.

example: 1

date string (ISO 8601)

Authoring date in UTC, uniformly random within [`from`, `to`] — whole seconds, like real git timestamps, dropping to millisecond precision only when the window is narrower than one second and holds no whole-second instant. Metadata only: git's own patch output carries no timestamps, so this is not part of `patch`.

example: 2026-03-14T09:26:53Z

hunks object[]

Structured hunks: `header`, `oldStart`, `oldLines`, `newStart`, `newLines` and `lines` (each `{ type: context|add|remove, text }`, plus `noNewline: true` on a line whose file version has no trailing newline). `oldLines` always equals the context+removed line count and `newLines` the context+added count. Empty for mode-only and binary changes.

example: [{"header":"@@ -12,2 +12,3 @@","oldStart":12,"oldLines":2,"newStart":12,"newLines":3,"lines":[{"type":"context","text":" const cache = new Map();"},{"type":"remove","text":" return cache.get(key);"},{"type":"add","text":" const hit = cache.get(key);"},{"type":"add","text":" return hit ?? null;"}]}]

patch string

The complete git-style patch for this file, rendered from `hunks` (never authored separately) and terminated with a newline.

example: diff --git a/src/lib/cache.js b/src/lib/cache.js index 4b1e2c7..9af0d31 100644 --- a/src/lib/cache.js +++ b/src/lib/cache.js @@ -12,2 +12,3 @@ const cache = new Map(); - return cache.get(key); + const hit = cache.get(key); + return hit ?? null;

Documented examples

Build-generated requests and complete responses
5
Ten single-file diffs
GET /api/diffs
{
  "data": [
    {
      "path": "src/test/java/com/example/app/UserClient.java",
      "oldPath": "src/test/java/com/example/app/UserClient.java",
      "changeType": "modify",
      "oldMode": "100644",
      "newMode": "100644",
      "insertions": 3,
      "deletions": 2,
      "date": "2026-07-16T12:30:10Z",
      "hunks": [
        {
          "header": "@@ -12,6 +12,7 @@",
          "oldStart": 12,
          "oldLines": 6,
          "newStart": 12,
          "newLines": 7,
          "lines": [
            {
              "type": "context",
              "text": "package com.example.app;"
            },
            {
              "type": "context",
              "text": ""
            },
            {
              "type": "context",
              "text": "import java.util.List;"
            },
            {
              "type": "add",
              "text": "import java.util.Objects;"
            },
            {
              "type": "context",
              "text": "import java.util.Optional;"
            },
            {
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Reproducible TypeScript modifications
GET /api/diffs?seed=42&changeType=modify&language=typescript&count=5
{
  "data": [
    {
      "path": "src/api/router-service.ts",
      "oldPath": "src/api/router-service.ts",
      "changeType": "modify",
      "oldMode": "100755",
      "newMode": "100755",
      "insertions": 3,
      "deletions": 0,
      "date": "2026-07-11T23:58:46Z",
      "hunks": [
        {
          "header": "@@ -14,6 +14,7 @@",
          "oldStart": 14,
          "oldLines": 6,
          "newStart": 14,
          "newLines": 7,
          "lines": [
            {
              "type": "context",
              "text": "import { z } from 'zod';"
            },
            {
              "type": "context",
              "text": ""
            },
            {
              "type": "context",
              "text": "export interface ClientOptions {"
            },
            {
              "type": "add",
              "text": "    const cached = cache.get(url.href);"
            },
            {
              "type": "context",
              "text": "  readonly baseUrl: string;"
            },
            {
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Zero-context hunks for parser tests
GET /api/diffs?contextLines=0&hunksPerFile=3&count=4&seed=7
{
  "data": [
    {
      "path": "test/fixtures/settings.json",
      "oldPath": null,
      "changeType": "add",
      "oldMode": null,
      "newMode": "100644",
      "insertions": 5,
      "deletions": 0,
      "date": "2026-06-05T17:46:03Z",
      "hunks": [
        {
          "header": "@@ -0,0 +1,5 @@",
          "oldStart": 0,
          "oldLines": 0,
          "newStart": 1,
          "newLines": 5,
          "lines": [
            {
              "type": "add",
              "text": "  \"dependencies\": {"
            },
            {
              "type": "add",
              "text": "    \"zod\": \"^3.23.8\""
            },
            {
              "type": "add",
              "text": "  },"
            },
            {
              "type": "add",
              "text": "  \"devDependencies\": {"
            },
            {
              "type": "add",
              "text": "    \"typescript\": \"^5.5.4\","
            }
          ]
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Renamed files only
GET /api/diffs?changeType=rename&count=3&seed=11
{
  "data": [
    {
      "path": "src/components/parser.js",
      "oldPath": "lib/queue-worker.js",
      "changeType": "rename",
      "oldMode": "100644",
      "newMode": "100644",
      "insertions": 3,
      "deletions": 2,
      "date": "2026-07-15T20:47:58Z",
      "hunks": [
        {
          "header": "@@ -17,8 +17,7 @@",
          "oldStart": 17,
          "oldLines": 8,
          "newStart": 17,
          "newLines": 7,
          "lines": [
            {
              "type": "context",
              "text": "module.exports = { createClient };"
            },
            {
              "type": "context",
              "text": "'use strict';"
            },
            {
              "type": "context",
              "text": ""
            },
            {
              "type": "remove",
              "text": "const { readFile } = require('fs/promises');"
            },
            {
              "type": "remove",
              "text": "const { URL } = require('url');"
            },
            {
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Just the rendered patches
GET /api/diffs?fields=patch&format=ndjson&count=5
{"patch":"diff --git a/src/test/java/com/example/app/UserClient.java b/src/test/java/com/example/app/UserClient.java\nindex 8ea741c..cd24a06 100644\n--- a/src/test/java/com/example/app/UserClient.java\n+++ b/src/test/java/com/example/app/UserClient.java\n@@ -12,6 +12,7 @@\n package com.example.app;\n \n import java.util.List;\n+import java.util.Objects;\n import java.util.Optional;\n \n public final class OrderService {\n@@ -25,8 +26,8 @@\n   public Optional<Order> findById(long id) {\n     return repository.findById(id);\n   }\n-\n-  public long total(List<Item> items) {\n+    if (items.isEmpty()) {\n+      return 0L;\n     long total = 0;\n     for (Item item : items) {\n       total += item.quantity() * item.price();\n"}
{"patch":"diff --git a/lib/client-store.js b/lib/client-store.js\nindex f70c9b4..93a911a 100644\n--- a/lib/client-store.js\n+++ b/lib/client-store.js\n@@ -16,6 +16,8 @@\n \n const { readFile } = require('fs/promises');\n const { URL } = require('url');\n+      attempt += 1;\n+    const cached = cache.get(url.href);\n \n const DEFAULT_TIMEOUT = 5000;\n const cache = new Map();\n@@ -28,5 +30,5 @@\n     const url = new URL(path, baseUrl);\n     const response = await fetch(url, { timeout });\n     if (!response.ok) {\n-      throw new Error('request failed');\n-    }\n\\ No newline at end of file\n+    cache.set(url.href, payload);\n+  function clear() { cache.clear(); }\n\\ No newline at end of file\n"}
{"patch":"diff --git a/src/client.js b/src/components/cache.js\nsimilarity index 56%\nrename from src/client.js\nrename to src/components/cache.js\nindex b436a5c..931543d 100755\n--- a/src/client.js\n+++ b/src/components/cache.js\n@@ -11,7 +11,8 @@\n     return response.json();\n   }\n \n-  return { request };\n+    cache.set(url.href, payload);\n+  function clear() { cache.clear(); }\n }\n \n module.exports = { createClient };\n@@ -26,6 +27,8 @@\n function createClient(options = {}) {\n   const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n   const baseUrl = options.baseUrl;\n+      attempt += 1;\n+    const cached = cache.get(url.href);\n \n   async function request(path) {\n     const url = new URL(path, baseUrl);\n"}
{"patch":"diff --git a/app/jobs/report.rb b/app/jobs/report.rb\nindex 68ff2bd..99f958d 100644\n--- a/app/jobs/report.rb\n+++ b/app/jobs/report.rb\n@@ -17,11 +17,7 @@\n       JSON.parse(response.body)\n     end\n \n-    private\n-\n     def get(path)\n-      Net::HTTP.get_response(URI(@base_url + path))\n-    end\n   end\n end\n # frozen_string_literal: true\n@@ -34,8 +30,7 @@\n     def initialize(base_url:, timeout: 5)\n       @base_url = base_url\n       @timeout = timeout\n-    end\n-\n+      @retries = retries\n     def fetch(path)\n       raise ArgumentError, 'path' if path.nil?\n \n"}
{"patch":"diff --git a/src/test/java/com/example/app/SessionTest.java b/src/main/java/com/example/app/Report.java\nsimilarity index 68%\nrename from src/test/java/com/example/app/SessionTest.java\nrename to src/main/java/com/example/app/Report.java\nindex 12d9827..3ff0d8b 100644\n--- a/src/test/java/com/example/app/SessionTest.java\n+++ b/src/main/java/com/example/app/Report.java\n@@ -1,4 +1,5 @@\n     this.repository = repository;\n+  @Override\n   }\n \n   public Optional<Order> findById(long id) {\n@@ -10,5 +11,4 @@\n     for (Item item : items) {\n       total += item.quantity() * item.price();\n     }\n-    return total;\n-  }\n+      return 0L;\n\\ No newline at end of file\n"}

GET /api/diffs/patch

Returns a single patch string covering files files in ascending path order, plus a per-file summary whose insertions, deletions and hunk counts sum exactly to the totals.

Live requestRuns against the public API
No key required
GET/api/diffs/patch

Request parameters

Number of files in the patch. Paths are unique within one patch and sorted ascending, like `git diff`.

Exact number of hunks per modified or renamed file. Added and deleted files always have exactly one whole-file hunk; mode-only and binary changes have none.

Unchanged context lines on each side of every change group, like `git diff -U<n>`. 0 reproduces `-U0` output, where hunks contain only changed lines and empty ranges appear as `-12,0`.

Change type to emit. Any value other than 'mixed' is a hard guarantee — every file in the response has that exact type. 'mixed' weights modifications highest.

Drives plausible file paths, extensions and line content. Omit to vary the language per file. Binary files keep asset paths (.png, .zip…) and carry no line content, so `changeType=binary` ignores this parameter entirely and warns when it is set.

Determinism: the same seed and parameters always render the byte-identical patch.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/diffs/patch"
const res = await fetch("https://randomapi.dev/api/diffs/patch");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/diffs/patch").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/diffs/patch"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 6
files int

Number of files in the patch. Paths are unique within one patch and sorted ascending, like `git diff`.

default: 3
allowed: 1 – 10
example: files=5
hunksPerFile int

Exact number of hunks per modified or renamed file. Added and deleted files always have exactly one whole-file hunk; mode-only and binary changes have none.

default: 2
allowed: 1 – 6
example: hunksPerFile=3
contextLines int

Unchanged context lines on each side of every change group, like `git diff -U<n>`. 0 reproduces `-U0` output, where hunks contain only changed lines and empty ranges appear as `-12,0`.

default: 3
allowed: 0 – 5
example: contextLines=0
changeType enum

Change type to emit. Any value other than 'mixed' is a hard guarantee — every file in the response has that exact type. 'mixed' weights modifications highest.

default: mixed
allowed: mixed | modify | add | delete | rename | mode | binary
example: changeType=rename
language enum

Drives plausible file paths, extensions and line content. Omit to vary the language per file. Binary files keep asset paths (.png, .zip…) and carry no line content, so `changeType=binary` ignores this parameter entirely and warns when it is set.

allowed: javascript | typescript | python | go | ruby | java | json | markdown
example: language=typescript
seed int

Determinism: the same seed and parameters always render the byte-identical patch.

default: 1
allowed: 0 – 4294967295
example: seed=99
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=patch,files
exclude list

Return all fields except these (comma-separated).

example: exclude=summary
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 6
patch string

The complete multi-file unified patch, files concatenated in ascending path order and terminated with a newline.

example: diff --git a/src/lib/cache.js b/src/lib/cache.js index 4b1e2c7..9af0d31 100644 --- a/src/lib/cache.js +++ b/src/lib/cache.js @@ -12,2 +12,3 @@ const cache = new Map(); - return cache.get(key); + const hit = cache.get(key); + return hit ?? null;

files integer

Number of files in the patch.

example: 3

hunks integer

Total hunk count across every file in the patch.

example: 4

insertions integer

Total added lines — the sum of `summary[].insertions`.

example: 9

deletions integer

Total removed lines — the sum of `summary[].deletions`.

example: 5

summary object[]

Per-file entries in patch order: `path`, `oldPath`, `changeType`, `insertions`, `deletions` and `hunks`.

example: [{"path":"src/lib/cache.js","oldPath":"src/lib/cache.js","changeType":"modify","insertions":5,"deletions":3,"hunks":2},{"path":"src/lib/queue.js","oldPath":null,"changeType":"add","insertions":4,"deletions":0,"hunks":1}]

Documented examples

Build-generated requests and complete responses
4
Three-file patch
GET /api/diffs/patch
{
  "data": {
    "patch": "diff --git a/docs/reference/getting-started-overview.md b/docs/reference/getting-started-overview.md\nindex dce7d29..cb2c8e4 100644\n--- a/docs/reference/getting-started-overview.md\n+++ b/docs/reference/getting-started-overview.md\n@@ -4,8 +4,9 @@\n npm install @example/app\n ```\n \n+## Troubleshooting\n ## Configuration\n-\n+4. Restart the development server.\n - `baseUrl` — the API origin to call.\n - `timeoutMs` — request timeout in milliseconds.\n \n@@ -20,5 +21,5 @@\n \n A small client for the Example API.\n \n-## Installation\n-\n+> Warning: rotate tokens every 90 days.\n+```js\n\\ No newline at end of file\ndiff --git a/fixtures/package.json b/fixtures/package.json\nindex 93520ce..60ba424 100644\n--- a/fixtures/package.json\n+++ b/fixtures/package.json\n@@ -5,7 +5,8 @@\n   \"private\": true,\n   \"type\": \"module\",\n   \"scripts\": {\n-    \"build\": \"tsc --build\",\n+  \"license\": \"MIT\",\n+  \"description\": \"Example API client\",\n     \"lint\": \"eslint .\",\n     \"test\": \"vitest run\"\n   },\n@@ -19,7 +20,7 @@\n }\n {\n   \"name\": \"@example/app\",\n-  \"version\": \"1.4.0\",\n+  \"description\": \"Example API client\",\n   \"private\": true,\n   \"type\": \"module\",\n   \"scripts\": {\ndiff --git a/internal/http/client-registry.go b/internal/http/client-registry.go\nindex b4f35e4..a6d44e5 100644\n--- a/internal/http/client-registry.go\n+++ b/internal/http/client-registry.go\n@@ -18,6 +18,8 @@\n \tif err != nil {\n \t\treturn fmt.Errorf(\"get: %w\", err)\n \t}\n+\tfor attempt := 0; attempt < c.retries; attempt++ {\n+\t\tif err == nil {\n \tdefer resp.Body.Close()\n \treturn nil\n }\n@@ -32,10 +34,8 @@\n type Client struct {\n \ttimeout time.Duration\n }\n-\n func NewClient(timeout time.Duration) *Client {\n-\treturn &Client{timeout: timeout}\n-}\n+\t\tif err == nil {\n \n func (c *Client) Get(ctx context.Context) error {\n \tctx, cancel := context.WithTimeout(ctx, c.timeout)\n",
    "files": 3,
    "hunks": 6,
    "insertions": 10,
    "deletions": 8,
    "summary": [
      {
        "path": "docs/reference/getting-started-overview.md",
        "oldPath": "docs/reference/getting-started-overview.md",
        "changeType": "modify",
        "insertions": 4,
        "deletions": 3,
        "hunks": 2
      },
      {
        "path": "fixtures/package.json",
        "oldPath": "fixtures/package.json",
        "changeType": "modify",
        "insertions": 3,
        "deletions": 2,
        "hunks": 2
      },
      {
        "path": "internal/http/client-registry.go",
        "oldPath": "internal/http/client-registry.go",
        "changeType": "modify",
        "insertions": 3,
        "deletions": 3,
        "hunks": 2
      }
    ]
  },
  "meta": {
    "endpoint": "diffs",
    "route": "/patch",
    "params": {
      "files": 3,
      "hunksPerFile": 2,
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Reproducible ten-file Go patch
GET /api/diffs/patch?files=10&language=go&seed=99
{
  "data": {
    "patch": "diff --git a/cmd/server/client-registry.go b/cmd/server/client-registry.go\ndeleted file mode 100644\nindex 841c87a..0000000\n--- a/cmd/server/client-registry.go\n+++ /dev/null\n@@ -1,8 +0,0 @@\n-func (c *Client) Get(ctx context.Context) error {\n-\tctx, cancel := context.WithTimeout(ctx, c.timeout)\n-\tdefer cancel()\n-\n-\tresp, err := http.Get(c.url)\n-\tif err != nil {\n-\t\treturn fmt.Errorf(\"get: %w\", err)\n-\t}\ndiff --git a/cmd/server/main.go b/cmd/server/main.go\nindex 93a603f..7f6e682 100644\n--- a/cmd/server/main.go\n+++ b/cmd/server/main.go\n@@ -12,8 +12,11 @@\n )\n \n type Client struct {\n+\t\tif err == nil {\n \ttimeout time.Duration\n }\n+\tif resp.StatusCode >= 500 {\n+\tlog.Printf(\"retry %d\", attempt)\n \n func NewClient(timeout time.Duration) *Client {\n \treturn &Client{timeout: timeout}\n@@ -27,8 +30,8 @@\n \tif err != nil {\n \t\treturn fmt.Errorf(\"get: %w\", err)\n \t}\n-\tdefer resp.Body.Close()\n-\treturn nil\n+\t\t}\n+\tif resp.StatusCode >= 500 {\n }\n package store\n \ndiff --git a/cmd/server/server.go b/cmd/server/server.go\nindex fa8f214..a981d64 100644\n--- a/cmd/server/server.go\n+++ b/cmd/server/server.go\n@@ -15,12 +15,8 @@\n \n \tresp, err := http.Get(c.url)\n \tif err != nil {\n-\t\treturn fmt.Errorf(\"get: %w\", err)\n-\t}\n \tdefer resp.Body.Close()\n \treturn nil\n-}\n-package store\n \n import (\n \t\"context\"\n@@ -28,8 +24,6 @@\n \t\"net/http\"\n )\n \n-type Client struct {\n-\ttimeout time.Duration\n }\n \n func NewClient(timeout time.Duration) *Client {\ndiff --git a/internal/api/handler-middleware.go b/internal/api/handler-middleware.go\nindex 1c1d42d..d5c4e60 100644\n--- a/internal/api/handler-middleware.go\n+++ b/internal/api/handler-middleware.go\n@@ -2,9 +2,12 @@\n \n \tresp, err := http.Get(c.url)\n \tif err != nil {\n+\t\t\tbreak\n+\t\t}\n \t\treturn fmt.Errorf(\"get: %w\", err)\n \t}\n-\tdefer resp.Body.Close()\n+\tmux.HandleFunc(\"/healthz\", healthHandler)\n+\tretries int\n \treturn nil\n }\n package store\n@@ -13,5 +16,5 @@\n \t\"context\"\n \t\"fmt\"\n \t\"net/http\"\n-)\n-\n+\tfor attempt := 0; attempt < c.retries; attempt++ {\n+\t\tif err == nil {\ndiff --git a/internal/api/main-options.go b/internal/api/main-options.go\nold mode 100644\nnew mode 100755\ndiff --git a/pkg/cache/config.go b/internal/api/server.go\nsimilarity index 72%\nrename from pkg/cache/config.go\nrename to internal/api/server.go\nindex 79ad449..d43c04d 100644\n--- a/pkg/cache/config.go\n+++ b/internal/api/server.go\n@@ -17,7 +17,8 @@\n func NewClient(timeout time.Duration) *Client {\n \treturn &Client{timeout: timeout}\n }\n-\n+\tif resp.StatusCode >= 500 {\n+\tlog.Printf(\"retry %d\", attempt)\n func (c *Client) Get(ctx context.Context) error {\n \tctx, cancel := context.WithTimeout(ctx, c.timeout)\n \tdefer cancel()\n@@ -26,8 +27,7 @@\n \tif err != nil {\n \t\treturn fmt.Errorf(\"get: %w\", err)\n \t}\n-\tdefer resp.Body.Close()\n-\treturn nil\n+\tmux.HandleFunc(\"/healthz\", healthHandler)\n }\n package store\n \ndiff --git a/internal/http/client-registry.go b/internal/http/client-registry.go\nindex a7dc82b..df5905f 100644\n--- a/internal/http/client-registry.go\n+++ b/internal/http/client-registry.go\n@@ -15,8 +15,7 @@\n \n func (c *Client) Get(ctx context.Context) error {\n \tctx, cancel := context.WithTimeout(ctx, c.timeout)\n-\tdefer cancel()\n-\n+\tif resp.StatusCode >= 500 {\n \tresp, err := http.Get(c.url)\n \tif err != nil {\n \t\treturn fmt.Errorf(\"get: %w\", err)\n@@ -28,7 +27,6 @@\n \n import (\n \t\"context\"\n-\t\"fmt\"\n \t\"net/http\"\n )\n \ndiff --git a/internal/http/config.go b/internal/http/config.go\nindex dbd64f1..dc2c983 100644\n--- a/internal/http/config.go\n+++ b/internal/http/config.go\n@@ -14,8 +14,7 @@\n \tctx, cancel := context.WithTimeout(ctx, c.timeout)\n \tdefer cancel()\n \n-\tresp, err := http.Get(c.url)\n-\tif err != nil {\n+\tfor attempt := 0; attempt < c.retries; attempt++ {\n \t\treturn fmt.Errorf(\"get: %w\", err)\n \t}\n \tdefer resp.Body.Close()\n@@ -23,7 +22,7 @@\n }\n package store\n \n-import (\n+\tfor attempt := 0; attempt < c.retries; attempt++ {\n \t\"context\"\n \t\"fmt\"\n \t\"net/http\"\ndiff --git a/internal/http/handler-middleware.go b/internal/http/handler-middleware.go\nnew file mode 100644\nindex 0000000..08f7c68\n--- /dev/null\n+++ b/internal/http/handler-middleware.go\n@@ -0,0 +1,9 @@\n+\n+func NewClient(timeout time.Duration) *Client {\n+\treturn &Client{timeout: timeout}\n+}\n+\n+func (c *Client) Get(ctx context.Context) error {\n+\tctx, cancel := context.WithTimeout(ctx, c.timeout)\n+\tdefer cancel()\n+\ndiff --git a/internal/http/handler.go b/internal/http/handler.go\nindex e4320fa..ce7c3b2 100644\n--- a/internal/http/handler.go\n+++ b/internal/http/handler.go\n@@ -3,8 +3,7 @@\n }\n package store\n \n-import (\n-\t\"context\"\n+\t\t}\n \t\"fmt\"\n \t\"net/http\"\n )\n@@ -13,8 +12,10 @@\n \ttimeout time.Duration\n }\n \n+\tif resp.StatusCode >= 500 {\n func NewClient(timeout time.Duration) *Client {\n-\treturn &Client{timeout: timeout}\n+\tmux.HandleFunc(\"/healthz\", healthHandler)\n+\tretries int\n }\n \n func (c *Client) Get(ctx context.Context) error {\n\\ No newline at end of file\n",
    "files": 10,
    "hunks": 16,
    "insertions": 30,
    "deletions": 31,
    "summary": [
      {
        "path": "cmd/server/client-registry.go",
        "oldPath": "cmd/server/client-registry.go",
        "changeType": "delete",
        "insertions": 0,
        "deletions": 8,
        "hunks": 1
      },
      {
        "path": "cmd/server/main.go",
        "oldPath": "cmd/server/main.go",
        "changeType": "modify",
        "insertions": 5,
        "deletions": 2,
        "hunks": 2
      },
      {
        "path": "cmd/server/server.go",
        "oldPath": "cmd/server/server.go",
        "changeType": "modify",
        "insertions": 0,
        "deletions": 6,
        "hunks": 2
      },
      {
        "path": "internal/api/handler-middleware.go",
        "oldPath": "internal/api/handler-middleware.go",
        "changeType": "modify",
        "insertions": 6,
        "deletions": 3,
        "hunks": 2
      },
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Wide context, one hunk per file
GET /api/diffs/patch?contextLines=5&hunksPerFile=1&files=4&seed=3
{
  "data": {
    "patch": "diff --git a/app/server/session.ts b/app/server/session.ts\nindex 7ee9db3..d300cc5 100644\n--- a/app/server/session.ts\n+++ b/app/server/session.ts\n@@ -8,7 +8,7 @@\n const responseSchema = z.object({ id: z.string() });\n \n export function createClient(options: ClientOptions) {\n   const timeoutMs = options.timeoutMs ?? 5_000;\n \n-  async function request(path: string) {\n-    const url = new URL(path, options.baseUrl);\n\\ No newline at end of file\n+  const cache = new Map<string, unknown>();\n+    const cached = cache.get(url.href);\n\\ No newline at end of file\ndiff --git a/docs/images/chart.woff2 b/docs/images/chart.woff2\nindex c2cd123..89e74fe 100644\nBinary files a/docs/images/chart.woff2 and b/docs/images/chart.woff2 differ\ndiff --git a/docs/reference/CONTRIBUTING-faq.md b/docs/reference/CONTRIBUTING-faq.md\nindex a97f2ce..1f09552 100644\n--- a/docs/reference/CONTRIBUTING-faq.md\n+++ b/docs/reference/CONTRIBUTING-faq.md\n@@ -13,12 +13,12 @@\n \n ```bash\n npm install @example/app\n ```\n \n-## Configuration\n-\n+## Troubleshooting\n+See the [reference](./reference.md) for details.\n - `baseUrl` — the API origin to call.\n - `timeoutMs` — request timeout in milliseconds.\n \n ## Usage\n \ndiff --git a/tests/parser-helpers.py b/tests/parser-helpers.py\nindex 4f916de..099c40d 100644\n--- a/tests/parser-helpers.py\n+++ b/tests/parser-helpers.py\n@@ -15,7 +15,7 @@\n \n     return request\n from __future__ import annotations\n \n import logging\n-from dataclasses import dataclass\n-\n+            return cached\n+        logger.warning('retrying %s', path)\n\\ No newline at end of file\n",
    "files": 4,
    "hunks": 3,
    "insertions": 6,
    "deletions": 6,
    "summary": [
      {
        "path": "app/server/session.ts",
        "oldPath": "app/server/session.ts",
        "changeType": "modify",
        "insertions": 2,
        "deletions": 2,
        "hunks": 1
      },
      {
        "path": "docs/images/chart.woff2",
        "oldPath": "docs/images/chart.woff2",
        "changeType": "binary",
        "insertions": 0,
        "deletions": 0,
        "hunks": 0
      },
      {
        "path": "docs/reference/CONTRIBUTING-faq.md",
        "oldPath": "docs/reference/CONTRIBUTING-faq.md",
        "changeType": "modify",
        "insertions": 2,
        "deletions": 2,
        "hunks": 1
      },
      {
        "path": "tests/parser-helpers.py",
        "oldPath": "tests/parser-helpers.py",
        "changeType": "modify",
        "insertions": 2,
        "deletions": 2,
        "hunks": 1
      }
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Binary files only
GET /api/diffs/patch?changeType=binary&files=2&seed=5
{
  "data": {
    "patch": "diff --git a/docs/images/banner.gif b/docs/images/banner.gif\nindex 0c6421d..7246525 100644\nBinary files a/docs/images/banner.gif and b/docs/images/banner.gif differ\ndiff --git a/docs/images/screenshot.webp b/docs/images/screenshot.webp\nindex 99c4b0b..5351cca 100644\nBinary files a/docs/images/screenshot.webp and b/docs/images/screenshot.webp differ\n",
    "files": 2,
    "hunks": 0,
    "insertions": 0,
    "deletions": 0,
    "summary": [
      {
        "path": "docs/images/banner.gif",
        "oldPath": "docs/images/banner.gif",
        "changeType": "binary",
        "insertions": 0,
        "deletions": 0,
        "hunks": 0
      },
      {
        "path": "docs/images/screenshot.webp",
        "oldPath": "docs/images/screenshot.webp",
        "changeType": "binary",
        "insertions": 0,
        "deletions": 0,
        "hunks": 0
      }
    ]
  },
  "meta": {
    "endpoint": "diffs",
    "route": "/patch",
    "params": {
      "files": 2,
      "hunksPerFile": 2,
      "contextLines": 3,
      "changeType": "binary",
      "seed": 5
    },
    "generatedAt": "2026-07-29T08:13:22.000Z",
    "warnings": [
      "'changeType=binary' produces no hunks at all, so 'hunksPerFile' and 'contextLines' are not used."
    ]
  }
}

About this API

Coverage & behavior

Generates unified diff / patch fixtures whose arithmetic is correct by construction. Every @@ -a,b +c,d @@ header is rendered from the hunk body it introduces, so the numbers can never drift out of sync the way hand-written fixtures always do:

  • b equals the count of context plus - removed lines; d equals context plus + added lines.
  • a and c are correct 1-based start lines in the old and new files. Consecutive hunks in a file are strictly increasing, never overlap, and always have at least one unchanged line between them.
  • A single-line range prints as @@ -5 +5,2 @@ — the ,1 is omitted, exactly like git diff and POSIX diff -u. An empty range prints its count as ,0 with the start set to the line before the range (0 at the start of a file), which is how @@ -0,0 +1,9 @@ and @@ -12,0 +13,2 @@ arise.
  • Context lines are byte-identical on both sides — a context line exists once in hunks[].lines and is rendered once.
  • insertions/deletions are summed from the hunks, so a file's numbers, the lines in its rendered patch and the /patch totals always reconcile with each other.

Change types. changeType is a hard guarantee, not a probability: set it and every file has that type.

changeType Emits
modify index …, --- a/…, +++ b/…, hunksPerFile hunks
add new file mode, --- /dev/null, one hunk starting @@ -0,0 +1,…
delete deleted file mode, +++ /dev/null, one hunk ending +0,0 @@
rename similarity index N%, rename from/rename to, plus content hunks
mode old mode/new mode only — no index line, no hunks, 0 insertions and 0 deletions
binary index … then Binary files a/x and b/x differ, and zero hunks

Because a mode record has neither hunks nor an index line, its path is the only thing left to tell two of them apart — so from the second record onwards the record index is appended to the file stem (src/lib/cache-3.js). Without it, two mode-only records could come out byte-identical whenever from and to pin every date to the same instant. language still picks the directory and extension for a mode change; for binary it is ignored entirely, and setting it adds a warning saying so.

\ No newline at end of file appears when — and only when — a rendered line is the final line of its side of the file and that file has no trailing newline; those lines carry noNewline: true in hunks[].lines.

Honest scope limits. The code inside these patches is synthetic: it is generated from small per-language pools and is not real source from any project. The patches are valid unified-diff syntax, but they are not guaranteed to apply to any repository — there is no base tree, and the blob names on index lines are random hex rather than real object IDs. This endpoint does not diff two documents you supply; that would be a different kind of endpoint, and GET query-string limits make passing two files impractical here. Renames are asserted by construction rather than inferred by a similarity algorithm, so the similarity index percentage is a stated fixture value, not a computed one. Hunk headers are emitted bare (no trailing function/section heading), and generated paths never contain spaces, so no path quoting is needed. The line counts are also not synchronised with /api/git-commits: that endpoint draws its own insertions/deletions independently, so the same seed will not produce a commit whose stats match a diff here.

Size budget. Every response must fit the platform's 256 KiB size budget, so large hunksPerFile × contextLines combinations lower the maximum count (for example 23 records at hunksPerFile=6&contextLines=5, against the full 100 at the defaults). Exceeding it returns a 400 naming every value involved instead of silently truncating the list.

Use it for

  • Test a unified-diff parser against correct @@ headers, empty ranges and missing-newline markers
  • Populate a code-review or pull-request UI with multi-file patches before the backend exists
  • Build diff-viewer fixtures without copying real source code out of a private repository
  • Check that a patch renderer handles renames, mode changes and binary files, not just modifications

Frequently asked questions

Where can I get a sample unified diff to test my parser?

Call /api/diffs for one changed file per record (structured hunks plus the rendered patch), or /api/diffs/patch for a single multi-file patch string. Both are seedable, so a fixture stays byte-identical forever.

Do the @@ hunk headers match the hunk body?

Always. The header is rendered from the body, so the old count equals the context plus removed lines and the new count equals the context plus added lines. Single-line ranges omit the ,1 and empty ranges print ,0, exactly like git diff.

Can I diff two files or strings that I supply?

No. This endpoint only generates fixtures; it never compares documents you send. Passing two files through a GET query string is impractical, so the API does not pretend to offer it.

Will these patches apply with git apply?

No. They are valid unified-diff syntax, but the content is synthetic and there is no base tree to apply against — the index blob names are random hex, not real git object IDs.

How do I get an added, deleted, renamed or binary file diff?

Set changeType to add, delete, rename, mode or binary. It is a guarantee rather than a probability, so every file in the response uses that form — /dev/null headers, rename from/rename to, old mode/new mode, or Binary files … differ with zero hunks.

Is the similarity index a real computed value?

No. Renames are asserted by construction rather than detected, so the similarity index N% line carries a stated fixture percentage between 55 and 98 instead of a measured one.

Standards & references