Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, IT partner for business & fractional CTO. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Detects: HTPBE? — Has This PDF Been Edited?

September 25, 2024

SaaS platform for PDF authenticity verification with a public REST API. Detects edits through 5-layer analysis of metadata, xref structure, digital signatures, and content.

Live demo

Stack

Next.jsReactTypeScript

Libraries

Drizzle ORMZodNextAuth.jspdf-lib

Databases

PostgreSQL

Services

MollieResendGoogle Analytics

Topics

SaaSPDFAPIAuthSecuritySEOSchema.org

Key Results

  • Answers a costly question in under 9 seconds — has this signed document been edited since it was signed?
  • Self-service instead of enterprise procurement — from $15/month, no sales calls, NDAs, or annual contracts
  • Every verdict is explainable — a 0–100 risk score with the specific markers behind it, not a black-box guess
  • Handles real-world documents — files up to 10 MB, 2.2× the platform's default limit
  • On-premise option for regulated teams — an Enterprise Docker deployment so documents never leave the organisation
HTPBE? — Has This PDF Been Edited?

The Business Problem

When a bank receives a signed loan contract, or a law firm gets a countersigned agreement, or an HR team processes an employment certificate — how do they verify the document has not been edited after signing? Most organisations have no reliable answer. They look at the signature icon in Acrobat and assume that means something. Often it does not.

Enterprise PDF verification tools charge $1–$1.50 per document and require sales calls, NDAs, and annual contracts before you can run a single test. There was no self-service option for teams that need to verify dozens or hundreds of documents a month without going through an enterprise procurement process. The gap was obvious: a product that works via API, explains what it found, and costs less than a coffee per day.

The Solution

I built a self-service SaaS that answers one question fast and clearly: has this signed PDF been edited since it was signed? Upload a document — or send it through the API — and in under nine seconds you get a 0–100 risk score with the specific evidence behind it, not a black-box verdict you have to trust blindly.

The verdict is reliable because it is deterministic. A legitimate PDF has one internal revision table; every later edit adds another. That single, checkable fact — combined with metadata analysis, digital-signature validation, and fingerprinting of known editing tools — is more dependable than any heuristic guess, and it is reproducible: the same document always yields the same explained result.

Around that engine I built the full commercial product: sign-up, separate live and test API environments, tiered billing (a self-built billing layer handling mandates, proration, dunning, and EU VAT), and algorithm versioning so that when the detection logic improves, an older stored result is automatically flagged rather than silently trusted. For banks, law firms, and other regulated teams whose documents cannot leave their own infrastructure, an Enterprise Docker option runs the whole thing on-premise, addressing GDPR, HIPAA, and PCI DSS requirements.

Results

What previously required an enterprise contract and per-document billing is now available via API for $15/month. Teams at banks, law firms, and HR departments can integrate PDF verification into their own workflows without a procurement process.

