Blackoutly API — integration with other systems
Blackoutly Enterprise. A guide for the integrator or administrator connecting Blackoutly to another system of the organisation: document management, case management, archive, e-delivery or your own scripts. Below, the external system is any such system.
1. How the API works
The Blackoutly API is a command line with a JSON result, not a network service. There is no HTTP / REST API — on purpose. The program never connects to the network for documents and listens on no port (the principle: documents never leave the computer). Integration happens locally, through:
- the command line
BlackoutlyCli.exe— the external system (its service, a scheduled task, a script) starts it as a process; - files in folders — input documents, review packages (
*.bo-review.json), results (*_bo_*.pdf); - JSON output (
--json) and exit codes — the external system learns what succeeded.
A person always confirms (the program's principle). So what is automated is not everything, but what is around the person:
prepare saves time (the suggestions are ready in advance), apply saves only what a second person approved.
Without an external system the same is done in the program's window (B may also save it themselves).
2. Requirements
-
Blackoutly 2026.1.0 or newer, installed on the computer or server where the integration runs (
C:\Program Files\Blackoutly\BlackoutlyCli.exe). -
A Blackoutly Enterprise license code, activated on that computer (policy
LicenseKeyin HKLM — it works for a service account too; activated once over the internet, or without internet with an activation file — see the Intune guide). Without it the commands return exit code 3. -
A shared review package folder that both the CLI and the employees see: policy
ReviewFolder(e.g.\\server\blackoutly\review) or the parameter--review-dir. If not set, the package is next to the original. -
For the service account — rights to read the input folder and to write to the review and result folders and the audit log.
- The CLI uses the same policies as the window (profile, rules file, categories, audit, SIEM).
3. Commands
BlackoutlyCli.exe prepare <files or folders>… [--review-dir FOLDER] [--json] [--no-ocr] [--force]
BlackoutlyCli.exe apply <files or folders>… [--review-dir FOLDER] [-o RESULT_FOLDER] [--json] [--no-ocr]
| Parameter | Meaning |
|---|---|
<files or folders> |
PDF, JPG, TIFF files; a folder — all such files in it (not its subfolders, not *_bo_* results) |
--review-dir |
The review package folder (default: policy ReviewFolder, otherwise next to the file) |
-o, --output-dir |
Where to save the results (default: policy DefaultOutputDir, otherwise next to the original) |
--json |
The result as a JSON array on standard output (without it — lines for a person) |
--no-ocr |
Do not recognise scans (faster; scanned pages get no suggestions) |
--force |
prepare: overwrite a package a person is already reviewing or has approved (normally do not use) |
prepare — prepare for review
Opens the files, finds the suggestions (for all the files together — a surname found in one is looked for in all) and
creates a review package with status draft for each. An employee who opens that file in the program sees the
suggestions; once submitted — submitted. prepare does not touch a file a person is already reviewing
(submitted / approved).
At most 50 files at a time (the Enterprise limit; the others are skipped with a warning — run again), each up to 500 pages.
apply — save the approved ones
For each file, looks for a package with the same SHA-256 and status approved, prepared and approved by different
people. Then: rotates the pages as in the review, blacks out the approved places (with the legal grounds), creates a
new PDF, checks the result (if the check finds problems the file is not saved, status blocked), writes an audit
record (interface: cli, prepared_by, approved_by) and removes the package. The original is never changed.
apply is not "save anyway" — check failures cannot be bypassed.
4. Result
Exit codes
| Code | prepare |
apply |
|---|---|---|
| 0 | all files prepared or skipped | all files saved |
| 2 | at least one file with an error | at least one not saved (not approved, stopped by the check, an error) or no files found |
| 3 | not an Enterprise license | not an Enterprise license |
| 1 | an unexpected program error (the text — on standard error) |
Code 2 is not a failure: the JSON shows which files to process later.
JSON (--json)
An array, one object per file. The field detail is an explanation for a person (in Lithuanian) — do not parse it,
use status.
prepare:
[
{"file": "D:\\Integration\\inbox\\claim.pdf", "status": "prepared",
"package": "\\\\server\\blackoutly\\review\\4f2a9c0e1b7d3a55.bo-review.json", "suggestions": 14},
{"file": "D:\\Integration\\inbox\\contract.pdf", "status": "skipped",
"detail": "jau peržiūrimas ar patvirtintas (--force — perrašyti)"},
{"file": "D:\\Integration\\inbox\\damaged.pdf", "status": "error", "detail": "…"}
]
status |
Meaning |
|---|---|
prepared |
The package was created (package), with suggestions suggestions |
skipped |
A person is already reviewing or has approved the file — nothing done |
error |
The file could not be opened (password, damaged, too many pages) |
apply:
[
{"file": "D:\\Integration\\inbox\\claim.pdf", "status": "saved",
"output": "D:\\Integration\\outbox\\claim_bo_20261018_142301.pdf", "redactions": 16,
"output_sha256": "9b1c…"},
{"file": "D:\\Integration\\inbox\\contract.pdf", "status": "not_approved", "detail": "būsena: submitted"},
{"file": "D:\\Integration\\inbox\\complaint.pdf", "status": "blocked", "detail": "Paslėpta reikšmė vis dar matoma 2 puslapyje."}
]
status |
Meaning | What the external system does |
|---|---|---|
saved |
The result output, checked |
Take the result, mark the case as anonymised |
not_approved |
No approved package (still under review), or the same person prepared and approved it | Try again later |
blocked |
The check found a hidden value still there — the file was not saved | Return it to the employee to fix |
error |
The file could not be opened | Tell the administrator |
output_sha256 matches the audit log record — the external system's record can be linked to the audit without keeping file names.
5. Examples
5.1. PowerShell: a scheduled task every 10 minutes
# C:\Scripts\blackoutly-api.ps1 — run by Task Scheduler as the service account
$Cli = "C:\Program Files\Blackoutly\BlackoutlyCli.exe"
$Inbox = "\\server\blackoutly\inbox" # the external system puts documents to anonymise here
$Review = "\\server\blackoutly\review" # review packages (the same as policy ReviewFolder)
$Outbox = "\\server\blackoutly\outbox" # results for the external system
$Done = "\\server\blackoutly\done" # processed originals
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
# 1) new files -> suggestions for the employees
$prepared = & $Cli prepare $Inbox --review-dir $Review --json | ConvertFrom-Json
if ($LASTEXITCODE -eq 3) { throw "A Blackoutly Enterprise license is needed" }
$prepared | Where-Object status -eq "error" | ForEach-Object { Write-Warning "$($_.file): $($_.detail)" }
# 2) approved files -> results
$applied = & $Cli apply $Inbox --review-dir $Review -o $Outbox --json | ConvertFrom-Json
foreach ($r in $applied) {
switch ($r.status) {
"saved" { Move-Item $r.file $Done -Force; Write-Host "Anonymised: $($r.output)" }
"blocked" { Write-Warning "Stopped by the check: $($r.file) — return it to the employee" }
default { } # not_approved: still under review, next time
}
}
The task: Task Scheduler → Create Task → run powershell.exe -NoProfile -ExecutionPolicy Bypass -File
C:\Scripts\blackoutly-api.ps1 every 10 min., as the service account, "Run whether user is logged on or not".
So that two runs never overlap — Settings → If the task is already running: Do not start a new instance.
5.2. Python
import json
import subprocess
CLI = r"C:\Program Files\Blackoutly\BlackoutlyCli.exe"
def blackoutly(command: str, *paths: str, review_dir: str, output_dir: str | None = None) -> list[dict]:
args = [CLI, command, *paths, "--review-dir", review_dir, "--json"]
if output_dir:
args += ["-o", output_dir]
proc = subprocess.run(args, capture_output=True, encoding="utf-8", timeout=3600)
if proc.returncode == 3:
raise RuntimeError("No Blackoutly Enterprise license")
if proc.returncode not in (0, 2):
raise RuntimeError(proc.stderr)
return json.loads(proc.stdout)
for row in blackoutly("apply", r"D:\Integration\inbox", review_dir=r"\\srv\review", output_dir=r"D:\Integration\outbox"):
if row["status"] == "saved":
print("OK", row["output"], row["output_sha256"])
5.3. C# (.NET)
using System.Diagnostics;
using System.Text.Json;
var psi = new ProcessStartInfo(@"C:\Program Files\Blackoutly\BlackoutlyCli.exe")
{
RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false,
StandardOutputEncoding = System.Text.Encoding.UTF8,
};
foreach (var a in new[] { "apply", @"D:\Integration\inbox", "--review-dir", @"\\srv\review", "-o", @"D:\Integration\outbox", "--json" })
psi.ArgumentList.Add(a);
using var p = Process.Start(psi)!;
string json = p.StandardOutput.ReadToEnd();
p.WaitForExit();
if (p.ExitCode == 3) throw new InvalidOperationException("A Blackoutly Enterprise license is needed");
foreach (var row in JsonDocument.Parse(json).RootElement.EnumerateArray())
if (row.GetProperty("status").GetString() == "saved")
Console.WriteLine(row.GetProperty("output").GetString());
5.4. One file from the command line (to try it)
"C:\Program Files\Blackoutly\BlackoutlyCli.exe" prepare D:\Test\claim.pdf
"C:\Program Files\Blackoutly\BlackoutlyCli.exe" apply D:\Test\claim.pdf -o D:\Test\results
6. The review package (*.bo-review.json)
The file name is the first 16 characters of the file's SHA-256: 4f2a9c0e1b7d3a55.bo-review.json. The package is bound
to the file's exact content: for a changed file (another SHA-256) the old package does not fit. The package holds
no document text — only the marks' places, so it can be kept in a shared folder.
{
"version": 1,
"path": "\\\\server\\blackoutly\\inbox\\claim.pdf",
"file_name": "claim.pdf",
"sha256": "4f2a9c0e1b7d3a55…",
"status": "approved",
"profile": "court",
"rotations": {"2": 90},
"marks": [
{"page": 0, "rects": [[72.0, 140.5, 188.2, 154.0]], "category": "name", "source": "auto",
"state": "accepted", "confidence": "high", "key": "0:112:129:name", "basis": null},
{"page": 1, "rects": [[60.0, 300.0, 300.0, 360.0]], "category": "manual", "source": "manual",
"state": "accepted", "confidence": null, "key": null, "basis": "gdpr"}
],
"seen": ["0:112:129:name"],
"prepared_by": {"user": "peter", "computer": "PC-104", "ts": "2026-10-18T10:02:11+03:00"},
"approved_by": {"user": "anna", "computer": "PC-117", "ts": "2026-10-18T13:45:40+03:00"},
"submitted_marks": [ … ]
}
| Field | Meaning |
|---|---|
status |
draft (prepared by prepare), submitted (submitted by an employee), approved (approved by another) |
rotations |
Page No. (from 0) → rotation in degrees clockwise |
marks[].rects |
Rectangles in points (1/72 inch), from the top left corner of the rotated page: x0, y0, x1, y1 |
marks[].state |
pending (not reviewed — will not be hidden), accepted (will be hidden), rejected |
marks[].category |
name, personal_code, address… or the organisation's org:<id>; manual — marked by hand |
marks[].basis |
The legal ground's ID from the rules file; null — by category, "-" — without a ground |
prepared_by, approved_by |
Windows user, computer, time |
submitted_marks |
The marks as A submitted them (as marks) — shows whether B changed them |
Do not create or change packages with your own system: the format may change in new versions (version), and an
approved_by changed by hand would bypass the four-eyes principle — a breach of the organisation's procedure (who
really saved it is always in the audit log). The external system may only read the marks (e.g. how many were
hidden); they are made through the program.
7. Security and tips
-
Protect the shared folders (
inbox,review,outbox) with NTFS rights: employees and the service account get only what they need. The review package holds no text, but the originals ininboxhold personal data. -
The result folder holds only anonymised files; originals are not copied there.
- The CLI writes to the audit log and SIEM too (when on) — API operations are seen just like work in the window.
-
Large batches: OCR is the slowest part (~2–10 s a page, depending on the computer). For scans use a server with more cores; for digital PDFs without scans
--no-ocris much faster. -
A watched folder (
WatchFolder) is an alternative without the CLI: the employee's program loads new files itself.
The other commands (info, suggest, redact) are for developers and diagnostics; do not use them for integration —
redact --auto saves without a person's confirmation.