{"openapi":"3.1.0","info":{"title":"HTPBE — PDF Tamper Detection API","summary":"Structural forensic detection of post-creation modifications in PDF documents.","description":"Detect whether a PDF was edited after it was issued. The API runs 61 forensic checks across metadata, file structure, digital signatures, generator fingerprinting, document assembly, content streams, image forensics, and structural integrity — returning a structured verdict (`intact`, `modified`, or `inconclusive`) plus the list of named markers that fired.\n\n**Two-step pattern.** `POST /analyze` submits a publicly accessible PDF URL and returns a check `id` immediately. `GET /result/{id}` returns the full structured verdict. `GET /checks` paginates the caller’s entire history with filters.\n\n**Auth.** Every request requires `Authorization: Bearer <api_key>`. Test keys (`htpbe_test_*`) accept only synthetic test URLs and return deterministic results, do not debit credits, and are free on every plan. Live keys (`htpbe_live_*`) accept any public PDF URL and draw from your credit pool.\n\n**Marker IDs.** `modification_markers[]` contains stable machine-readable strings (e.g. `HTPBE_SIGNATURE_REMOVED`). The full id → outcome-label dictionary is published at https://htpbe.tech/how — branch your integration on the id, render the user-facing label from the dictionary. IDs are part of the API contract and are only ever added, never renamed.","version":"1.0.0","contact":{"name":"HTPBE support","url":"https://htpbe.tech","email":"info@htpbe.tech"},"license":{"name":"Proprietary","url":"https://htpbe.tech/legal/terms"},"x-logo":{"url":"https://htpbe.tech/icons/htpbe.svg"}},"externalDocs":{"description":"Full prose documentation, code samples, and how it works","url":"https://htpbe.tech/api"},"servers":[{"url":"https://api.htpbe.tech/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Analysis","description":"Submit a PDF for structural forensic analysis."},{"name":"Results","description":"Retrieve previously computed analysis results."}],"paths":{"/analyze":{"post":{"tags":["Analysis"],"summary":"Submit a PDF for analysis","operationId":"analyzePdf","description":"Downloads the PDF from the supplied public URL, runs the full forensic check suite (typically 2–3 seconds), persists the result, and returns the `id`. Use `GET /result/{id}` to retrieve the structured verdict.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeRequest"}}}},"responses":{"201":{"description":"Analysis completed and persisted. Use `id` with `GET /result/{id}`.","headers":{"Location":{"description":"Full URL of the result resource.","schema":{"type":"string","format":"uri"},"example":"https://api.htpbe.tech/v1/result/3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeResponse"},"example":{"id":"3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"504":{"$ref":"#/components/responses/AnalysisTimeout"}}}},"/result/{id}":{"get":{"tags":["Results"],"summary":"Retrieve a single check result","operationId":"getResult","description":"Returns the complete forensic analysis for a previously submitted check. Owner-only — only the API client that originally submitted the check can retrieve it; cross-client access returns 404 even if the id exists.","parameters":[{"name":"id","in":"path","required":true,"description":"Check ID returned from `POST /analyze` (UUID v4).","schema":{"type":"string","format":"uuid","minLength":10},"example":"506a6b1b-1360-48a2-b389-abb346f85d04"}],"responses":{"200":{"description":"Full check result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/checks":{"get":{"tags":["Results"],"summary":"List the caller’s checks","operationId":"listChecks","description":"Paginated history of all checks performed by your API client. Filterable by tool name (creator OR producer), specific creator/producer, verdict status, and submission date range. Owner-only — other clients’ checks are never returned.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Results per page (1–500)."},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of results to skip for pagination."},{"name":"tool","in":"query","schema":{"type":"string"},"description":"Filter by tool name (matches Creator OR Producer). Case-sensitive, exact match.","example":"Adobe PDF Library 15.0"},{"name":"creator","in":"query","schema":{"type":"string"},"description":"Filter by Creator tool only. Case-sensitive, exact match.","example":"Microsoft Word for Microsoft 365"},{"name":"producer","in":"query","schema":{"type":"string"},"description":"Filter by Producer tool only. Case-sensitive, exact match.","example":"Adobe PDF Library 15.0"},{"name":"status","in":"query","schema":{"type":"string","enum":["intact","modified","inconclusive"]},"description":"Filter by verdict."},{"name":"from_date","in":"query","schema":{"type":"integer","format":"int64"},"description":"Unix timestamp (seconds). Return checks whose `check_date` is on or after this value.","example":1738368000},{"name":"to_date","in":"query","schema":{"type":"integer","format":"int64"},"description":"Unix timestamp (seconds). Return checks whose `check_date` is on or before this value.","example":1740960000}],"responses":{"200":{"description":"Paginated list of checks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChecksListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"htpbe_live_* (live key) or htpbe_test_* (synthetic test key)","description":"API key authentication. Send `Authorization: Bearer <api_key>`. Live keys (`htpbe_live_*`) accept any public PDF URL and draw from your credit pool. Test keys (`htpbe_test_*`) accept only synthetic test URLs (see `lib/constants/test-mock-responses.ts` patterns) and return deterministic results without debiting credits. The raw key without the `Bearer` prefix is also accepted for legacy clients."}},"schemas":{"Verdict":{"type":"string","enum":["intact","modified","inconclusive"],"description":"`intact` — no structural evidence of modification. `modified` — one or more forensic markers fired. `inconclusive` — file was assembled outside an institutional system (consumer software, online editor, or scanned), so the integrity check does not apply."},"ModificationConfidence":{"type":"string","enum":["certain","high","none"],"nullable":true,"description":"`certain` — reserved for signature/date-mismatch markers and tier-dependent definitive markers. `high` — every other modification marker. `none` — verdict is `intact`. `null` — legacy records."},"OriginType":{"type":"string","enum":["institutional","consumer_software","online_editor","scanned","unknown"],"description":"How the PDF was created. `institutional` documents come from a known issuer pipeline (banks, government, payroll systems). `consumer_software`, `online_editor`, and `scanned` cannot return `intact` because the integrity check does not apply to documents anyone can produce or reprocess from scratch. `unknown` covers files where the generator could not be classified."},"Origin":{"type":"object","required":["type","software"],"properties":{"type":{"$ref":"#/components/schemas/OriginType"},"software":{"type":"string","nullable":true,"description":"Best-effort generator identification, e.g. `\"Microsoft Word\"`.","example":"Adobe Acrobat Pro DC"}}},"StatusReason":{"type":"string","enum":["html_renderer_origin","consumer_software_origin","online_editor_origin","scanned_document"],"description":"Present only when `status` is `inconclusive`. Explains which origin class triggered the inconclusive verdict."},"ModificationMarker":{"type":"string","description":"Stable machine-readable forensic marker id. There are currently 61 public marker ids across 61 forensic checks. Branch your integration logic on these ids; render the user-facing outcome label from the dictionary at https://htpbe.tech/how. Ids are part of the public contract and are only ever added, never renamed.","enum":["HTPBE_ALTERED_TYPEFACE_IDENTITY","HTPBE_ANNOTATION_PREDATES_CREATION","HTPBE_BURNED_IN_ANNOTATIONS","HTPBE_CHARACTER_OVERLAY_EDIT","HTPBE_COLLAPSED_TO_RASTER","HTPBE_CONTENT_STREAM_EDITING_MARKERS","HTPBE_DANGLING_STRUCTURE_REFERENCE","HTPBE_DATES_DISAGREE","HTPBE_DECLARED_SIZE_MISMATCH","HTPBE_DESIGN_TEMPLATE_ASSEMBLY","HTPBE_DOCUMENT_RESTAMPED_AFTER_CREATION","HTPBE_DRAWING_OPS_INCONSISTENT","HTPBE_EDITING_TOOL_FINGERPRINT","HTPBE_FAKE_SCANNER_ORIGIN","HTPBE_FLATTENED_EDIT_HISTORY","HTPBE_FONT_LAYER_INCONSISTENT_WITH_ISSUER","HTPBE_FONT_VS_TOOL_MISMATCH","HTPBE_GLYPH_LEVEL_EDIT","HTPBE_IDENTITY_BLANKED","HTPBE_IDENTITY_LAYERS_DISAGREE","HTPBE_IDENTITY_OVERWRITTEN","HTPBE_IMAGE_EDITED_AND_RESAVED","HTPBE_IMPOSSIBLE_TIMESTAMP","HTPBE_IMPOSSIBLE_TOOL_PIPELINE","HTPBE_INCOMPLETE_REDACTION","HTPBE_INVALID_TIMEZONE","HTPBE_INVISIBLE_DUPLICATE_TEXT","HTPBE_LAYERED_IDENTITY_DISAGREEMENT","HTPBE_METADATA_ONLY_REVISION","HTPBE_MIXED_FONT_EMBEDDING","HTPBE_MIXED_PAGE_DIMENSIONS","HTPBE_MIXED_TOOLING_CLASSES","HTPBE_MULTIPLE_REVISION_LAYERS","HTPBE_NAVIGATION_TABLE_MISMATCH","HTPBE_ONLINE_EDITOR_ORIGIN","HTPBE_PAGES_FROM_MULTIPLE_SOURCES","HTPBE_PARTIAL_FONT_REPLACEMENT","HTPBE_PHYSICAL_SCAN_GEOMETRY","HTPBE_POST_SIGNATURE_EDIT","HTPBE_PRINTED_OUT_OF_PDF_READER","HTPBE_PRINT_PIPELINE_TAMPERING","HTPBE_PRODUCER_IDENTITY_FORGED","HTPBE_REEXPORTED_THROUGH_OFFICE_SUITE","HTPBE_RENDERED_PSEUDO_SCAN","HTPBE_RESIDUAL_DOCUMENT_STRUCTURE","HTPBE_RESIDUAL_PRIOR_GENERATOR","HTPBE_SCAN_IN_DIGITAL_DOC","HTPBE_SCRIPTING_ON_REEMITTED","HTPBE_SELECTIVE_IDENTITY_EDIT","HTPBE_SIGNATURE_REMOVED","HTPBE_TEMPLATE_PATTERN_BREAK","HTPBE_TEXT_AS_VECTOR_OUTLINES","HTPBE_TEXT_OVERLAY_ON_SCAN","HTPBE_TIMESTAMP_LAYERS_DISAGREE","HTPBE_TOOLCHAIN_DEFAULT_METADATA_RESIDUE","HTPBE_TOOL_VS_STRUCTURE_MISMATCH","HTPBE_TRAILING_BYTES_AFTER_EOF","HTPBE_UNKNOWN_IDENTITY_CLAIM","HTPBE_UNLOCK_TOOL_RESIDUE","HTPBE_WIDGET_APPEARANCE_MISMATCH","HTPBE_XMP_DATES_DESYNCED"]},"AnalyzeRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Publicly accessible HTTP/HTTPS URL pointing to a PDF file. Must be downloadable without authentication. Maximum file size 10 MB (10,485,760 bytes). FTP, file://, and localhost URLs are rejected.","example":"https://example.com/documents/contract.pdf"},"original_filename":{"type":"string","description":"Optional. Original filename of the document before any storage renaming. If provided, this value is stored and returned in results instead of the filename extracted from the URL path.","example":"q3-statement.pdf"}}},"AnalyzeResponse":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid","description":"Use with `GET /result/{id}` to retrieve the full verdict.","example":"3f9c8b7a-2e1d-4c5f-9b8e-7a6d5c4b3a21"}}},"CheckResult":{"type":"object","required":["id","filename","file_size","algorithm_version","current_algorithm_version","status","origin","date_sequence_valid","metadata_completeness_score","xref_count","has_incremental_updates","update_chain_length","has_digital_signature","signature_count","signature_removed","modifications_after_signature","page_count","object_count","has_javascript","has_embedded_files","modification_markers"],"properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string","description":"Original filename or URL-derived basename."},"check_date":{"type":"integer","format":"int64","nullable":true,"description":"Unix timestamp (seconds) of when the analysis ran. `null` for test-key results and legacy records."},"file_size":{"type":"integer","description":"File size in bytes."},"algorithm_version":{"type":"string","description":"Algorithm version that produced this result.","example":"2.23.3"},"current_algorithm_version":{"type":"string","description":"Latest algorithm version live in production. If this differs from `algorithm_version`, the result is stale relative to current detection capabilities.","example":"2.23.3"},"outdated_warning":{"type":"string","description":"Human-readable warning surfaced when `algorithm_version` is older than `current_algorithm_version`."},"status":{"$ref":"#/components/schemas/Verdict"},"status_reason":{"$ref":"#/components/schemas/StatusReason"},"origin":{"$ref":"#/components/schemas/Origin"},"creation_date":{"type":"integer","format":"int64","nullable":true,"description":"PDF metadata CreationDate as Unix timestamp (seconds), or `null`."},"modification_date":{"type":"integer","format":"int64","nullable":true,"description":"PDF metadata ModDate as Unix timestamp (seconds), or `null`."},"creator":{"type":"string","nullable":true,"description":"Declared Creator from the Info dictionary.","example":"Microsoft Word for Microsoft 365"},"producer":{"type":"string","nullable":true,"description":"Declared Producer from the Info dictionary.","example":"Adobe PDF Library 15.0"},"modification_confidence":{"$ref":"#/components/schemas/ModificationConfidence"},"date_sequence_valid":{"type":"boolean","description":"False when CreationDate, ModDate, and the XMP metadata dates contradict each other or are physically impossible."},"metadata_completeness_score":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Heuristic completeness score for the Info/XMP metadata block (0–1)."},"xref_count":{"type":"integer","description":"Number of cross-reference tables in the file."},"has_incremental_updates":{"type":"boolean","description":"True when the file contains incremental update sections (post-creation revisions)."},"update_chain_length":{"type":"integer","description":"Length of the incremental update chain."},"pdf_version":{"type":"string","nullable":true,"description":"PDF version string from the header (e.g. `1.7`, `2.0`)."},"has_digital_signature":{"type":"boolean"},"signature_count":{"type":"integer"},"signature_removed":{"type":"boolean","description":"True when forensic evidence indicates a digital signature was stripped after signing."},"modifications_after_signature":{"type":"boolean","description":"True when content was changed after the document was digitally signed."},"page_count":{"type":"integer"},"object_count":{"type":"integer"},"has_javascript":{"type":"boolean"},"has_embedded_files":{"type":"boolean"},"modification_markers":{"type":"array","items":{"$ref":"#/components/schemas/ModificationMarker"},"description":"Every forensic marker that fired. Empty when `status` is `intact` or `inconclusive`."}}},"ChecksListResponse":{"type":"object","required":["data","has_more","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CheckResult"}},"has_more":{"type":"boolean","description":"True when more results match the filter and are reachable with a larger `offset`."},"total":{"type":"integer","description":"Total number of checks matching the filter (across all pages)."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable explanation."}}}},"responses":{"BadRequest":{"description":"The request body or query parameters were malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"No credits and no active subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The action is not allowed for this key (e.g. live PDF URL with a test key, which only accepts synthetic test URLs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No check with this id exists for the authenticated caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"The downloaded PDF exceeds the 10 MB limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"The URL was reachable but the response was not a PDF, or the PDF could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"Unexpected server-side error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"AnalysisTimeout":{"description":"Analysis exceeded the 20-second processing budget.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}