SheetDiff™Structural Diff API

Structural Diff API

A REST API that compares an AI-generated transcript against its annotator post-edit — detecting row-level structural changes (splits, merges, modifications, additions, deletions), with per-column diff detail, CER/WER/SegER/SER/SACR scoring, and a composite quality grade per batch.

API Key Auth
x-api-key header
Rate Limited
10/min · 60/15min
JSON REST
application/json
8-Pass Engine
split + merge detection

Quick Start

No SDK needed. Send a POST request with your two arrays of transcript rows and receive a full diff in JSON. The API is in tasting phase — request an API key to get started.

1. Verify the service is live:

bash
curl https://structural-diff-engine.onrender.com/v1/health

2. Run a comparison:

curl -X POST https://structural-diff-engine.onrender.com/v1/diff \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "original": [
      { "speaker": "Alice", "start_time": 0, "end_time": 1, "transcript": "Hello world" },
      { "speaker": "Bob",   "start_time": 1, "end_time": 3, "transcript": "Good morning everyone" }
    ],
    "reworked": [
      { "speaker": "Alice", "start_time": 0, "end_time": 1, "transcript": "Hello there" },
      { "speaker": "Bob",   "start_time": 1, "end_time": 2, "transcript": "Good morning" },
      { "speaker": "Bob",   "start_time": 2, "end_time": 3, "transcript": "everyone" }
    ]
  }'

Base URL

All endpoints are prefixed with /v1.

url
https://structural-diff-engine.onrender.com

Authentication

Include your API key in the x-api-key request header on every call to /v1/diff.

bash
curl -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -X POST https://structural-diff-engine.onrender.com/v1/diff -d '{...}'
Keys are provisioned individually per agency. A missing or invalid key returns 401 Unauthorized.

Rate Limits

Two independent tiers are enforced per API key, falling back to IP when no key is present. Exceeding either tier returns 429 Too Many Requests.

TierLimitResponse header
Burst10 requests / minuteRateLimit-Limit
Window60 requests / 15 minutesRateLimit-Remaining

Endpoints

GET /v1/health

Lightweight liveness probe. No authentication required. Returns service version and uptime.

GET/v1/health· No auth
json
{ "status": "ok", "version": "1.0.0", "uptime": 42, "timestamp": "..." }

POST /v1/diff

Compare two arrays of transcript rows. Returns row-level results with quality scores. Max payload: 5 MB · Max rows: 30,000.

POST/v1/diff Auth required

Request Body

NameTypeDescription
original*arrayRow objects from the baseline / original version.
reworked*arrayRow objects to compare against.
configobjectOptional algorithm overrides. See .
headersstring[]Column names — required when using 2-D array input.
columnMappingobjectColumn index map for 2-D array input. See .

Row object fields

All fields are optional except transcript. Unknown fields are passed through unchanged.

NameTypeDescription
transcript*stringThe text content of the row.
speakerstringSpeaker name or ID.
start_timenumber|stringSegment start time in seconds.
end_timenumber|stringSegment end time in seconds.
non_speech_eventsstringAnnotations such as [music], [laughter].
emotionstringEmotion label.
languagestringLanguage code (e.g. "en", "ar").
localestringLocale code (e.g. "en-US").
accentstringAccent tag.
file_namestringSource file name. Pass-through only — not used by the diff algorithm.

Response Shape

All successful responses use this envelope:

json
{
  "status": "success",
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-04-08T21:00:00.000Z",
  "data": {
    "results": [
      {
        "status": "MODIFIED",
        "originalRow": { "transcript": "Hello world", ... },
        "reworkedRow": { "transcript": "Hello there", ... },
        "notes": "transcript changed"
      },
      {
        "status": "SPLIT",
        "originalRow": { "transcript": "Good morning everyone", ... },
        "reworkedRows": [ { "transcript": "Good morning" }, { "transcript": "everyone" } ],
        "notes": "split into 2 rows"
      }
    ],
    "scores": { "overallCER": 0.12, "overallWER": 0.18, "SegER": 0.33, "transcriptCER": 0.12, "transcriptWER": 0.18, "SER": 0.05, "transcriptSER": 0.04, "SACR": null },
    "composite": { "grade": 3.8, "label": "Good", "percent": "12.3" },
    "meta": { "originalRows": 2, "reworkedRows": 3, "headers": [...] }
  }
}

Diff statuses

StatusMeaning
UNCHANGEDRow is identical in both versions.
MODIFIEDRow exists in both versions but content changed.
ADDEDRow is only present in the reworked version.
DELETEDRow is only present in the original version.
SPLITOne original row was divided into two or more reworked rows.
MERGEDTwo or more original rows were combined into one reworked row.

Scores

NameTypeDescription
overallCERnumberCharacter Error Rate across all columns (0–1, lower is better).
overallWERnumberWord Error Rate across all columns (0–1).
SegERnumberSegmentation Error Rate — boundary events (splits, merges, added and deleted rows) / expected segment count (0–1, lower is better).
transcriptCERnumberCER computed on the transcript column only.
transcriptWERnumberWER computed on the transcript column only.
SERnumberSentence Error Rate — MODIFIED rows / (UNCHANGED + MODIFIED). Fraction of comparable rows with any edit (0–1).
transcriptSERnumberSER computed on sentences within the transcript column text.
SACRnumberSpeaker Attribution Change Rate — speaker-changed rows / MODIFIED rows. null when no speaker column is detected.

