Generate RPT-016 ZIP archives for regulator submission, and check them against their manifest.
RPT-016 is a ZIP archive produced for regulatory submission. It contains a cover PDF, one PDF per included report type, the machine-readable check record, and a manifest that records the SHA-256 hash and size of every other file in the archive.
Bundles are not signed. manifest.json carries signature: null, signatureAlgorithm: null and integrity: "sha256_hashes_only". Re-hashing the files shows whether any of them changed after the manifest was written; it does not show who wrote the manifest.
{bundleId}.zip
├── report.pdf # Bundle cover sheet (RPT-016)
├── reports/
│ └── RPT-001-{checkId}.pdf # One PDF per included report type
├── evidence.json # Machine-readable check record
└── manifest.json # SHA-256 of every file above (not signed)manifest.json is not listed in its own files[] array. includeTypes in evidence.json lists what the archive really contains; anything you requested that cannot be produced appears in unavailableReports, never as a placeholder document.
includeTypes is validated at registration. Naming a report type the platform does not produce returns 400 UNSUPPORTED_REPORT_TYPES with a reason per entry, so a bundle can never be registered promising a document that will be missing from the ZIP. Omit the field to let the bundle default to the report type for the check.
curl -X POST https://api.trustverify.co.uk/api/v1/reports/rpt-016 \
-H "Authorization: Bearer tv_live_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"checkId": "chk_01J8KX9QMN23PQRST",
"includeTypes": ["RPT-001"],
"regulatorRef": "FCA-2026-00142"
}'
# Response: { "bundleId": "bnd_abc123", "status": "generating", "includeTypes": ["RPT-001"], … }
# Then download the archive itself:
curl -H "Authorization: Bearer tv_live_sk_YOUR_KEY" \
-o bundle.zip https://api.trustverify.co.uk/api/v1/bundles/bnd_abc123.zipEach entry in manifest.files[] names a file in the archive with its size in bytes and its SHA-256. Hash every file and compare. A mismatch means that file changed after the manifest was written.
import crypto from 'crypto';
import fs from 'fs';
// Extract the bundle ZIP first, then run this in the extracted folder.
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
for (const f of manifest.files) {
const data = fs.readFileSync(f.name);
const sha256 = crypto.createHash('sha256').update(data).digest('hex');
if (sha256 !== f.sha256 || data.length !== f.bytes) {
throw new Error(`Changed since the manifest was written: ${f.name}`);
}
}
console.log('✓ Every file matches the manifest');A matching manifest shows the files have not changed since it was written, not who wrote it: anyone who edits a file can write a new manifest to match. Keep each bundle exactly as downloaded, with a record of when and from which account you downloaded it.