MetricValue
Analysis time≤9 seconds (within Vercel timeout)
Max file size10 MB (2.2× Vercel's 4.5 MB limit)
Detection markers7+ deterministic (iLovePDF, PDF24, QPDF, etc.)
Algorithm versions7 in 5 days (v2.0.0 → v2.1.3)
Database schema6 tables with dynamic quota calculation
Pricing tiers$15–$499/month + Enterprise on-premise

The deterministic approach means results are reproducible, explainable, and fast. Enterprise option (Docker/Kubernetes) addresses GDPR, HIPAA, and PCI DSS requirements for fintech and legal clients.

Technical SEO

The category — "PDF authenticity verification API" — is small enough that organic discovery from developers searching for the exact problem is the primary acquisition channel. That makes technical SEO load-bearing, not optional:

  • JSON-LD schema for SoftwareApplication, FAQPage, BreadcrumbList, and Organization — surfaces rich results for the long-tail queries this niche actually generates ("detect if PDF was edited", "verify digital signature PDF API")
  • Server-rendered marketing, pricing, and blog pages via Next.js App Router — full HTML, no client-side data fetching on public pages
  • Dynamic OG images per blog post — engineering content (how the 5-layer analysis works, PDF tampering markers) gets shared in developer communities, and the link previews need to render the article, not a generic banner
  • llms.txt describing the API, pricing, and the technical articles — the developer audience increasingly evaluates tools through ChatGPT and Perplexity before they ever click through
  • Sitemap auto-generated from blog and docs content, IndexNow ping on every deploy so new technical articles reach Bing within minutes
  • Core Web Vitals passing on mobile — minimal third-party scripts, fonts loaded without layout shift, images optimized

Under the Hood

For the technically curious, here is how the core pieces are built. One real constraint shaped the upload path: the platform's serverless functions cap request bodies at 4.5 MB, and real-world contracts and scans regularly exceed that.

Client-Side Upload to Bypass Vercel Limits

A two-stage upload pattern: the browser uploads directly to Vercel Blob via a presigned URL (bypassing the serverless limit), then the server downloads and analyses it:

// components/Hero/index.tsx — Step 1: browser → Vercel Blob
const blob = await upload(filename, file, {
  access: "public",
  handleUploadUrl: "/api/blob-token",
  clientPayload: JSON.stringify({ size: file.size }),
});
 
// Step 2: server downloads from Blob URL and analyzes
await analyzePdf(blob.url, file.name); // Server Action
 
// app/api/blob-token/route.ts — origin-based security
export async function POST(req: Request): Promise<Response> {
  const origin = req.headers.get("origin") ?? "";
  const isAllowed = ALLOWED_ORIGINS.some((o) => origin === o);
  if (!isAllowed) return new Response("Forbidden", { status: 403 });
 
  const body = await req.json();
  const jsonResponse = await handleUpload({
    body,
    request: req,
    onBeforeGenerateToken: async () => ({
      allowedContentTypes: ["application/pdf"],
      maximumSizeInBytes: 10 * 1024 * 1024,
    }),
  });
  return Response.json(jsonResponse);
}

This allows processing files up to 10 MB without infrastructure changes.

5-Layer PDF Forensics with Algorithm Versioning

Analysis is split into independent layers. The critical marker is xref table counting: a legitimate PDF has one, each incremental update adds another. PDF 1.5+ complicates this with xref streams (different syntax):

// lib/services/pdf-structure.service.ts
private countXrefTables(pdfBuffer: Buffer): number {
  let count = 0;
  let pos = 0;
  while (pos < pdfBuffer.length) {
    // Classic xref tables: "xref\n" at start of line
    const xrefIdx = pdfBuffer.indexOf(Buffer.from('xref\n'), pos);
    // xref streams (PDF 1.5+): "/Type /XRef" in stream dictionary
    const xrefStreamIdx = pdfBuffer.indexOf(Buffer.from('/Type /XRef'), pos);
    const nextIdx = Math.min(
      xrefIdx === -1 ? Infinity : xrefIdx,
      xrefStreamIdx === -1 ? Infinity : xrefStreamIdx
    );
    if (nextIdx === Infinity) break;
    count++;
    pos = nextIdx + 5;
  }
  return count;
}

The algorithm is versioned (v2.1.3), and the version is saved with each result. When requesting /api/v1/result/{uid}, outdated results are automatically flagged with algorithmOutdated: true.

Dynamic Quota System Without Counters

Instead of storing a usage counter in the user row (denormalization → race conditions), quota is calculated dynamically from the checks table:

// lib/services/quota.service.ts
async checkQuota(userId: string): Promise<QuotaStatus> {
  const user = await db.select().from(users).where(eq(users.id, userId)).get();
 
  const startOfMonth = new Date();
  startOfMonth.setDate(1);
  startOfMonth.setHours(0, 0, 0, 0);
 
  // Count from checks table — no stale counters, no race conditions
  const result = await db
    .select({ count: sql<number>`cast(count(*) as integer)` })
    .from(checks)
    .innerJoin(apiKeys, eq(checks.apiKeyId, apiKeys.id))
    .where(
      and(
        eq(apiKeys.userId, userId),
        gte(checks.checkDate, Math.floor(startOfMonth.getTime() / 1000))
      )
    );
 
  const used = result[0]?.count ?? 0;
  const limit = user.requestsPerMonth; // null = unlimited (Enterprise)
  return { used, limit, remaining: limit === null ? null : limit - used };
}

Dual-Environment API Keys (Live/Test)

Keys follow format htpbe_{live|test}_{43-random-chars}. Test keys only accept mock URLs from whitelist — developers can test integration without consuming quota:

// app/api/v1/analyze/route.ts
const keyEnv = getApiKeyEnvironment(apiKey); // 'live' | 'test'
 
if (keyEnv === 'test') {
  const TEST_URLS = ['https://htpbe.tech/samples/modified-high.pdf', ...];
  if (!TEST_URLS.includes(file_url)) {
    return Response.json({
      error: 'Test keys only work with official HTPBE? sample URLs',
      test_urls: TEST_URLS,
    }, { status: 422 });
  }
}
// Live keys: fetch any public PDF URL, analyze, bill quota

LTV Validation (Avoiding False Positives)

Long-Term Validation (LTV) adds timestamps and certificates to signed PDFs — this is legitimate modification that shouldn't trigger alerts:

// lib/services/pdf-ltv.service.ts
export function analyzeLTV(bytes: Uint8Array): LTVAnalysis {
  const text = new TextDecoder("latin1").decode(bytes);
 
  // Detect Document Security Store (DSS)
  const hasDSS = /\/Type\s*\/DSS\b/.test(text);
 
  // Detect Document Timestamp
  const hasDTS =
    /\/Type\s*\/DocTimeStamp\b/.test(text) || /\/SubFilter\s*\/ETSI\.RFC3161\b/.test(text);
 
  // ETSI PAdES-LTV compliance markers
  const isETSICompliant = hasDSS || hasDTS || /\/SubFilter\s*\/ETSI\.CAdES\.detached\b/.test(text);
 
  return {
    hasLTV: hasDSS || hasDTS,
    hasDSS,
    hasDTS,
    isETSICompliant,
    ltvDetails: isETSICompliant ? extractLTVDetails(bytes) : null,
  };
}
Iurii RoguliaAvailable

Need something similar?

I build custom solutions — from APIs to full products. Let's talk about your project.

View all projects

Related projects

vatnode — EU VAT Validation API
vatnode — EU VAT Validation API
January 19, 2026
vatnode — EU VAT Validation API

Developer-first SaaS API for EU VAT validation via VIES with Redis caching, change monitoring, and webhook notifications.

Stack

Next.jsReactTypeScriptHonoNode.jsTurborepoDockerCaddy

Libraries

Drizzle ORMBullMQZodBetter AuthTanStack QueryZustand

Databases

PostgreSQLRedis

Services

StripeResendGoogle Analytics

Topics

SaaSAPIAuthFintechTax/VATMonorepoWebhooksSEOSchema.org
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
October 12, 2024
Pikkuna — E-commerce for Vinyl Curtains & PVC Products

International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe → Zoho CRM → Airtable → Mailgun →

Stack

Next.jsReactTypeScript

Libraries

next-intlZodpdf-libPuppeteerStripe.js

Databases

Redis

Services

StripeZohoMailgunPostNordNetvisorVercelVercel BlobGoogle AnalyticsMeta CAPIAirtableUpstash

Topics

E-commercePaymentsShippingPDFPWACTOArchitectureSEOSchema.orgi18nPerformance

Related posts

Next.js SaaS Checklist: Launch Production-Ready in 8 Weeks
January 19, 2026· 17 min
Next.js SaaS Checklist: Launch Production-Ready in 8 Weeks

40+ point production SaaS checklist: auth, Stripe billing, PostgreSQL, rate limiting, email, monitoring, and security — with honest 8-week build estimates.

Stack

Next.jsTypeScript

Libraries

Drizzle ORMBetter AuthBullMQZodReact Email

Databases

PostgreSQLRedis

Services

StripeVercelResendSentry

Topics

SaaSArchitectureAuth
API Key Management for a Public SaaS API
August 5, 2026· 13 min
API Key Management for a Public SaaS API

API key management for a public SaaS: hashing keys at rest, prefix + last-4 display, fail-closed validation, and revocation — plus the scoping, rotation, and

Stack

TypeScriptNode.jsHono

Libraries

Drizzle ORM

Databases

PostgreSQL

Topics

SaaSAPIAuthSecurity