Composite grade

NameTypeDescription
gradenumberNumeric score (1.0–5.0, higher is better) averaged across enabled metrics.
labelstringHuman-readable label — one of: "Excellent", "Good", "Acceptable", "Below Average", "Poor", "Unacceptable".
percentstringAverage error percentage across the enabled scoring metrics.
enabledMetricsstring[]Array of metric names that contributed to this composite (e.g. ["CER", "Transcript CER", "WER", "Transcript WER", "SegER", "SER", "Transcript SER"]). Empty when all metrics are disabled.

Response meta

NameTypeDescription
originalRowsnumberNumber of rows in the original array.
reworkedRowsnumberNumber of rows in the reworked array.
headersstring[]Column header names used for this diff.

Config Options

Pass a config object in the request body to override algorithm defaults. All fields are optional.

NameTypeDefaultDescription
simpleModebooleanfalseDisable split and merge detection. Pure row-by-row diff.
enableSplitsbooleantrueEnable split row detection.
enableMergesbooleantrueEnable merge row detection.
enableCERbooleantrueCompute Character Error Rate.
enableWERbooleantrueCompute Word Error Rate.
enableSegERbooleantrueCompute Segmentation Error Rate (splits, merges, boundary events).
enableSERbooleantrueCompute Sentence Error Rate.
stripDiacriticsbooleantrueNormalise Arabic/accented characters before comparison.
positionalModebooleanfalseCompare rows strictly by position, skipping alignment.
ignoreColNamesstring[][]Column names excluded from MODIFIED detection.
enableInlineDiffbooleantrueInclude transcriptDiff on MODIFIED rows. Set false to skip char-level diff and reduce response size.
structuralTransformsTransformRule[][]Pre-comparison find/replace rules applied to both sides before similarity scoring (max 20 rules).
enableTranscriptCERbooleantrueCompute CER restricted to the transcript column only. Independent of enableCER.
enableTranscriptWERbooleantrueCompute WER restricted to the transcript column only. Independent of enableWER.
enableTranscriptSERbooleantrueTranscript-column sentence-level SER — counts changed sentences across MODIFIED, SPLIT, and MERGED rows.
enableSACRbooleantrueCompute Speaker Attribution Change Rate (speaker-changed MODIFIED rows / total MODIFIED rows). Auto-detects speaker column; returns null when none found.
speakerColNamestringauto-detectOverride auto-detection of the speaker column. Case-insensitive match. (e.g. "spk_id").
enableCompositebooleantrueCompute composite quality grade (1–5 average across enabled metrics).
cerInCompositebooleantrueInclude overallCER in composite grade. CER is still computed and returned when false.
werInCompositebooleantrueInclude overallWER in composite grade. WER is still computed and returned when false.
segerInCompositebooleantrueInclude SegER in composite grade. SegER is still computed and returned when false.
serInCompositebooleantrueInclude SER in composite grade. SER is still computed and returned when false.

Column Mapping

When original / reworked are 2-D arrays (arrays of arrays) instead of objects, supply headers and/or columnMapping to tell the engine which index carries each field.

json
{
  "original":      [[0, 1, "Alice", "Hello world"]],
  "headers":       ["start_time", "end_time", "speaker", "transcript"],
  "columnMapping": { "transcript": 3, "speaker": 2, "start_time": 0, "end_time": 1 }
}
NameTypeDescription
transcript*integer0-based column index of the transcript field.
speakerinteger0-based column index of the speaker field.
start_timeinteger0-based column index of start time.
end_timeinteger0-based column index of end time.
nseinteger0-based column index of non-speech events.
extraColsinteger[]Additional column indices to include (max 20).

Error Reference

All errors use a uniform envelope:

json
{
  "status": "error",
  "requestId": "550e8400-...",
  "timestamp": "2026-04-08T21:00:00.000Z",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [{ "field": "original", "message": "\"original\" is required" }]
  }
}
HTTPCodeCause
400BAD_REQUESTMalformed JSON body
401UNAUTHORIZEDMissing or invalid x-api-key header
404NOT_FOUNDUnknown endpoint
413PAYLOAD_TOO_LARGERequest body exceeds 5 MB
422VALIDATION_ERRORBody failed schema validation (see details array)
429RATE_LIMIT_EXCEEDEDBurst or window rate limit hit
500INTERNAL_SERVER_ERRORUnexpected server or engine error

Request Tracing

Provide an x-request-id header to correlate requests across your system. Alphanumeric characters, hyphens, and underscores only, max 64 characters. The value is echoed back in the response headers.

bash
curl -H "x-request-id: job-2026-01-batch-3" \
  -H "x-api-key: YOUR_KEY" \
  -X POST https://structural-diff-engine.onrender.com/v1/diff -d '{...}'

Get API Access

The API is available to agencies and teams in tasting phase. Keys are provisioned individually. Reach out to receive your key and start integrating.