Redline API Quickstart
Compare HTML, Markdown, plain text, JSON, DOCX, or searchable PDF. Get an HTML redline, structured JSON changes, or both. DOCX also supports optional Word Track Changes. Document diffs compare text and supported styles. Optional rendered pages show source layout; DOCX fonts and pagination can differ from Word. Images are displayed, but not compared. Scanned PDFs need OCR.
Send two strings to the content comparison endpoint. These examples use anonymous access within quota; authenticated API calls add the Bearer header described below. The response contains the detected changes.
Python
Install the requests package, then run:
import requests
response = requests.post(
"https://api.bookcicle.com/v1/redline/html",
json={"original": "One year.", "revised": "Two years."},
timeout=30,
)
response.raise_for_status()
print(response.json())
curl
curl https://api.bookcicle.com/v1/redline/html \
-H 'Content-Type: application/json' \
-d '{"original":"One year.","revised":"Two years."}'
JavaScript
const response = await fetch("https://api.bookcicle.com/v1/redline/html", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ original: "One year.", revised: "Two years." })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());
Base URL
https://api.bookcicle.com/v1
Authentication
Anonymous callers can receive HTML comparisons within the existing anonymous size, rate, and usage limits. Exports require a signed-in user or a valid API key. Anonymous callers cannot create export jobs or obtain export download URLs. A private comparison or viewer token does not authorize exports.
All authenticated requests require a Bearer token in the Authorization header:
Authorization: Bearer bc_live_0123456789abcdef0123456789abcdef
You can generate an API key in the API Keys Dashboard. Never share or commit your secret key.
Send your Bearer token and JSON content type. Signed upload URLs include their own authorization.
Payload signing: For direct document requests larger than 1 MiB of serialized JSON, clients must also send x-amz-content-sha256 containing the lowercase SHA-256 digest of the complete request body. Browsers and client SDKs supply this automatically.
Content Comparison API (Format-Agnostic)
Compare HTML, Markdown, plain text, or JSON in one synchronous request. Additions use <ins>; deletions use <del>.
POST /v1/redline/html
Content-Type: application/json
{
"original": "<p>The fox jumped over the fence.</p>",
"revised": "<p>The quick fox jumped over the old fence.</p>",
"mode": "auto",
"format": "html",
"output": ["html"]
}
Choose output: ["html"] (default), ["json"], or ["html", "json"]. These select fields in the response object.
Content comparison request
| Field | Type | Required / default | Meaning |
original | string or upload reference | Required | Baseline content, or {"uploadId":"..."} for staged inputs. |
revised | string or upload reference | Required | Revised content, or {"uploadId":"..."} for staged inputs. |
format | string | "auto" | html, markdown / md, txt / plain, json, or auto. Markdown becomes canonical HTML; JSON source is canonicalized and key-sorted. |
mode | string | "auto" | auto, word (inline words), or block (larger text segments). |
output | array of strings | ["html"] | ["html"], ["json"], or ["html", "json"]. Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL (comparisonId, viewerUrl, embedUrl, and viewerToken; aliases: viewer, embed). These select object fields, not an array response. |
hostedViewerTtl | string or integer | "24h" | Adjustable availability and token validity window for hosted embeds (when "hosted_viewer" is in output). Creator tier supports "24h" (default) or "32h". Creator Pro supports "24h" (default), "32h", "48h", or "72h". Integer hours (24, 32, 48, 72) or seconds (e.g. 86400) are also accepted. |
Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL. See Hosted Viewer & Embeds.
JSON changes refer to the output HTML. See Structured JSON Contract, for the output schema and HTML location mapping.
Content comparison response
| Field | Type | Presence | Meaning |
schemaVersion | string | Always | redline.html.v1 for HTML-only; redline.comparison.v1 when JSON is requested. |
html | string | HTML requested | HTML redline fragment for rendering; omitted for JSON-only. |
cssUrl | URL string | Always | Public Redline v1 stylesheet for the current environment. Load once in your rendering document. |
json | object | JSON requested | First page of structured changes; see the JSON tables below. |
changesUrl | URL string | JSON requested | Private pagination endpoint; preserve its token. Relative URLs resolve against the API origin. |
requestedMode / effectiveMode | string | Always | Requested and selected text diff modes. |
format / engine / fallback | string / string / boolean | Always | Effective input format; dom / text engine; whether fallback was used. |
units | integer | Always | Comparison’s metered Redline units. |
usage | object | Always | used, allowance, remaining, and plan for the account. |
comparisonId | string | Stored comparison | Identifier for stored comparison retrieval. |
viewerUrl / embedUrl | URL string | Stored HTML available | Browser review and embed pages, including private access tokens. These are viewer pages, not raw HTML download URLs. |
viewerToken | string | Stored HTML available | Private comparison access token; treat as a credential. |
Small text comparisons can reuse a caller-scoped result for 60 minutes when inputs and options match. Cache hits do not consume additional units; simultaneous first requests may both be charged. Document retries use Idempotency-Key instead.
Response
{
"schemaVersion": "redline.html.v1",
"cssUrl": "https://dev.redline.bookcicle.com/styles/v1/redline.css",
"html": "<p>The <ins>quick </ins>fox jumped over the <ins>old </ins>fence.</p>",
"requestedMode": "auto",
"effectiveMode": "word",
"format": "html",
"engine": "dom",
"fallback": false,
"units": 1,
"usage": {
"used": 142,
"allowance": 25000,
"remaining": 24858
}
}
Structured JSON Contract
Changes are anchored to the comparison’s HTML representation for every input format. The engine collects changes before serializing HTML; text fallback uses its diff segments.
Request ["html", "json"] for rendering and structured changes. JSON-only output still refers to the HTML comparison representation. Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL.
What locations refer to
Locations describe normalized HTML, not source coordinates. They do not provide PDF page numbers or bounding boxes, Word revision IDs, JSON Pointer paths, byte offsets, or table-cell coordinates.
Structured change fields
| Field | Type / presence | Meaning |
id | string; always | Document-order change ID, such as chg_000001. Stable across pages of the same comparison. They are not HTML element IDs, DOM selectors, or identifiers guaranteed to survive a new comparison. |
type | string; always | insert, delete, replace, format, link, block_insert, block_delete. Adjacent deletion/insertion groups can form a replacement. |
location.blockIndex | integer; always | Block counter in traversal of the HTML comparison representation. DOM blocks start at 1; 0 can indicate a change outside a recognized block. Not a source paragraph number or character offset. |
location.blockType | string; optional | HTML block type, such as p or h1. Unavailable in text fallback. |
location.headingPath / sectionTitle | array of strings / string; optional | Surrounding headings and nearest heading title in the HTML representation. Empty context is omitted. |
before / after | object; conditional | Insertions omit before; deletions omit after. |
before.text / after.text | string; on present fragments | Changed text, usable by agents without parsing HTML. |
before.html / after.html | string; on present fragments | HTML of the changed content, not a full source document. |
before.format / after.format | object; optional | Semantic or presentation formatting when available. |
before.href / after.href | string; optional | Link destination when available; omitted in text fallback. |
Use fragment text and heading context to summarize edits. Request HTML too when rendering a redline. Applying changes to the original file requires your own source mapping.
Structured JSON page fields
| Field | Type / presence | Meaning |
schemaVersion | string; always | "1"; independent of the outer API schema version. |
comparisonId | string; optional | Stored comparison identifier. |
format / engine / fallback / attributeMode | metadata; always | Input format and engine behavior. Locations still refer to HTML representation. |
summary | object; always | Whole-comparison counts: changeCount, insertions, deletions, replacements, formatChanges, linkChanges, blockInsertions, blockDeletions. |
changes | array; always | Changes on the current page. |
totalChanges | integer; always | Number of changes in the complete comparison. |
offset / limit | integer; always | Zero-based page offset; default limit 100, maximum 500. |
nextOffset | integer; optional | Next page offset; omitted when there are no further changes. |
Example: both outputs (response excerpt)
{
"cssUrl": "https://dev.redline.bookcicle.com/styles/v1/redline.css",
"html": "<h1>Terms</h1><p>Pay within <del>30</del><ins>45</ins> days.</p>",
"json": {
"schemaVersion": "1",
"comparisonId": "cmp_example",
"format": "html",
"engine": "dom",
"fallback": false,
"attributeMode": "semantic",
"summary": {
"changeCount": 1,
"insertions": 0,
"deletions": 0,
"replacements": 1,
"formatChanges": 0,
"linkChanges": 0,
"blockInsertions": 0,
"blockDeletions": 0
},
"changes": [{
"id": "chg_000001",
"type": "replace",
"location": {
"blockIndex": 2,
"blockType": "p",
"headingPath": ["Terms"],
"sectionTitle": "Terms"
},
"before": { "text": "30", "html": "30" },
"after": { "text": "45", "html": "45" }
}],
"totalChanges": 1,
"offset": 0,
"limit": 100
},
"changesUrl": "https://api.bookcicle.com/v1/redline/comparisons/cmp_example/changes?token=EXAMPLE_PRIVATE_TOKEN"
}
Metadata, fallback, and pagination
Text fallback omits heading, block-type, formatting, and link context. Use before.text and after.text.
Follow nextOffset by GETting changesUrl with &offset=<nextOffset>&limit=100. Keep its private token. No nextOffset means the last page; the summary covers the whole comparison.
DOCX/PDF return this schema in content_complete.contentDiff.json, with contentDiff.changesUrl. Word Track Changes uses separate revision IDs and counts.
Documents API (DOCX & PDF Early Access)
Direct small-document input
For DOCX or PDF pairs up to 750 KiB combined file bytes, send both files as base64 in the comparison request. This skips upload authorization and file transfer. Larger pairs use upload IDs. Both sources must use the same input method.
{
"format": "pdf",
"original": { "dataBase64": "<base64 original file bytes>", "contentType": "application/pdf" },
"revised": { "dataBase64": "<base64 revised file bytes>", "contentType": "application/pdf" },
"mode": "auto",
"output": ["html", "json"],
"metrics": true
}
Word exports require authentication. Anonymous callers may request an HTML comparison within quota. If an anonymous request also asks for track_changes, the stream emits track_changes_failed with code export_authentication_required, then continues the HTML comparison and normal completion without an export job. Supply an authenticated Bearer token to create an export or retrieve its download URL. Replaying a cached anonymous comparison does not restore export access.
Upload two DOCX files or two searchable PDFs, then stream progress with SSE. Request HTML, JSON, or both; JSON changes use the same native comparison as the HTML response. Native Word Track Changes is default off and DOCX-only. Output fields and options are listed below.
Document comparison request
| Field | Type | Required / default | Meaning |
format | string | Required | docx or pdf; both files must use the same format. |
original.uploadId / revised.uploadId | string | Required for uploaded inputs | Upload IDs from the upload endpoint. Legacy originalUploadId / revisedUploadId aliases are also accepted. |
original.dataBase64 / revised.dataBase64 | string | Required for direct inputs | Base64 raw file bytes; use both sources together instead of upload IDs. Public limit: 750 KiB combined raw bytes. |
original.contentType / revised.contentType | string | Optional for direct inputs | Declared MIME type must match the selected document format. |
mode | string | "auto" | auto / word / block for the HTML comparison engine. |
attributeMode | string | "semantic" | semantic / presentation for HTML attribute comparison. |
output | array of strings | ["html"] | ["html"], ["json"], ["html", "json"]. Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL (contentDiff.comparisonId, contentDiff.hostedUrl; alias: viewer). Native Word Track Changes is default off; pass "track_changes" in the output array to queue background generation of a .docx file with native Word tracked revisions. |
pageRendering | boolean | false | Return source-page PDFs after the text diff. Applies to both formats; omission and false skip all page rendering. |
metrics | boolean | false | Set true to include server processing metrics in content_complete and complete, including totalMs and downloadMs. |
title | string | Optional | Display title for stored comparisons. |
Document content_complete event
| Field | Type / presence | Meaning |
requestId / format | string; always | Request identifier and source document format. |
units | integer; always | Comparison’s metered units. |
contentDiff.html | string; HTML requested | HTML rendered from the semantic document diff. |
contentDiff.cssUrl | URL string; always | Same rendering contract as text API cssUrl; current environment’s public v1 CSS. |
contentDiff.json | object; JSON requested | Same structured JSON page schema as the text API. |
contentDiff.changesUrl | URL string; JSON requested | Private pagination URL; relative paths resolve against the API origin. |
contentDiff.engine / fallback | string / boolean; always | Comparison engine and fallback status. |
contentDiff.comparisonId / hostedUrl | string / URL; stored output | Stored comparison identifier and private retrieval URL. hostedUrl retrieves JSON containing HTML; it is not a raw HTML page. |
contentDiff.earlyAccess | boolean; optional | Early Access status when supplied. |
Why the HTML redline and Word Track Changes can differ
The semantic document diff supplies HTML; Word exports compare the original DOCX pair independently.
Grouping, counts, and revision IDs may differ. mode does not configure the Word Track Changes comparator. Review each output against the source files.
Rendered source pages
Set pageRendering: true for page previews. The stream sends content_complete first, then page_rendering_complete with private PDF URLs, each side’s page count, the renderer and renderMs. URLs expire after 15 minutes; stored artifacts expire after 24 hours.
event: page_rendering_complete
data: {"schemaVersion":"redline.pages.v1","renderer":"<renderer identifier>","renderMs":210,"pages":[{"side":"original","pdfUrl":"<private URL>","pageCount":2,"expiresInSeconds":900},{"side":"revised","pdfUrl":"<private URL>","pageCount":3,"expiresInSeconds":900}]}
PDF previews preserve source bytes, page sizes and rotation. DOCX preview layout depends on available fonts; exact Word pagination is not guaranteed. The UI displays one page per side and highlights unambiguous text changes. The text comparison contains every supported change, including repeated text and style edits.
Review actions: Rendered pages are read-only. Use the interactive HTML review viewer for Accept/Reject, or review native DOCX Track Changes in Word. Review decisions do not update the source-page previews.
page_rendering_failed leaves the successful text diff available. Rendering is bounded to 1,000 pages, 32 MiB per PDF and 30 seconds of DOCX layout per side, within a 55-second request budget. Document comparison screens enable pages; the playground starts off and offers a checkbox.
Stream completion: Require both content_complete and complete; HTTP 200 only opens the stream. error means comparison failure. track_changes_failed and page_rendering_failed affect only their optional outputs.
Metrics: Use content_complete.metrics.totalMs for the semantic result and complete.metrics.totalMs for all requested work. comparisonMs measures server time through the semantic result and stays unchanged in final completion. pageRenderingMs measures optional page preparation and storage, including failed attempts; it is zero before rendering or when disabled. diffMs measures only the diff engine. renderMs in a successful page event measures page preparation and storage. Direct inputs report downloadMs: 0; upload transfer and browser painting are excluded.
Upload and stream example
- For the upload path, presign and upload both documents via
POST /v1/redline/uploads.
- Stream comparison via SSE with upload references:
POST /v1/redline/documents
Authorization: Bearer bc_live_...
Content-Type: application/json
Accept: text/event-stream
{
"format": "docx",
"original": {
"uploadId": "upl_orig_123"
},
"revised": {
"uploadId": "upl_rev_456"
},
"mode": "auto",
"output": ["html", "track_changes"]
}
- Receive ordered real-time SSE progress events with self-contained job polling URLs:
event: accepted
data: {"requestId":"req_abc"}
event: validating
data: {"format":"docx"}
event: converting
data: {"side":"original"}
event: converting
data: {"side":"revised"}
event: comparing
data: {"mode":"auto"}
event: content_complete
data: {"requestId":"req_abc","format":"docx","contentDiff":{"html":"...","engine":"document","fallback":false},"units":2}
event: track_changes_queued
data: {"jobId":"job_789","status":"queued","pollUrl":"https://api.bookcicle.com/v1/redline/jobs/job_789","artifactType":"track_changes_docx"}
event: complete
data: {"requestId":"req_abc","status":"complete","jobs":[{"type":"track_changes_docx","jobId":"job_789","pollUrl":"https://api.bookcicle.com/v1/redline/jobs/job_789"}],"jobId":"job_789","units":2}
Large File Uploads
For text formats, the inline limit is 800 KiB (819200 bytes) for the complete UTF-8 JSON request, including escaping, field names, and options. Measure the serialized request, not just the document text. Larger comparisons use uploads.
- Request
POST /v1/redline/uploads for each input with filename, contentType, and its UTF-8 sizeBytes. Text inputs use text/plain; charset=utf-8. Each response returns uploadId and uploadUrl.
- PUT the raw input bytes to each
uploadUrl, using the same content type. Limits are 5 MiB per anonymous input and 10 MiB per authenticated input. Use the same identity to authorize uploads and compare them.
- For HTML, Markdown, JSON, or plain text, call
POST /v1/redline/html with upload references. Keep the same mode, format, and output options. DOCX and PDF continue to use POST /v1/redline/documents.
{
"original": { "uploadId": "upl_12345678-1234-4234-9234-123456789abc" },
"revised": { "uploadId": "upl_87654321-4321-4321-9321-abcdef123456" },
"mode": "word",
"format": "html",
"output": ["html"]
}
For text formats, check the complete serialized request against the 800 KiB limit. For PDF/DOCX, choose direct input up to 750 KiB combined raw file bytes and uploads above that threshold. Upload IDs belong to the identity that created them; they are not arbitrary storage paths.
Hosted Viewer & Embeds
Redline can generate a hosted, access-controlled text comparison viewer or provide responsive iframe embed codes without needing to build custom review UI in your application.
Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL:
<iframe
src="https://redline.bookcicle.com/embed/cmp_9f82d1?token=cmp_tok_abc123&theme=system&collapse=true"
width="100%"
height="600"
loading="lazy"
style="border:1px solid #e2e8f0; border-radius:8px;"
></iframe>
Supported iframe query parameters
| Parameter | Description |
theme=system|light|dark | Forces color scheme. |
view=unified|split | Sets initial layout. |
collapse=true|false | Collapses or expands long unchanged text runs (defaults to true). |
controls=false | Suppresses viewer top navigation chrome. |
bg=transparent | Removes background fill for seamless container embedding. |
review=true|false | Enables interactive track changes review with inline Accept/Reject buttons and progress tracking (Creator & Creator Pro). |
Interactive Track Changes Review Mode (Creator & Creator Pro)
Redline enables interactive change-by-change review inside embedded viewers. End-users can accept or reject individual insertions and deletions or bulk-resolve changes using Accept All and Reject All. Every action dispatches bidirectional window.postMessage events to your host application.
Unified Layout Lock: When Review Mode is active (review=true or via redline:startReview), the viewer is automatically locked into Unified layout. The Split layout option is explicitly disabled in the UI and via postMessage commands during active review to preserve synchronous inline Accept/Reject action buttons, change boundaries, and DOM hierarchy. Split mode re-enables once review is completed or exited.
Review Completion & Clean Block Pruning: When resolving changes, rejected insertions (or accepted deletions) that span entire list items (<li>) or paragraphs (<p>) automatically prune empty container nodes so no orphaned bullets or blank lines remain. Once all changes are reviewed one-by-one, Accept All and Reject All are dismissed leaving Finish Review as the sole action, alongside a floating bottom-right completion prompt ensuring users never miss the final export step.
Bidirectional PostMessage Protocol
Listen for review callbacks in your host container:
window.addEventListener("message", (event) => {
const { type, ...payload } = event.data || {};
if (type === "redline:changeResolved") {
// Fired whenever an individual change is accepted or rejected
console.log(`Change ${payload.changeId} ${payload.action}ed. Remaining: ${payload.remaining} of ${payload.total}`);
} else if (type === "redline:reviewComplete") {
// Fired when all changes are resolved or Finish Review is clicked
console.log("Review complete! Final resolved HTML:", payload.resolvedHtml);
console.log("Accepted changes:", payload.accepted);
console.log("Rejected changes:", payload.rejected);
}
});
Host applications can also programmatically resolve changes and manage viewer layout:
// Start review mode
iframe.contentWindow.postMessage({ type: "redline:startReview" }, "https://redline.bookcicle.com");
// Accept or reject a specific change ID
iframe.contentWindow.postMessage({ type: "redline:acceptChange", changeId: "chg-1" }, "*");
iframe.contentWindow.postMessage({ type: "redline:rejectChange", changeId: "chg-2" }, "*");
// Bulk resolve remaining changes
iframe.contentWindow.postMessage({ type: "redline:acceptAll" }, "*");
iframe.contentWindow.postMessage({ type: "redline:rejectAll" }, "*");
// Query live review state
iframe.contentWindow.postMessage({ type: "redline:requestState" }, "*");
// Change viewer appearance
iframe.contentWindow.postMessage({ type: "redline:setView", view: "split" }, "*");
iframe.contentWindow.postMessage({ type: "redline:setTheme", theme: "dark" }, "*");
iframe.contentWindow.postMessage({ type: "redline:setCollapse", collapsed: false }, "*");
Drop-in Redline Embed SDK (/scripts/v1/redline.js)
New subscriber-created private viewer links allow guest browser review without a reviewer account. The service supplies the review entitlement after validating the viewer link.
Review choices stay in the browser. The completion callback provides resolved HTML for your application to save or process. It does not save a Redline draft or change Word downloads or page previews.
Rather than writing raw <iframe> tags and manual postMessage listeners, include Redline's lightweight SDK script:
<script src="https://redline.bookcicle.com/scripts/v1/redline.js"></script>
<div id="redline-mount" style="min-height: 560px;"></div>
<script>
const viewer = Redline.embed({
container: "#redline-mount",
comparisonId: "cmp_demo_marketing",
review: true, // Enable interactive track changes review
theme: "system", // "system" | "light" | "dark"
view: "unified", // "unified" | "split"
onChangeResolved: (evt) => {
console.log(`Change ${evt.changeId} ${evt.action}ed (${evt.remaining} remaining)`);
},
onComplete: (evt) => {
console.log("Final approved document HTML:", evt.resolvedHtml);
// Persist or commit the approved document to your database
}
});
// Programmatic controls:
// viewer.accept("chg-1");
// viewer.reject("chg-2");
// viewer.acceptAll();
// viewer.setTheme("dark");
// viewer.destroy();
</script>
Hosted Embed Availability & Expiration (Adjustable TTL)
Pass "hosted_viewer" in the output array to receive a secure embed token and hosted comparison URL. Hosted viewer tokens and stored comparison files are intentionally ephemeral, designed for interactive diff review, approval gates, and PR previews. You can customize the availability window using the hostedViewerTtl parameter:
Hosted Embed Availability & Lifecycle Limits
| Property | Specification | Details |
token_ttl | 24h default (up to 72h) | The signed embed token (cmp_tok_<timestamp>_<signature>) expires 24 hours after issuance by default, or after your selected TTL. Requests beyond this duration return token_expired. |
creator_ttl | 24h, 32h options | Creator accounts can configure hostedViewerTtl to "24h" (default) or "32h". |
creator_pro_ttl | 24h, 32h, 48h, 72h options | Creator Pro accounts can configure hostedViewerTtl to "24h" (default), "32h", "48h", or "72h" for extended multi-day review workflows. |
storage_lifecycle | Matches token TTL | Stored HTML diffs and JSON changes remain accessible for the duration of the token TTL, scheduled for automated expiration cleanup thereafter. |
expiration_ui | Inline expired state | Expired comparison links return HTTP 404 and present a clear notice: Hosted viewer tokens expire after the configured TTL. Stored comparisons are scheduled to expire after the retention period. |
clock_skew | 60 seconds tolerance | Tokens timestamped more than 60 seconds into the future are rejected (token_issued_in_future) to prevent forged timestamps. |
demo_comparisons | Permanent / No expiration | Public sample comparisons (demo, sample, cmp_demo*) bypass token verification and never expire, useful for dev testing. |
Requesting a Hosted Comparison with Custom TTL
Pass output: ["html", "hosted_viewer"] and optional hostedViewerTtl (e.g. "32h" for Creator, or up to "72h" for Creator Pro):
// Request a hosted comparison with extended 48-hour availability (Creator Pro)
const response = await fetch("https://api.bookcicle.com/v1/redline/html", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.REDLINE_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
original: originalHtml,
revised: revisedHtml,
output: ["html", "hosted_viewer"],
hostedViewerTtl: "48h" // "24h" (default), "32h" (Creator); "48h", "72h" (Creator Pro)
})
});
const data = await response.json();
// data returns:
// - data.comparisonId: "cmp_a1b2c3d4e5f6..."
// - data.viewerToken: "cmp_tok_1740000000_abc123..." (HMAC-SHA256, active for 48h)
// - data.embedUrl: "https://redline.bookcicle.com/embed/cmp_...?token=cmp_tok_..."
// - data.viewerUrl: "https://redline.bookcicle.com/view/cmp_...?token=cmp_tok_..."
Long-Term Retention & Re-Hosting Strategies
- Permanent Archival: If your application needs long-term or audit-ready diff retention, store the returned
html fragment and structured json changes directly in your own database or object storage. Pair with Redline’s public, versioned stylesheet (https://redline.bookcicle.com/styles/v1/redline.css).
- Re-issuing Embed Links: To generate a new 24-hour viewer link for a past comparison, simply re-post the original and revised documents or upload IDs with
output: ["html", "hosted_viewer"]. Redline’s sub-100ms comparison engine generates a fresh 24-hour token instantly.
CSS Styling Contract
Redline HTML returns presentation-neutral markup with semantic tags. To use our default theme, link the stylesheet:
<link rel="stylesheet" href="https://redline.bookcicle.com/styles/v1/redline.css">
Load the returned cssUrl once in your rendering document. Document SSE uses contentDiff.cssUrl; the field is also present for JSON-only results.
Render the HTML fragment in an isolated preview. HTML fragments do not contain a stylesheet link; hosted viewers already load it.
The stylesheet is public and environment-specific. Viewer and artifact URLs are private; keep their returned tokens.
Or override specific selectors:
ins { background: #dcfce7; color: #166534; text-decoration: none; }
del { background: #fee2e2; color: #991b1b; text-decoration: line-through; }
Units & Quota Calculation
Text usage is metered by UTF-8 input bytes; PDF/DOCX usage counts raw file bytes:
units = ceil((original_bytes + revised_bytes) / 102400)
1 unit = 100 KiB combined raw input bytes; base64 encoding overhead is not billed. Direct PDF/DOCX pairs support 750 KiB combined raw file bytes; text-format inline requests support 800 KiB serialized JSON. Larger inputs use uploads. Document comparisons remain capped at 10 MiB combined; each anonymous file is capped at 5 MiB.
Error Codes
| Code | HTTP | Description |
export_authentication_required | 401 on export and job endpoints | Supply a valid authenticated Bearer token to export. In a document comparison stream, this code appears in a nonfatal track_changes_failed event; the HTML result and normal completion remain available within quota. |
unauthorized | 401 | Missing or invalid authentication. |
invalid_api_key | 401 | Key does not exist or has invalid prefix. |
api_key_revoked | 403 | Key has been explicitly revoked. |
quota_exceeded | 402 | Monthly plan allowance depleted. |
rate_limited | 429 | Requests per minute limit exceeded. |
inline_limit_exceeded | 413 | JSON request > 819200 bytes. Upload inputs and send upload IDs. |
document_too_large | 413 | Document exceeds 10 MiB maximum. |
document_too_complex | 422 | DOM tag or block density guard triggered. |
Troubleshooting payload signatures: If a direct request over 1 MiB fails with an authorization or signature mismatch error, ensure the x-amz-content-sha256 header contains the lowercase hex SHA-256 digest computed over the exact UTF-8 request body bytes.
Rate Limits
API requests are rate-limited based on your active plan tier:
| Plan | Rate Limit | Max Document Size |
| Basic | 30 requests / minute | 800 KiB JSON |
| Creator | 120 requests / minute | 5 MiB |
| Creator Pro | 300 requests / minute | 10 MiB |
When exceeded, endpoints return HTTP 429 Too Many Requests. Rate-limit headers vary by endpoint; anonymous viewer and job requests can be limited before authentication errors are returned. Honor Retry-After when supplied. If absent, use bounded backoff; do not depend on every response containing x-ratelimit-limit, x-ratelimit-remaining, or x-ratelimit-reset.