Manifests and integrity
A manifest describes every source document of a filing’s current version, with a SHA-256 hash and byte size for each. Verify the hashes to prove that what you stored is what VelociFile served.
Get a manifest
Section titled “Get a manifest”curl -sS --fail-with-body "https://api.dev.velocifile.ai/v1/filings/$VF_FILING_ID/versions/$VF_VERSION_ID/manifest" \ -H "Authorization: Bearer $VELOCIFILE_API_KEY"{ "schema_version": "1.0.0", "filing_id": "fil_ABCD-123456789", "filing_version_id": "fv_0a1b2c3d4e5f60718293a4b5c6d7e8f9", "completeness": "complete", "missing_documents": [], "rights": { "agreement_id": "agreement_ref", "permitted_uses": ["internal_analysis", "product_design", "extraction", "internal_storage"], "redistribution_permitted": false, "retain_until": null }, "documents": [ { "document_id": "doc_123", "filename": "rate-manual.pdf", "media_type": "application/pdf", "byte_size": 482113, "sha256": "24788f42af309e5c328101cad446208feed26e3e0caa736125fe364711e00192", "role": "rate_manual", "source_uri": "https://...", "observed_at": "2026-10-20T08:00:00Z" } ]}Document role is one of form, endorsement, rate_manual, underwriting_manual, supporting or
unknown. It comes from the SERFF schedule the document was filed under. Correspondence and anything
unrecognized is unknown.
rights comes from your agreement and states what you may do with the documents. Honor it.
Limits
Section titled “Limits”A manifest covers at most 200 documents. Each PDF is at most 100 MiB, a version at most 1 GiB, and the
manifest itself at most 1 MiB. Documents must be PDFs. A version over these limits answers
422 CAPABILITY_UNSUPPORTED with the reason.
Completeness
Section titled “Completeness”VelociFile judges completeness against the attachment list in the filing’s own SERFF record.
complete: every attachment the filing lists is in the manifest.partial: one or more listed attachments are not held.missing_documentsnames each one.completenessandmissing_documentsare fixed when the version’s first manifest is built. They are not recomputed later, even if the held attachments change afterwards.- If the stored filing record has no attachment list, the manifest is
partialwith one missing item saying the source attachment list is unavailable. VelociFile never claims completeness it cannot check.
Documents that a filing adopts by reference from another filing (for example an ISO manual) are outside this definition. See deviation 4.
Verify the manifest
Section titled “Verify the manifest”The manifest bytes are deterministic: two-space indent, UTF-8, one trailing newline. The version’s
manifest_sha256 is the SHA-256 of the exact response body. Hash the raw bytes you received, before
any JSON parsing.
Download and verify a PDF
Section titled “Download and verify a PDF”GET .../documents/{document_id}/download returns a presigned URL that expires 15 minutes after
issuance, the document’s sha256 and its byte_size. The response carries Cache-Control: no-store.
{ "url": "https://...", "expires_at": "2026-10-22T16:15:00Z", "sha256": "24788f42af309e5c328101cad446208feed26e3e0caa736125fe364711e00192", "byte_size": 482113}Request the URL, then fetch the PDF immediately. Do not store or share the URL. If the source object
changed since the manifest was built, the request answers 410 VERSION_SUPERSEDED: read the filing
again.
import hashlibimport os
import httpx
BASE = "https://api.dev.velocifile.ai/v1"headers = {"Authorization": f"Bearer {os.environ['VELOCIFILE_API_KEY']}"}filing_id = os.environ["VF_FILING_ID"]version_id = os.environ["VF_VERSION_ID"]version_path = f"{BASE}/filings/{filing_id}/versions/{version_id}"
# Fetch the manifest first: building it is what records manifest_sha256 on the version.manifest_response = httpx.get(f"{version_path}/manifest", headers=headers, timeout=60)manifest_response.raise_for_status()version = httpx.get(version_path, headers=headers, timeout=30)version.raise_for_status()
# 1. The manifest itself: hash the raw response bytes.expected_manifest_hash = version.json()["manifest_sha256"]assert expected_manifest_hash is not None, "manifest_sha256 is null after fetching the manifest"actual = hashlib.sha256(manifest_response.content).hexdigest()assert actual == expected_manifest_hash, "manifest hash mismatch"
# 2. Each PDF: hash the bytes and compare with the manifest.for document in manifest_response.json()["documents"]: issued = httpx.get( f"{version_path}/documents/{document['document_id']}/download", headers=headers, timeout=30 ) issued.raise_for_status() pdf = httpx.get(issued.json()["url"], timeout=120) # presigned: send no Authorization header pdf.raise_for_status() assert len(pdf.content) == document["byte_size"], f"{document['document_id']}: size mismatch" digest = hashlib.sha256(pdf.content).hexdigest() assert digest == document["sha256"], f"{document['document_id']}: hash mismatch" print("verified", document["document_id"], digest)import { createHash } from "node:crypto";
const BASE = "https://api.dev.velocifile.ai/v1";const headers = { Authorization: `Bearer ${process.env.VELOCIFILE_API_KEY}` };const versionPath = `${BASE}/filings/${process.env.VF_FILING_ID}/versions/${process.env.VF_VERSION_ID}`;
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");const get = async (url, init) => { const response = await fetch(url, init); if (!response.ok) throw new Error(`${response.status} ${url}`); return response;};
// Fetch the manifest first: building it is what records manifest_sha256 on the version.const manifestResponse = await get(`${versionPath}/manifest`, { headers });const manifestBytes = Buffer.from(await manifestResponse.arrayBuffer());const version = await (await get(versionPath, { headers })).json();
// 1. The manifest itself: hash the raw response bytes.if (version.manifest_sha256 === null) throw new Error("manifest_sha256 is null after fetching the manifest");if (sha256(manifestBytes) !== version.manifest_sha256) throw new Error("manifest hash mismatch");
// 2. Each PDF: hash the bytes and compare with the manifest.const manifest = JSON.parse(manifestBytes.toString("utf8"));for (const document of manifest.documents) { const issued = await ( await get(`${versionPath}/documents/${document.document_id}/download`, { headers }) ).json(); const pdf = Buffer.from(await (await get(issued.url)).arrayBuffer()); // presigned: no Authorization if (pdf.length !== document.byte_size) throw new Error(`${document.document_id}: size mismatch`); const digest = sha256(pdf); if (digest !== document.sha256) throw new Error(`${document.document_id}: hash mismatch`); console.log("verified", document.document_id, digest);}On a mismatch, discard the file and fetch it again. If it still does not match, contact VelociFile
with the request_id of the manifest call.
Concurrency
Section titled “Concurrency”At most 10 download issuances can be in flight per key. See Rate limits.