Retrieve a previously completed PDF analysis by its unique check ID.
GET https://api.htpbe.tech/v1/result/{id}
This endpoint requires API key authentication via the Authorization header.
Authorization: Bearer YOUR_API_KEYImportant: You can only retrieve analysis results that belong to your API client. Attempting to access another client's results will return a 404 error, even if the check ID exists. This ensures complete data privacy and isolation between API clients.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Check ID returned from POST /api/v1/analyze. UUID v4 for all requests — live and test keys both return UUID v4 IDs. |
Live key format: UUID v4 — xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
Test key format: Deterministic UUID v4 — fixed all-zeros pattern, e.g. 00000000-0000-4000-8000-000000000001
Valid Examples:
506a6b1b-1360-48a2-b389-abb346f85d04(live key request)00000000-0000-4000-8000-000000000001(test key request —clean.pdf)
Invalid Examples:
abc123(too short, not a UUID)- `` (empty string)
Where to Get: The id field returned by POST /api/v1/analyze.
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token with your API key (Bearer htpbe_live_... or Bearer htpbe_test_...). The Bearer prefix is recommended but optional — sending the raw key directly is also accepted, but only if the key starts with htpbe_ (e.g., Authorization: htpbe_live_sk_...). |
curl https://api.htpbe.tech/v1/result/506a6b1b-1360-48a2-b389-abb346f85d04 \
-H "Authorization: Bearer htpbe_live_sk_1234567890abcdef"// Node.js / TypeScript
const checkId = '506a6b1b-1360-48a2-b389-abb346f85d04';
const response = await fetch(`https://api.htpbe.tech/v1/result/${checkId}`, {
headers: {
Authorization: `Bearer ${process.env.HTPBE_API_KEY}`,
},
});
const result = await response.json();# Python
import requests
import os
check_id = '506a6b1b-1360-48a2-b389-abb346f85d04'
response = requests.get(
f'https://api.htpbe.tech/v1/result/{check_id}',
headers={
'Authorization': f"Bearer {os.getenv('HTPBE_API_KEY')}"
}
)
result = response.json()Returns a ResultResponse object containing all stored analysis data for the check.
Note: /api/v1/analyze returns only { "id": "..." }. This endpoint is where all analysis data lives: metadata, structure, signatures, findings, and verdict details.
{
// Core Identification
id: string;
filename: string;
check_date: number | null;
file_size: number;
// Algorithm version tracking
algorithm_version: string;
current_algorithm_version: string;
outdated_warning?: string;
// Primary Verdict
status: "intact" | "modified" | "inconclusive";
status_reason?: "html_renderer_origin" | "consumer_software_origin" | "online_editor_origin" | "scanned_document";
origin: {
type: "consumer_software" | "institutional" | "unknown" | "online_editor" | "scanned";
software: string | null;
};
// PDF Metadata (Unix timestamps)
creation_date: number | null;
modification_date: number | null;
creator: string | null;
producer: string | null;
// Critical Modification Detection
modification_confidence: string | null;
// Metadata Analysis
date_sequence_valid: boolean;
metadata_completeness_score: number;
// PDF Structure
xref_count: number;
has_incremental_updates: boolean;
update_chain_length: number;
pdf_version: string | null;
// Digital Signatures
has_digital_signature: boolean;
signature_count: number;
signature_removed: boolean;
modifications_after_signature: boolean;
// Content Analysis
page_count: number;
object_count: number;
has_javascript: boolean;
has_embedded_files: boolean;
// Modification Evidence
modification_markers: string[];
}- Type:
string(UUID v4) - Always Present: Yes
- Description: Unique identifier for this check
- Format:
xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx - Example:
"506a6b1b-1360-48a2-b389-abb346f85d04"
- Type:
string - Always Present: Yes
- Description: The stored filename for this check. If
original_filenamewas provided in thePOST /analyzerequest body, that value is used. Otherwise it is extracted from the last segment of theurlpath. - Examples:
"contract.pdf"(provided asoriginal_filenamein the analyze request, or extracted fromhttps://example.com/docs/contract.pdf)"invoice-2024-01.pdf""document.pdf"(default set at analysis time when no filename is determinable from the URL)""(empty string for legacy records created before filename tracking)
- Type:
number | null(Unix timestamp in seconds) - Can Be Null: Yes —
nullfor test key results (synthetic responses have no real analysis timestamp) and for legacy records created before timestamp tracking was introduced - Description: Timestamp when the analysis was performed
- Format: Seconds since Unix epoch (January 1, 1970 00:00:00 UTC)
- Example:
1736542583= January 10, 2025, 18:23:03 UTC - Convert to Date:
- JavaScript:
check_date ? new Date(check_date * 1000) : null - Python:
datetime.fromtimestamp(check_date) if check_date else None
- JavaScript:
- Type:
number(integer, bytes) - Always Present: Yes
- Range:
1to10485760(10 MB) - Description: File size in bytes
- Example:
245632= ~240 KB - Convert to MB:
file_size / 1024 / 1024
Note: Version numbers reflect the algorithm in use at the time of analysis. The current version may differ.
- Type:
string - Always Present: Yes
- Description: Version of the detection algorithm used when this check was performed. Defaults to
"1.0.0"for records created before algorithm versioning was introduced. - Format: Semantic versioning (e.g.,
"2.2.1") - Usage: Compare against the current version to determine if re-analysis is recommended
- Example:
"2.2.1"
- Type:
string - Always Present: Yes
- Description: The current algorithm version running on the server
- Format: Semantic versioning (e.g.,
"2.2.1") - Usage: Compare
algorithm_versionagainstcurrent_algorithm_versionto determine if the check is outdated. Ifalgorithm_version !== current_algorithm_version, the check was performed with an older algorithm and re-analysis is recommended. - Example:
"2.2.1"
- Type:
string(optional) - Present When:
algorithm_versionis older thancurrent_algorithm_version(semver comparison — a future version would not trigger this warning even if the values differ) - Absent When:
algorithm_versionis current - Description: Human-readable explanation of what changed in the newer algorithm version and why re-analysis is recommended
- Example:
"Our detection software has been updated since this file was analyzed. The results shown below may no longer be accurate. We recommend re-analyzing this file for more accurate results."
- Type:
"intact" | "modified" | "inconclusive" - Always Present: Yes
- Description: PRIMARY VERDICT. Priority:
modified > inconclusive > intact"modified"— forensic evidence of post-creation modification detected; takes priority over origin type — a modified Word or Excel document is stillmodified"inconclusive"— consumer software, online editor, or scanned origin with no modification detected; integrity check does not apply to documents anyone can create, reprocess, or scan from scratch"intact"— no modification detected and origin appears institutional
- Note:
status_reasononly appears whenstatus === "inconclusive"
- Type:
"consumer_software_origin" | "online_editor_origin" | "scanned_document" | "html_renderer_origin"| absent - Always Present: No — only present when
status === "inconclusive" - Description: Explains why the result is inconclusive.
- Values:
"html_renderer_origin"— the PDF was generated by an HTML-to-PDF renderer (wkhtmltopdf). This tool is used both by legitimate institutional HR and payroll systems and by professional document forgery operations producing fake payslips, invoices, and statements from scratch. Because both produce structurally identical output, PDF analysis alone cannot distinguish authentic data from fabricated data. If this document will be used to support a financial or compliance decision, verify its content directly with the issuing organisation."consumer_software_origin"— the PDF was created by consumer software (Microsoft Word, LibreOffice, Google Docs, etc.). These tools allow anyone to create a document from scratch, so there is no meaningful "original" to compare against."online_editor_origin"— the PDF was processed through an online editing service (iLovePDF, Smallpdf, PDF24, etc.). These services strip original metadata, making provenance verification impossible."scanned_document"— the PDF is a pure raster scan (no fonts, no text layer, at least one image per page). Anyone can print and scan a document, so its content cannot be verified through metadata analysis.
- Usage: Always check
status_reasonwhenstatus === "inconclusive"— it tells you why the result is inconclusive, not just that it is.
if (result.status === 'inconclusive') {
if (result.status_reason === 'html_renderer_origin') {
// wkhtmltopdf — used by both legitimate HR/payroll systems and forgery operations.
// Structural PDF analysis cannot distinguish authentic data from fabricated data.
// Verify directly with the issuing organisation before making any decisions.
} else if (result.status_reason === 'scanned_document') {
// Pure raster scan — no text layer, provenance unverifiable
} else if (result.status_reason === 'online_editor_origin') {
// Processed through an online editor — original metadata stripped
} else {
// result.status_reason === 'consumer_software_origin'
// Created in consumer software — not tampered, just unverifiable
}
}All timestamps are Unix integers (seconds since epoch). Convert with: new Date(timestamp * 1000) in JavaScript, datetime.fromtimestamp(ts) in Python.
- Type:
number | null(Unix timestamp in seconds) - Can Be Null: Yes
- Description: PDF creation timestamp from embedded metadata
- Null When: PDF does not contain creation date
- Example:
1704110400= January 2, 2024 00:00:00 UTC - Convert: JavaScript:
new Date(creation_date * 1000).toISOString()
- Type:
number | null(Unix timestamp in seconds) - Can Be Null: Yes
- Description: Last modification timestamp from embedded metadata
- Null When: PDF does not contain modification date
- Critical: If different from
creation_date, indicates modification - Example:
1707840000= February 13, 2024 12:00:00 UTC
- Type:
string | null - Can Be Null: Yes
- Description: Application that created the original document
- Example:
"Microsoft Word for Microsoft 365" - Common Values:
"Microsoft Word for Microsoft 365","Adobe InDesign CC","LibreOffice Writer","Google Docs"
- Type:
string | null - Can Be Null: Yes
- Description: Software that generated the final PDF
- Example:
"Adobe PDF Library 15.0" - Common Values:
"Adobe PDF Library 15.0","PDFKit","Microsoft: Print To PDF","iText 7.0.0"
- Type:
string | null(enum) - Can Be Null: Yes
- Description: How certain the algorithm is about the modification verdict. Use this to decide what action to take.
- Possible Values:
"certain"— conclusive structural or cryptographic evidence; reject the document — the finding cannot be a false positive (date mismatch, post-signature modification, signature removal)"high"— strong forensic evidence; flag for manual review — highly reliable but rare false positives are possible in unusual legitimate workflows (e.g. linearization, batch processing pipelines)"none"— no modification detected; document can be accepted as-is
- Relationship to
status:certainandhighalways correspond tostatus: "modified".nonecorresponds tostatus: "intact"or"inconclusive"depending on origin. - Null When: Legacy records before this field was added
- Type:
boolean - Always Present: Yes
- Description: Whether creation and modification dates are in chronologically valid sequence
- Valid Sequence:
creation_date≤modification_date - Possible Values:
true- Dates are valid (creation before or equal to modification)false- Suspicious - Modification date is before creation date (impossible in normal workflow)
- Red Flag:
falsestrongly suggests date tampering
- Type:
number(integer) - Always Present: Yes
- Range:
0to100 - Description: How complete the PDF metadata is (percentage of fields present)
- Calculation: Based on presence of:
- Creation date
- Modification date
- Creator
- Producer
- Title, Subject, Author (standard metadata)
- Interpretation:
90-100- Excellent metadata, most fields present70-89- Good metadata coverage50-69- Moderate metadata30-49- Limited metadata0-29- Very sparse metadata (may hinder analysis)
- Impact: Higher scores enable more confident modification detection
- Type:
number(integer) - Always Present: Yes
- Description: Number of cross-reference (xref) tables in the PDF
- Interpretation:
1= original document,2+= document has been modified and saved multiple times
- Type:
boolean - Always Present: Yes
- Description: Whether PDF has incremental update sections (been saved multiple times after initial creation)
- Values:
true= PDF has been edited and saved incrementally,false= PDF was generated in one operation
- Type:
number(integer) - Always Present: Yes
- Description: Number of update sections in PDF structure (how many times the PDF was saved)
- Interpretation:
1= original,2-5= light editing,6-10= moderate editing,11+= heavy editing
- Type:
string | null - Can Be Null: Yes
- Description: PDF specification version
- Examples:
"1.7","1.4","2.0",null - Common Versions:
"1.7"(most common, Acrobat 8.x+),"1.4"(Acrobat 5.x),"2.0"(modern ISO standard)
- Type:
boolean - Always Present: Yes
- Description: Whether PDF currently contains digital signatures
- Values:
true= digitally signed,false= not signed - Note: Signature validity is NOT checked (only presence)
- Type:
number(integer) - Always Present: Yes
- Description: Number of digital signature fields in the PDF
- Range:
0and higher
- Type:
boolean - Always Present: Yes
- Description: Whether a digital signature was removed from the document
- Critical: If
true, this is strong evidence of tampering
- Type:
boolean - Always Present: Yes
- Description: Whether PDF was modified after being digitally signed
- Critical: If
true, the digital signature is likely invalidated
- Type:
number(integer) - Always Present: Yes
- Range:
1and higher - Description: Total number of pages in the PDF document
- Example:
12= 12-page document - Usage: Useful for estimating document size and complexity
- Type:
number(integer) - Always Present: Yes
- Range:
0and higher - Description: Number of PDF objects in the internal structure
- What It Means:
- PDF objects include: pages, fonts, images, annotations, forms, etc.
- Higher object count generally means more complex document
- Useful for detecting unusual structural complexity
- Typical Ranges:
50-200- Simple text document200-500- Document with images500-1000- Complex document with forms/annotations1000+- Very complex document or potential obfuscation
- Type:
boolean - Always Present: Yes
- Description: Whether PDF contains embedded JavaScript code
- Values:
true= JavaScript present (potential security risk),false= no JavaScript detected - Security Note: JavaScript in PDFs can be used for malicious purposes
- Type:
boolean - Always Present: Yes
- Description: Whether PDF has embedded file attachments
- Values:
true= has attachments (potential security risk),false= no attachments - Security Note: Attachments can hide malware
- Type:
string[](array of strings) - Always Present: Yes
- Description: All modification signals detected, ordered strongest-first. The first element (
modification_markers[0]) is the strongest marker — the single most important forensic indicator that drove the verdict. Usually contains one entry; may have two or three when multiple independent signals fired simultaneously (e.g. dates disagree AND an editing tool detected). Empty whenstatusis"intact"or"inconclusive". - Format: Each entry is a stable machine-readable id (e.g.
HTPBE_SIGNATURE_REMOVED,HTPBE_DATES_DISAGREE). The full id → outcome-label dictionary is published on htpbe.tech/how — branch on the id in your integration, render the user-facing label from the dictionary. - Empty When: No modification detected (
status: "intact"orstatus: "inconclusive") - Example Values:
"HTPBE_DATES_DISAGREE""HTPBE_MULTIPLE_REVISION_LAYERS""HTPBE_SIGNATURE_REMOVED""HTPBE_POST_SIGNATURE_EDIT""HTPBE_EDITING_TOOL_FINGERPRINT""HTPBE_IDENTITY_BLANKED""HTPBE_FONT_VS_TOOL_MISMATCH""HTPBE_DESIGN_TEMPLATE_ASSEMBLY""HTPBE_TEXT_OVERLAY_ON_SCAN""HTPBE_TEXT_AS_VECTOR_OUTLINES"
{
"id": "506a6b1b-1360-48a2-b389-abb346f85d04",
"filename": "contract.pdf",
"check_date": 1736542583,
"file_size": 245632,
"algorithm_version": "2.2.1",
"current_algorithm_version": "2.2.1",
"status": "modified",
"origin": {
"type": "institutional",
"software": null
},
"creation_date": 1704110400,
"modification_date": 1707840000,
"creator": "Adobe Acrobat Pro DC",
"producer": "Adobe PDF Library 15.0",
"modification_confidence": "certain",
"date_sequence_valid": true,
"metadata_completeness_score": 90,
"xref_count": 2,
"has_incremental_updates": true,
"update_chain_length": 3,
"pdf_version": "1.7",
"has_digital_signature": false,
"signature_count": 0,
"signature_removed": true,
"modifications_after_signature": false,
"page_count": 12,
"object_count": 487,
"has_javascript": false,
"has_embedded_files": false,
"modification_markers": ["HTPBE_SIGNATURE_REMOVED", "HTPBE_DATES_DISAGREE"]
}{
"id": "00000000-0000-4000-8000-000000000011",
"filename": "inconclusive.pdf",
"check_date": null,
"file_size": 204800,
"algorithm_version": "2.2.1",
"current_algorithm_version": "2.2.1",
"status": "inconclusive",
"status_reason": "html_renderer_origin",
"origin": {
"type": "consumer_software",
"software": "Microsoft Excel"
},
"creation_date": 1709283600,
"modification_date": 1709283600,
"creator": "Microsoft Excel 2019",
"producer": "Microsoft: Print To PDF",
"modification_confidence": "none",
"date_sequence_valid": true,
"metadata_completeness_score": 65,
"xref_count": 1,
"has_incremental_updates": false,
"update_chain_length": 1,
"pdf_version": "1.7",
"has_digital_signature": false,
"signature_count": 0,
"signature_removed": false,
"modifications_after_signature": false,
"page_count": 1,
"object_count": 82,
"has_javascript": false,
"has_embedded_files": false,
"modification_markers": []
}{
"id": "00000000-0000-4000-8000-000000000014",
"filename": "inconclusive-online-editor.pdf",
"check_date": null,
"file_size": 312000,
"algorithm_version": "2.2.1",
"current_algorithm_version": "2.2.1",
"status": "inconclusive",
"status_reason": "online_editor_origin",
"origin": {
"type": "online_editor",
"software": "iLovePDF"
},
"creation_date": null,
"modification_date": null,
"creator": null,
"producer": "iLovePDF",
"modification_confidence": "none",
"date_sequence_valid": true,
"metadata_completeness_score": 15,
"xref_count": 1,
"has_incremental_updates": false,
"update_chain_length": 1,
"pdf_version": "1.7",
"has_digital_signature": false,
"signature_count": 0,
"signature_removed": false,
"modifications_after_signature": false,
"page_count": 2,
"object_count": 110,
"has_javascript": false,
"has_embedded_files": false,
"modification_markers": []
}{
"id": "00000000-0000-4000-8000-000000000015",
"filename": "scanned-document.pdf",
"check_date": null,
"file_size": 890000,
"algorithm_version": "2.2.1",
"current_algorithm_version": "2.2.1",
"status": "inconclusive",
"status_reason": "scanned_document",
"origin": {
"type": "scanned",
"software": null
},
"creation_date": null,
"modification_date": null,
"creator": null,
"producer": null,
"modification_confidence": "none",
"date_sequence_valid": true,
"metadata_completeness_score": 0,
"xref_count": 1,
"has_incremental_updates": false,
"update_chain_length": 1,
"pdf_version": "1.4",
"has_digital_signature": false,
"signature_count": 0,
"signature_removed": false,
"modifications_after_signature": false,
"page_count": 3,
"object_count": 45,
"has_javascript": false,
"has_embedded_files": false,
"modification_markers": []
}All errors follow this format:
{
"error": "Human-readable error message",
"code": "machine_readable_error_code",
"details": "Optional additional context (present for some errors)"
}Returned when the check ID format is invalid.
{
"error": "Invalid check ID format",
"code": "invalid_request"
}Causes:
idis empty stringidis not a valid UUID v4 (e.g.abc123,12345678-1234-1234-1234-12345678)idcontains invalid characters
Solution: Ensure you're using the full UUID v4 returned by POST /api/v1/analyze or from the id field in GET /api/v1/checks. Format: xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx (32 hex digits with 4 hyphens).
Authentication errors (same as analyze endpoint).
See: analyze.md for details.
No active subscription found for this API key.
See: analyze.md for details.
Access forbidden due to account status (deactivated key).
See: analyze.md for details.
Check not found or access denied.
{
"error": "Check not found or access denied",
"code": "not_found"
}Causes:
- Check ID doesn't exist in the database
- Check ID belongs to another API client (data isolation)
- Check ID is malformed but passed basic validation
Security Note: For privacy, we intentionally don't distinguish between "doesn't exist" and "belongs to another client". This prevents enumeration attacks.
Solution:
- Verify the check ID is correct
- Ensure you're using the API key that created the check
- If you need to share results between API clients, contact support for guidance
Server error during retrieval.
{
"error": "Failed to fetch result",
"code": "internal_error"
}Cause: Unexpected database or server error.
Solution:
- Retry the request
- If persists, contact support with the check ID
Store check IDs and retrieve full results later for compliance:
// Store check ID after analysis
await db.documents.create({
id: uploadId,
htpbe_check_id: analysisResponse.id,
verified_at: new Date(),
});
// Later, retrieve full details for audit
const auditData = await fetch(`https://api.htpbe.tech/v1/result/${doc.htpbe_check_id}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});Use the additional fields for comprehensive reports:
const result = await getResult(checkId);
const report = {
summary:
result.status === 'modified'
? 'Modified'
: result.status === 'inconclusive'
? 'Unverifiable'
: 'Original',
// Use fields not available in analyze endpoint
details: {
pages: result.page_count,
objects: result.object_count,
metadataQuality: result.metadata_completeness_score,
analysisDate: new Date(result.check_date * 1000),
},
criticalMarkers: result.modification_markers,
};Related Endpoints:
- POST /api/v1/analyze - Analyze a PDF
- GET /api/v1/checks - List all checks with filtering for custom analytics