# LLM JSON Repair & Schema Validator (`kadev-code/llm-json-repair`) Actor

Fast, deterministic repair and schema validation for broken or malformed JSON from ChatGPT, Claude, DeepSeek, and local LLMs.

- **URL**: https://apify.com/kadev-code/llm-json-repair.md
- **Developed by:** [Kadev Code](https://apify.com/kadev-code) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 repaired jsons

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LLM JSON Repair & Schema Validator

Repair structural JSON from LLM output and validate it against an optional JSON
Schema. Parsing runs locally without calling an LLM, downloading schemas, or
executing input code. The engine accepts a conservative subset of syntax errors
so that ambiguous content does not become a paid result.

Supported repairs include single-quoted strings, unquoted object keys, Python
`True`/`False`/`None`, trailing commas, comments outside strings, Markdown fences,
and missing closing brackets after complete values. For example,
`{name: 'Alice', active: True,}` becomes `{"name":"Alice","active":true}`.
An escaped apostrophe in `{'msg': 'it\'s ok'}` is preserved as `"it's ok"`.
An unescaped apostrophe, incomplete string, missing value, duplicate key,
non-finite number, or unrecognized bare value is rejected. Floating-point
numbers whose decimal value cannot survive output serialization are also
rejected; integers remain Python integers.
For example, `{"x": 0.1234567890123456789}` is rejected rather than rounded.
A literal newline inside a quoted string is also rejected because guessing its
escape could change the value; submit the JSON escape `\n` instead. Missing
closing brackets after complete values can be repaired, but extra closing
braces/brackets such as `{"a":1}}}` produce a warning, `lossy=true`, and no result.

### Input

| Parameter | Type | Default | Meaning |
|---|---|---|---|
| `texts` | array of strings | required | 1–100 texts, at most 1,000,000 characters each and 5,000,000 characters combined |
| `schema` | object | omitted | Optional JSON Schema, at most 100,000 serialized characters; local fragment references only |
| `extractMode` | `first` or `all` | `first` | Select first structural block or combine all selected blocks |
| `failOnError` | boolean | `false` | Fail the run after delivering the first invalid result |

The API also accepts a singular string `text` when `texts` is absent. Explicit
`texts: []`, missing input and incorrect types produce a clear input error and
no custom charge. Examples are provided as Console prefill only; missing input
never starts a paid demonstration. Empty individual strings produce invalid
results. JSON nesting is limited to 100 levels.

This example uses two records that both satisfy the supplied schema:

```json
{
  "texts": [
    "Here is the result: {name: 'Alice', active: True,}",
    "{\"name\":\"Bob\",\"active\":false}"
  ],
  "schema": {
    "type": "object",
    "properties": {
      "name": {"type": "string"},
      "active": {"type": "boolean"}
    },
    "required": ["name", "active"]
  },
  "extractMode": "first",
  "failOnError": false
}
```

### Selection and preservation

Only object or array roots are accepted. Scalar roots and empty containers have
`valid=false`. Placeholders such as `{name}` are ignored because they contain no
key/value pair. Arrays containing only numbers, only strings, or only booleans
embedded in unfenced prose are treated as ambiguous citations, options, or flags.
Examples include `See section [3] for details`, `Choose from ['red', 'blue']`,
and `flags [true, false]`; none produces a valid result. Submit such arrays
directly (`[3]`, `[true, false]`) or in a Markdown code fence to make their intent
explicit. This conservative heuristic can also reject adjacent scalar arrays
without prose. Mixed arrays and object arrays still follow structural extraction.

`first` selects the first structural candidate and preserves an array root.
Later blocks and surrounding prose are intentionally outside that selection.
`all` appends object blocks and flattens array blocks one level; for a schema
with `type: object`, each resulting element is validated separately. For other
schemas the combined array is validated. This selection is part of the input
contract, rather than an attempt to preserve every word of surrounding prose.
`Result: {"ok": true} ... Thanks {for} reading` in `all` mode yields
`[{"ok": true}]`, with a warning for the ignored placeholder.

Every token inside each selected block must be accounted for by a supported
syntax conversion. The engine does not salvage a smaller object by dropping
unrecognized fields. For `{"a": 1, bla bla bla}`, it returns `valid=false`,
`lossy=true`, and a warning. No partial repaired data is returned or charged.
Here `lossy=true` means content preservation could not be verified and the
repair was refused; it never denotes a successful paid repair. Closing JSON
brackets outside candidate blocks are conservatively considered unmatched, even
after the first selected block; they are never silently discarded.

### Output and billing

An ordinary completed run produces one dataset row per submitted text. Invalid
batch configuration instead produces one error row with `index: null` and fails
the run. Spending limits, `failOnError`, or storage failures can stop processing
before all rows are delivered.

```json
{
  "index": 0,
  "valid": true,
  "repaired": {"name": "Alice", "active": true},
  "issuesDetected": [
    "quoted_unquoted_keys",
    "converted_single_quotes",
    "normalized_python_constants",
    "removed_trailing_commas",
    "extracted_json_from_surrounding_text"
  ],
  "schemaErrors": [],
  "lossy": false,
  "warnings": [],
  "error": null
}
```

`issuesDetected` replaces the former heuristic `repairsApplied` field. It reports
syntax categories observed during normalization, without offsets, counts or a
complete edit history. `warnings` explains ambiguous repairs or extraction
decisions; `schemaErrors` contains validation diagnostics. A schema mismatch can
include the repaired data for inspection while keeping `valid=false`.
Repeated warnings are aggregated into one string per category, with a suffix
such as `(250000 occurrences)`; a single occurrence keeps its original message.
Each result is capped at 96,000 serialized ASCII-escaped JSON bytes, leaving
space for the row index below a local 100 KB item budget. This includes repaired
data and diagnostics. An oversized result becomes a small invalid error row
with `repaired=null`, `lossy=true`, and no custom charge; data is never truncated
into a valid result. This local cap is conservative and is not a claim about
the platform's current item limit. Split large documents into smaller texts.

Proposed price: **$0.0005 per valid delivered text** (`json-repaired`), including
already-valid input. A text with multiple selected blocks still costs one event.
Invalid, empty, lossy or schema-failing results have no custom event charge.
The SDK's `push_data(..., charged_event_name="json-repaired")` reserves budget,
delivers an eligible row and charges it. The run stops at the reported limit.
A delivery or charging exception fails the run without retry; earlier delivered
results may already have been charged, and uncertain transport outcomes cannot
be promised a refund by this code.

Before publication, remove both synthetic events `apify-actor-start` and
`apify-default-dataset-item` in Apify Console so that neither errors nor starts
generate automatic fees. The runtime refuses a paid synthetic configuration
before pushing rows, but cannot undo a start fee already imposed by the platform.
Console pricing has not been configured or tested in this local worktree.
See [pricing rationale](.actor/pricing.md) for fixed startup cost and limitations.

### Performance and development

Measured locally on Windows, Python 3.12.13, 2026-10-22: one warmup followed by five
timed repetitions of `repair_single_text`, without a schema, SDK initialization,
dataset I/O or charging. Sizes below are decimal KB/MB and ASCII bytes equal
character counts. These are observations, not Apify latency guarantees.

| Input size | Object with long string, median | Many small objects needing syntax repair, median |
|---|---:|---:|
| 10 KB (10,000 bytes) | 0.662 ms | 6.944 ms |
| 100 KB (100,000 bytes) | 6.527 ms | 68.586 ms |
| 1 MB (1,000,000 bytes) | 64.818 ms | 681.792 ms |

Both 10 KB workloads produce valid results. The 100 KB and 1 MB workloads
exceed the output cap and produce small, uncharged error rows after parsing.
Candidate scanning uses non-overlapping ranges and precomputed context indices,
so surrounding-text checks do not rescan the input for every candidate.

In `all` mode, 100,000 repetitions of `{"a":1} ` (800 KB) took a median
748.494 ms and returned a 243-byte oversized-result error row. Repeated `[1] `
at exactly 1 MB took a median 1,345.985 ms and returned a 256-byte row with one
aggregated warning. All five samples for both cases stayed below 2 seconds.
See [full measurements and baseline comparison](benchmark-results.json).

Reproduce from the repository root:

```sh
python -m pip install -r actors/llm-json-repair/requirements-dev.txt
python -m pytest actors/llm-json-repair/tests -q
python actors/llm-json-repair/benchmark.py
```

Add `--baseline-ref e5da053 --output actors/llm-json-repair/benchmark-results.json`
to also measure the pre-R2 implementation once, loaded in memory using read-only
`git show`, and save a comparison report. Baseline measurements can be slow.

The Docker image uses Python 3.12. Runtime dependencies exclude pytest and the
permissive `json-repair` library; normalization uses Python's strict JSON decoder
and explicit token transformations. Tests block external connections and place
SDK storage under pytest `tmp_path` using `APIFY_LOCAL_STORAGE_DIR`. The actor's
`.gitignore` covers local `storage/` and Python artifacts. Default and minimum
memory are 256 MB, with a 512 MB maximum; cloud peak memory is not yet measured.

### Errors, retention and responsible use

Unsupported or remote schema references return an explicit error without fetching
the reference. Only local `#` fragments are supported, including `$defs` links.
Do not submit secrets or personal data that you are not authorized to process.
Review repaired output before using it for consequential actions: structural
validity does not prove that an LLM's statements are correct.

The Actor creates no extra raw-input files and uses no third-party model API.
Apify retains the run input and dataset according to its storage settings; data
is **not automatically discarded when the run completes**. Manage retention and
deletion in your own Apify account. Local test storage is temporary.

References: [SDK event charging](https://docs.apify.com/sdk/python/docs/concepts/pay-per-event),
[platform monetization](https://docs.apify.com/actors/publishing/monetize/pay-per-event).

# Actor input Schema

## `texts` (type: `array`):

Required non-empty list of up to 100 strings; at most 1,000,000 characters per string and 5,000,000 characters total. No automatic demonstration.

## `schema` (type: `object`):

Optional object JSON Schema; at most 100,000 serialized characters. Local fragment references only; remote references are not supported.

## `extractMode` (type: `string`):

first selects the first structural JSON block; all combines selected objects and flattens selected arrays one level. Non-JSON placeholders are ignored. Numeric arrays in unfenced prose are ambiguous and rejected.

## `failOnError` (type: `boolean`):

If enabled, the Actor run fails if any item cannot be repaired. If disabled (default), invalid items are pushed to the dataset with valid=false and error details.

## Actor input object example

````json
{
  "texts": [
    "```json\n{\n  name: 'Alice',\n  \"role\": 'Engineer',\n  \"skills\": ['Python', 'AI',],\n  \"active\": True,\n}\n```\nHope this helps!",
    "Here is the product info: {\"id\": 101, \"title\": \"Smart Lamp\", \"price\": 29.99, \"inStock\": true}"
  ],
  "schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "role": {
        "type": "string"
      },
      "skills": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "active": {
        "type": "boolean"
      }
    },
    "required": [
      "name",
      "role"
    ]
  },
  "extractMode": "first",
  "failOnError": false
}
````

