Developers

How this works, in artifacts.

The technical account of the product, in three parts.

How a transform actually works

One real transform, walked end to end: a single recorded run followed from the document a model wrote to the one record it could not process. The ruleset below is the demo's own, read from the committed run rather than written out beside it.

A ruleset is JSON

There is no new language to learn, and nothing to compile. That is what makes a repair reviewable: it arrives as a few changed lines you can read, not as generated code you have to audit.

The grammar constrains the model; it is not a language you write. Four operators (map, explode, filter, aggregate) plus coercions for units, encodings, and formats. The model may only produce this shape, which is what makes its output reviewable.

This one is 90 lines end to end: 2 stages, 5 mapped fields, 8 declared drops. A reviewer reads it in a sitting, which is the whole argument for constraining the model to a grammar rather than letting it write code.

{
  "dsl_version": 2,
  "inventory_version": 1,
  "stages": [
    {
      "id": "explode-1",
      "op": "explode",
      "path": "properties.periods"
    },
    {
      "fields": [
        {
          "coerce": null,
          "missing": null,
          "on_null": "omit",
          "source": "properties.periods.startTime",
          "target": "startTime"
        },
        {
          "coerce": null,
          "missing": null,
          "on_null": "omit",
          "source": "properties.periods.isDaytime",
          "target": "isDaytime"
        },
        {
          "coerce": {
            "from_unit": "fahrenheit",
            "kind": "unit",
            "to_unit": "celsius"
          },
          "missing": null,
          "on_null": "omit",
          "source": "properties.periods.temperature",
          "target": "temperatureCelsius"
        },
        {
          "coerce": null,
          "missing": null,
          "on_null": "omit",
          "source": "properties.periods.probabilityOfPrecipitation.value",
          "target": "precipitationProbability"
        },
        {
          "coerce": null,
          "missing": null,
          "on_null": "omit",
          "source": "properties.periods.shortForecast",
          "target": "weatherDescription"
        }
      ],
      "id": "map-fields",
      "op": "map"
    }
  ]
}
The ruleset from the demo's recorded run — the document a model wrote, and the one the engine executed to produce that run's output. Its 8 drop declarations are the one part held back from this block: they are mostly prose, and they are listed in full below.

Reading it

op: explode
One source record becomes one output record per element of the array at this path — here, one per forecast period.
op: map
Every output field, named explicitly. The output holds exactly the targets this stage lists and nothing else, so a field that is not mapped does not pass through by accident.
source / target
The path this rule reads, and the output field it writes. Paths are dot-separated keys; explode is the only way to reach inside an array.
coerce
The one conversion this field applies on the way through — here Fahrenheit to Celsius, declared on the rule rather than hidden in code.
on_null: omit
What to do when the source is absent or null: leave the key out rather than write a fabricated null. If the target schema requires that field, the record fails the output check instead — which is exactly what happens below.
dsl_version
The grammar this document was written against. A ruleset authored against a newer one is refused rather than half-understood.

Every field is accounted for

This ruleset accounts for 13 source fields: 5 mapped to an output field and 8 declared as deliberately dropped, each with a reason. Nothing is discarded silently. This is the rule the whole system is built on: if a mapping cannot say what it drops, it is broken — however many operators we add later.

Authoring will not accept a ruleset that leaves a field it extracted unaccounted for. A field the source starts sending that the ruleset has never seen is a different case, and it does not fail the record: the run counts it and reports it, because unexpected extra data must not stop a record that is otherwise fine.

Every source field this ruleset reads, and what it does with each
Source fieldWhat happens to it
properties.periods.startTimestartTime
properties.periods.isDaytimeisDaytime
properties.periods.temperaturetemperatureCelsius — unit coercion
properties.periods.probabilityOfPrecipitation.valueprecipitationProbability
properties.periods.shortForecastweatherDescription
properties.unitsDropped System-of-units flag not needed; temperature unit is carried per-period by temperatureUnit.
properties.periods.numberDropped Sequence index of the forecast window; not part of the output record.
properties.periods.nameDropped Human label for the window (e.g. 'Tonight'); no target field.
properties.periods.endTimeDropped Output record carries only startTime; no target for endTime.
properties.periods.temperatureUnitDropped Source unit indicator ('F'); consumed as the from_unit of the temperature conversion, not emitted.
properties.periods.probabilityOfPrecipitation.unitCodeDropped Unit code for the precipitation percentage; not needed for the integer percent output.
properties.periods.windSpeedDropped Wind speed free text; no target field in the output record.
properties.periods.windDirectionDropped Wind direction; no target field in the output record.

