When the Hosted Web Report and the Open-Source Web Report don’t fit your product, you can build your own report from the raw scan data. Copyleaks gives you the full text of the scanned document, the text of every matched source, and the exact positions of each match, so you can draw the highlights and the side-by-side comparison in your own interface. This guide picks up after a scan has completed. It walks through collecting the results, exporting the data, and turning match positions into highlights.

Get started

1

Before you begin

Before you begin, ensure you have:
  • A scan submitted with a status webhook, as shown in the Detect Plagiarism in Text guide. That guide also covers installing an SDK and logging in.
  • Public endpoints on your server (or signed storage URLs) that Copyleaks can upload the exported files to.
Some data is only produced when you ask for it in the scan submission:
  • properties.pdf.create: true if you want to offer the PDF report.
  • properties.aiGeneratedText.detect: true if you want to show AI Text Detection results.
  • properties.includeHtml: true if you want to render the HTML version of the document. This guide uses the text version, which every scan includes.
2

Read the completed webhook

When the scan finishes, Copyleaks sends the completed webhook to your status URL. It is the summary of your report: it lists every matched source, grouped by where it was found, and the totals for the whole document.
  • results.internet, results.database (the Shared Data Hub), results.batch, results.repositories (your Private Cloud Hubs) and results.internalAIData each hold a list of matched sources. Use title, introduction, matchedWords and, for internet results, url to build the list of sources in your report.
  • Keep the id of every result. You need it to export that result’s full text and match positions.
  • results.score holds the totals for the document: the number of identical, minor-change and related-meaning (paraphrased) words, and the aggregatedScore.
  • notifications.alerts lists any scan alerts you may want to show alongside the report.
3

Export the results and the crawled version

The completed webhook only summarizes the matches. To draw highlights you need two kinds of files, which you get from the Export endpoint:
  • The crawled version: the text of the scanned document, as Copyleaks read it.
  • One result per matched source: the source’s text and the positions of every match.
Request them all in one export call. Choose a new exportId for each export, and pass up to 1,000 result IDs from the completed webhook.
4

Receive the exported files

Copyleaks uploads each file to the endpoint you gave it, using the verb you chose. The crawled version and the results are JSON. Store them, keyed by scan ID and result ID, so your report can load them later.When every upload is done, Copyleaks calls your completionWebhook with the Export Completed payload. Check that completed is true and that every entry in tasks has isHealthy: true before you show the report. If a task failed, run a new export with a new exportId for the missing files.
5

Map match positions to highlights

Each result holds the matched source’s text in text.value, and the match positions in text.comparison. Matches are split into three types, from strongest to weakest:Every type has two sides, and each side lists the matches as parallel starts and lengths arrays:
  • source positions point into the scanned document: the crawled version’s text.value.
  • suspected positions point into the matched source: this result’s own text.value.
chars holds character positions, which you use to slice the text. words holds the same matches as word positions.Character positions count UTF-16 code units, the way JavaScript, Java and C# index strings. A character outside the Basic Multilingual Plane, such as an emoji or a math letter like 𝑥, counts as two. Python indexes strings by code point instead, so in Python, slice the UTF-16 encoding of the text, as the example below does. Otherwise every highlight after such a character lands in the wrong place.
The nth match runs from starts[n] for lengths[n] characters. Here, characters 0 to 19 of the scanned document match characters 13 to 32 of the matched source.The functions below collect the ranges for one side of a result and split a text into plain and highlighted segments. Several results can match the same passage of the scanned document, so where ranges overlap, the strongest type wins.
Use them for the two views most reports have:
  • Document view: collect the source ranges of every result and apply them to the crawled version’s text.value. This shows every matched passage in the scanned document.
  • Comparison view: for one result, apply its source ranges to the crawled version’s text.value and its suspected ranges to the result’s own text.value, and show the two side by side.
The text comes from the scanned document and from third-party sources, so treat it as untrusted. In a browser, build the highlights with textContent instead of inserting HTML:
Render in the browser
The crawled version also lists the parts of the document that were excluded from the scan in text.exclude, for example quotes or references excluded by your properties.exclude settings, and the page boundaries in text.pages.startPosition. You can use them the same way, for example to grey out excluded text or to split the document into pages. See the Crawled Version reference.
6

Add AI Text Detection results (optional)

If the scan was submitted with properties.aiGeneratedText.detect: true, add an aiDetection item to the same export request:
With the Java SDK, call exportModel.setAiDetection(new ExportAiDetection(endpoint, "POST", null)). The Python and JavaScript SDKs have no setter for it, so set the field on the export model before you call export: export.aiDetection = {"endpoint": endpoint, "verb": "POST"} in Python, or exportModel.aiDetection = { endpoint, verb: 'POST' } in JavaScript. Both SDKs send it with the rest of the model.The uploaded file is the AI Text Detection Result. Its positions point into the same crawled version text.value and also count UTF-16 code units:
  • results splits the text into sections. A classification of 2 marks text flagged as AI-generated, and 1 marks human-written text. Each section’s matches[].text.chars holds its starts and lengths.
  • summary.ai is the AI score: the share of the text’s words flagged as AI-generated, from 0 to 1. It is not a probability.
Collect the flagged ranges and pass them to the same toSegments function, as their own layer:
7

Add the PDF report (optional)

If the scan was submitted with properties.pdf.create: true, you can offer the PDF report as a download next to your own report. Add a pdfReport item to the export request:
The file is uploaded with the application/pdf content type. An export that asks for pdfReport or aiDetection without the matching submit property is rejected with 400 Bad Request.
8

Summary

You now have everything a custom report needs: the list of sources and the totals from the completed webhook, the document text from the crawled version, and the match positions from each result. Your report shows reviewers where the matches and flagged passages are, so they can examine the evidence themselves.

Next steps

Export

See every artifact you can export and its options.

New Plagiarism Result

Review the full structure of an exported result.

Crawled Version

Review the full structure of the scanned document’s text.

Install Open-Source Web Report

Prefer a ready-made interface you can theme? Use the Open-Source Web Report.

Support

Should you require any assistance or have inquiries, contact Copyleaks Support or ask a question on Stack Overflow with the copyleaks-api tag.