# Actor output Schema

## `results` (type: `string`):

Repair and validation results in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

````javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "texts": [
        "```json\n{\n  name: 'Alice',\n  \"role\": 'Engineer',\n  \"skills\": ['Python', 'AI',],\n  \"active\": True,\n}\n```\nHope this helps!",
        "Here is the product info: {\"id\": 101, \"title\": \"Smart Lamp\", \"price\": 29.99, \"inStock\": true}"
    ],
    "schema": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string"
            },
            "role": {
                "type": "string"
            },
            "skills": {
                "type": "array",
                "items": {
                    "type": "string"
                }
            },
            "active": {
                "type": "boolean"
            }
        },
        "required": [
            "name",
            "role"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kadev-code/llm-json-repair").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

````

## Python example

````python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "texts": [
        """```json
{
  name: 'Alice',
  \"role\": 'Engineer',
  \"skills\": ['Python', 'AI',],
  \"active\": True,
}
````

Hope this helps!""",
"Here is the product info: {"id": 101, "title": "Smart Lamp", "price": 29.99, "inStock": true}",
],
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"role": { "type": "string" },
"skills": {
"type": "array",
"items": { "type": "string" },
},
"active": { "type": "boolean" },
},
"required": \[
"name",
"role",
],
},
}

# Run the Actor and wait for it to finish

run = client.actor("kadev-code/llm-json-repair").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)

print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

````

## CLI example

```bash
echo '{
  "texts": [
    "```json\\n{\\n  name: '\''Alice'\'',\\n  \\"role\\": '\''Engineer'\'',\\n  \\"skills\\": ['\''Python'\'', '\''AI'\'',],\\n  \\"active\\": True,\\n}\\n```\\nHope this helps!",
    "Here is the product info: {\\"id\\": 101, \\"title\\": \\"Smart Lamp\\", \\"price\\": 29.99, \\"inStock\\": true}"
  ],
  "schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "role": {
        "type": "string"
      },
      "skills": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "active": {
        "type": "boolean"
      }
    },
    "required": [
      "name",
      "role"
    ]
  }
}' |
apify call kadev-code/llm-json-repair --silent --output-dataset

````

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kadev-code/llm-json-repair"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/kJem6tVYoJhk6uPmu/builds/kLXTQwqwPQATjtWks/openapi.json