When a record fails

A record the rules cannot process does not fail the run. It is set aside whole, labelled with what failed and why, while the rest of the batch keeps flowing. In the run below the output gate accepted 2 records and rejected 1; the run finished and reported both, rather than stopping at the one it could not pass.

There are two checkpoints a record can fail at, and the entry says which one stopped it: the rules while they run — a value that will not convert, a lookup key outside the table — or the output shape afterwards, when the result is checked against the target schema.

A coercion never guesses. The lossless conversions either convert exactly or fail the record; they do not truncate, round, or approximate to keep a record moving. The ones that lose information by design say so through the kind you chose.

Every source record ends the run in exactly one of three states: it transformed, it was set aside, or the rules deliberately produced nothing for it. The response says which, per record.

The record this run rejectedRejected
missing_required_field
required field 'precipitationProbability' is missing — expected field 'precipitationProbability' present, got absent
at /precipitationProbability
{
  "isDaytime": true,
  "startTime": "2026-07-03T06:00:00-05:00",
  "temperatureCelsius": 32.78,
  "weatherDescription": "Mostly Sunny"
}
Recorded from the demo's run, which validates output directly rather than over the API. Run through the transform route, a record that fails like this one is parked on the quarantine queue as an entry with its own handle — kept whole rather than dropped, which is what makes re-running it against a later version possible.

API reference

These routes are the engine's contract. This site's own deployment does not serve them: a public deployment mounts the account-session boundary beside the enrollment endpoint, the authenticated delivery webhook its mail provider calls back on, and the health probes. The model-reaching routes listed below are structurally absent from it rather than merely unadvertised. Running the full surface is something you do on your own infrastructure.

POST/v1/rulesets

Dispatch a ruleset authoring job from the request's samples and schemas.

403
The request could not be authenticated.
409
The submission key was already used for a different request.
422
The request could not be processed as given.
503
This deployment does not accept authoring jobs from this caller.

POST/v1/transform

Run a pinned ruleset over JSON records and validate the normalized output.

404
The named ruleset or version does not exist.
422
The request could not be processed as given.

POST/v1/transform/csv

Run a pinned ruleset over an uploaded CSV file and validate the output.

404
The named ruleset or version does not exist.
422
The request could not be processed as given.

GET/v1/quarantine

Report every quarantined record currently held, oldest first.

403
The request could not be authenticated.

POST/v1/quarantine/{entry_id}/rerun

Re-run one quarantined record against a pinned ruleset version and report.

403
The request could not be authenticated.
404
The named quarantine entry, or the ruleset version to re-run under, does not exist.
410
The entry's raw record has passed its retention deadline, so it can no longer be re-run.

POST/v1/healing/proposals

Dispatch a healing draft job for one quarantined failure.

403
The request could not be authenticated.
404
The named quarantine entry, or the ruleset version it failed against, does not exist.
409
The submission key was already used for a different request.
410
The entry's raw record has passed its retention deadline, so it can no longer be re-run.
422
The request could not be processed as given.
503
This deployment does not accept authoring jobs from this caller.

POST/v1/healing/proposals/{proposal_id}/approve

Approve a gated proposal: pin it as a new version and re-run the record.

403
The request could not be authenticated.
404
The named heal proposal does not exist.
409
The approval could not be applied: either the proposal's status does not permit a decision — only a gated proposal is decided, and only once — or the ruleset has gained a version since the proposal was gated, so the draft must be re-drafted against the current version.
410
The entry's raw record has passed its retention deadline, so it can no longer be re-run.

What's coming

The next capabilities on the roadmap, and the one place on this site where capability the product does not have yet is named.

Read and diff any version
Fetch a stored ruleset and compare two versions side by side, so you can see exactly what a repair changed months after you approved it.
Healing that can run unattended
Approval-gated healing is the product today, and stays the default. An opt-in mode for feeds you trust to repair themselves is a deliberate, separate decision.
More formats
JSON and CSV today. XML, EDI, and the other shapes partners actually send are next.