Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

FileApi — Document Processing & AI Analysis Platform

CITests.NET

PackageVersionDownloadsLicence
Aelena.FileApi.CoreNuGetDownloadsMIT
Aelena.FileApi.Core.PdfNuGetDownloadsAGPL-3.0
Aelena.FileApi.CliNuGetDownloadsAGPL-3.0

A comprehensive .NET 10 / C# 14 document processing platform. Four ports available:

  • HTTP API
  • Rich CLI
  • gRPC service
  • NuGet library

All powered by the same pure Core library with zero ASP.NET dependencies.

Builds and tests green on .NET 10 (LTS) and .NET 11 preview.


⚠️ Licensing — read this before taking a dependency

This repository ships two libraries under two different licences, and the difference matters.

PackageLicenceContainsSafe for closed-source use?
Aelena.FileApi.CoreMITDOCX, images, email, hashing, PII, readability, text, ZIP, share links, jobsYes
Aelena.FileApi.Core.PdfAGPL-3.0-or-laterAll PDF operationsNo — see below
Aelena.FileApi.Cli (fileapi tool)AGPL-3.0-or-laterEverything, including PDFNo — see below

Aelena.FileApi.Core.Pdf is built on iText 7, which is licensed under the AGPL. The AGPL is a strong copyleft licence: if you use it in a network-facing application, that obligation extends to your application's source. iText sells a commercial licence if that is not acceptable — that is a matter between you and iText, and installing this package does not grant it.

PDF lives in its own package precisely so that everything else can stay MIT. If you do not need PDF, depend on Aelena.FileApi.Core alone and no copyleft code enters your build. The split is enforced by the project structure: Core has no reference to iText, direct or transitive.

# MIT, no copyleft anywhere in the graph
dotnet add package Aelena.FileApi.Core
# AGPL — only if you understand and accept the obligation
dotnet add package Aelena.FileApi.Core.Pdf

The self-hosted HTTP API and gRPC service in this repository include PDF by default, so a deployment of either is likewise subject to the AGPL.

If you clone this repository

The NuGet split does not help you here — a clone contains everything, including the AGPL part. So there is a supported way to build without it:

dotnet build -p:IncludePdf=false
dotnet publish src/Aelena.FileApi.Api -f net10.0 -c Release -p:IncludePdf=false

-p:IncludePdf=false removes the Aelena.FileApi.Core.Pdf project reference, the /pdf/* endpoints, the fileapi pdf command group, and the PDF gRPC methods. The result contains no iText assembly at all — not a disabled feature flag, an absent dependency. What remains is MIT throughout.

Default build-p:IncludePdf=false
Effective licenceAGPL-3.0-or-laterMIT
/pdf/* endpoints30+ routesabsent (404)
fileapi pdf …availableabsent from --help
gRPC PDF methodsavailableUnimplemented status
Everything elseavailableavailable
iText in outputyesno

CI publishes both ways on every push and fails if an iText assembly appears in the opt-out output, so this stays true rather than drifting.

Full detail, including the terms of every dependency, is in LICENSING.md.

Architecture

 ┌──────────────────┐ ┌────────────────────────┐
│ Core (MIT) │◄──│ Core.Pdf (AGPL) │
│ DOCX, images, │ │ PDF only — iText 7 │
│ email, hash, │ │ Separated so that │
│ PII, text, zip │ │ Core stays MIT │
└────────┬─────────┘ └───────────┬────────────┘
│ │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼──────┐ ┌────▼────┐ ┌──────▼────┐
│ HTTP API │ │ CLI │ │ gRPC │
│ (MinAPIs) │ │ (rich) │ │ (grpc/) │
└────────────┘ └─────────┘ └───────────┘

Design Principles

  • Pure library — Core has zero Microsoft.Extensions.* dependencies; usable in console apps, desktop apps, cloud functions, anywhere
  • Ports & adapters — API and CLI are thin wrappers calling static Core services
  • Thread-safe — All Core operations are static and proven safe under concurrent load
  • Terse & functional — records, pattern matching, expression-bodied lambdas
  • Observability — OpenTelemetry traces + metrics + logs (API layer), Serilog structured logging
  • Cloud-ready — Docker multi-stage build, deployable to Azure Container Apps, App Service, AKS

Features

CategoryDescriptionStatus
PDF Toolkit30+ operations: metrics, metadata, extract text/pages/markdown/annotations/bookmarks, merge, split, rotate, reorder, delete pages, watermark, encrypt/decrypt, compress, page numbers, form fields, health checkImplemented
DOCX ProcessingMetrics, metadata, paragraph extraction, markdown conversion, search, health check, metadata removalImplemented
Image ProcessingResize, rotate, crop, convert (PNG/JPEG/WebP/BMP/GIF/TIFF), thumbnail, flip, blur, grayscale, compress, strip metadata, EXIF, auto-orient, invert, edge detect, equalize, color palette, base64Implemented
PII DetectionRegex-based scanning for emails, credit cards (Visa/MC/Amex), IBANs, SSNs, phone numbers, national IDs (US/ES/FR/DE/IT/UK/PT), dates of birthImplemented
Text AnalysisMetrics, search (literal + regex), readability scores (Flesch, Gunning Fog, SMOG)Implemented
Email Parsing.eml (RFC 5322 / MIME) parsing with MimeKit — headers, body, attachmentsImplemented
File HashingSHA-256, MD5, SHA-1, composite hashImplemented
ZIP InspectionList entries with sizes, compression, CRC-32Implemented
Share LinksCRUD with SQLite persistence, password protection, expiry, recipient restrictions — all enforced on accessImplemented
Async JobsCompare, summarize, batch — async job pattern with in-memory store and pollingJob pattern ready
Document ComparisonLexical, semantic, summary modes with cross-format supportJob pattern ready; LLM pipeline pending
AI AnalysisSummarization, classification, Q&A via LLMEndpoints ready; LLM pipeline pending
Image AI (LLM)Describe, tag, detect objects, moderate, extract data, visual Q&AEndpoints ready; LLM pipeline pending
GeospatialKML, KMZ, GeoJSON, Shapefile, DXF feature extractionEndpoint stubs; NetTopologySuite integration pending
VideoContainer/track metadata extractionStub; MediaInfo integration pending

Endpoint Families (~100 routes)

FamilyPrefixRoutesDescription
Health/health1Liveness check
Auth/api/auth/*1JWT cookie management
PDF/pdf/*30+Full PDF manipulation toolkit
DOCX/docx/*10Word document processing
TXT/txt/*2Plain text metrics and search
Image/image/*13Image manipulation (ImageSharp)
Image AI/image-ai/*14Local + LLM-powered image analysis
Hash/hash1Multi-algorithm file hashing
PII/pii/detect1PII detection (20+ regex patterns)
Search/search1Universal cross-format search
Readability/readability1Flesch, Gunning Fog, SMOG scores
ZIP/zip/inspect1Archive inspection
Email/email/parse1Parse .eml/.msg files
Compare/compare2Async document comparison
Summarize/summarize2Async document summarization
Batch/batch/*2Parallel multi-file processing
Classify/classify1Document type classification
Q&A/qa1Document-grounded Q&A
Share/share/*4Shareable report links
Geospatial/geospatial/*4Feature extraction from geo formats
Video/video/metadata1Container/track metadata
Markdown/markdown/to-pdf1Markdown to PDF conversion
Strip/strip/images1Remove images from documents
Redact/redact, /pdf/redact2Text redaction — not implemented, returns 501

Authentication

All endpoints require a JWT token as an auth_token httpOnly cookie.

Public paths (no auth): /health, /docs, /swagger, /openapi.json, /api/auth/set-cookie

Processing Modes

ModePatternDescription
SyncDirect responseFast operations (<2s): metrics, hash, search, text extraction
AsyncPOST → 202 + job_id, GET → pollSlow/LLM operations: compare, summarize
BatchPOST /batch/{op} → 202Parallel multi-file with per-file webhooks

Error Responses

All errors follow RFC 9457 Problem Details:

{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "File must be a PDF",
"instance": "/pdf/metrics"
}

Confidentiality Routing

LevelDescription
privateDocuments processed locally via OpenWebUI/Ollama (default)
publicDocuments sent to cloud LLM (e.g. OpenAI GPT-4o)
air_gappedFully offline processing, no LLM calls

Quick Start

Docker

docker-compose up --build
# API at http://localhost:9401# Swagger UI at http://localhost:9401/swagger

Local Development

dotnet restore
dotnet build
dotnet run --project src/Aelena.FileApi.Api -f net10.0

The projects multi-target net10.0 and net11.0, so run, publish, and a single-project build need -f. Without the .NET 11 SDK installed, build the LTS target alone:

dotnet build -p:TargetFrameworks=net10.0

Run Tests

dotnet test

dotnet test reports 580 passing. That is 290 distinct tests — 193 unit plus 97 endpoint — run once against each target framework:

SuiteTestsFrameworksExecutions
Aelena.FileApi.Tests (unit, concurrency)193net10.0, net11.0386
Aelena.FileApi.Api.Tests (endpoint, error-contract, auth, share)97net10.0, net11.0194
Main solution total290580
Aelena.FileApi.Grpc.Tests (separate solution)8net10.0, net11.016

To run a single framework: dotnet test -f net10.0.

Build NuGet Package

dotnet pack src/Aelena.FileApi.Core -c Release -o artifacts/

CLI — Rich Console Interface

The fileapi CLI provides direct access to all Core operations from the terminal, with rich Spectre.Console output.

Install / Run

# Run via dotnet
dotnet run --project src/Aelena.FileApi.Cli -f net10.0 -- <command> [options]
# Or build and use directly
dotnet build src/Aelena.FileApi.Cli -c Release -f net10.0
./src/Aelena.FileApi.Cli/bin/Release/net10.0/fileapi <command>

Commands

# PDF operations
fileapi pdf metrics document.pdf # Page count, words, OCR needs, signatures
fileapi pdf extract-text document.pdf # Extract all text
fileapi pdf metadata document.pdf # Title, author, dates, version
fileapi pdf health document.pdf # Corruption, fonts, JavaScript checks
fileapi pdf merge -o merged.pdf a.pdf b.pdf # Merge PDFs
fileapi pdf rotate --angle 90 doc.pdf # Rotate pages
fileapi pdf encrypt --password s3cret doc.pdf # Password protect
fileapi pdf decrypt --password s3cret doc.pdf # Remove protection
fileapi pdf search --query "contract" doc.pdf # Search text# DOCX operations
fileapi docx metrics report.docx # Paragraphs, words, tables, images
fileapi docx metadata report.docx # Title, author, revision
fileapi docx markdown report.docx # Convert to Markdown
fileapi docx health report.docx # Tracked changes, macros# Image operations
fileapi image exif photo.jpg # EXIF metadata + GPS
fileapi image resize -w 800 photo.jpg # Resize with aspect ratio
fileapi image rotate --angle 90 photo.jpg # Rotate
fileapi image convert --format webp photo.png # Format conversion
fileapi image grayscale photo.jpg # Grayscale
fileapi image blur --radius 5 photo.jpg # Gaussian blur
fileapi image compress --quality 60 photo.jpg # JPEG compression# Utilities
fileapi hash invoice.pdf # SHA-256, MD5, SHA-1
fileapi readability essay.txt # Flesch, Gunning Fog, SMOG scores
fileapi pii detect contract.pdf # Detect emails, SSNs, credit cards
fileapi txt metrics notes.txt # Line, word, token counts
fileapi txt search --query "TODO" notes.txt
fileapi zip archive.zip # List entries with sizes
fileapi email message.eml # Parse headers, body, attachments

Configuration

All settings via environment variables or appsettings.json (section AppSettings):

VariableDefaultDescription
AppSettings__PublicLlmBaseUrlhttps://api.openai.com/v1Cloud LLM endpoint
AppSettings__PublicLlmApiKeyCloud LLM API key
AppSettings__PublicLlmModelgpt-4oCloud LLM model
AppSettings__PrivateLlmBaseUrlhttp://host.docker.internal:3000/api/v1Local LLM endpoint
AppSettings__PrivateLlmApiKeyLocal LLM API key
AppSettings__JwtSecretKeyyour-secret-key-change-in-productionJWT signing key. The default is a placeholder — outside Development the app refuses to start until it is replaced with a random value of at least 32 bytes.
AppSettings__JwtAlgorithmHS256Signing algorithm; the only one accepted on validation. HS256, HS384, or HS512.
AppSettings__CorsOriginshttp://localhost:9600Allowed CORS origins
AppSettings__MaxRequestsPerDay0 (unlimited)Daily request cap per user
AppSettings__MaxFileSizeBytes0 (unlimited)Max upload size
OpenTelemetry__EndpointOTLP exporter endpoint

Solution Structure

file-api/
├── Aelena.FileApi.sln
├── Directory.Build.props # net10.0, C# 14, nullable, TreatWarningsAsErrors
├── Directory.Packages.props # Central Package Management: one pinned version per package
├── Directory.Build.targets # Test-project settings (imports after each csproj)
├── docker-compose.yml
├── prompts/ # Scriban templates for LLM prompts
│
├── src/
│ ├── Aelena.FileApi.Core/ # NuGet library — ALL business logic
│ │ ├── Models/ # 60+ C# record types
│ │ ├── Enums/ # Confidentiality, CompareMode, DocumentType, etc.
│ │ ├── Errors/ # FileApiException → ProblemDetails
│ │ ├── Abstractions/ # ILlmClient, ILlmClientFactory
│ │ └── Services/
│ │ ├── Pdf/ # PdfService (iText7) — 23 static methods
│ │ ├── Docx/ # DocxService (Open XML SDK) — 10 methods
│ │ ├── Image/ # ImageService (ImageSharp) — 18 methods
│ │ ├── Llm/ # LlmClientFactory, PromptRenderer, OpenAiCompatibleClient
│ │ ├── Jobs/ # InMemoryJobStore<T>
│ │ ├── Persistence/ # ShareRepository (SQLite/Dapper)
│ │ └── Common/ # TextAnalysis, PageRangeParser, TextSearch, HashService,
│ │ # TxtService, ZipService, ReadabilityService, PiiService,
│ │ # EmailService, UserRegex
│ │
│ ├── Aelena.FileApi.Core.Pdf/ # AGPL — PDF only, the sole iText consumer
│ │ └── Services/Pdf/ # PdfService. Kept out of Core so Core is MIT.
│ │
│ ├── Aelena.FileApi.Api/ # HTTP wrapper (Minimal APIs)
│ │ ├── Program.cs # Top-level: DI, Serilog, OpenTelemetry, all routes
│ │ ├── Endpoints/ # 22 endpoint files + FormFileExtensions
│ │ ├── Middleware/ # Exception, Audit, AuthRateLimit
│ │ ├── Logging/ # Source-generated LoggerMessage delegates
│ │ ├── Auth/ # JwtCookieAuth
│ │ ├── Services/ # WebhookService
│ │ └── Configuration/ # AppSettings
│ │
│ └── Aelena.FileApi.Cli/ # `fileapi` console app (System.CommandLine 2.0)
│ ├── Commands/ # One file per command group
│ └── Helpers/ # Output, ExitCode, CommandExtensions, Format
│
├── grpc/ # gRPC port — its own solution, same Core
│ ├── src/Aelena.FileApi.Grpc/
│ └── tests/
│
└── tests/
├── Aelena.FileApi.Tests/ # 193 unit tests (xUnit + AwesomeAssertions)
└── Aelena.FileApi.Api.Tests/ # 97 endpoint, error-contract, auth, and share tests

Tech Stack

ComponentLibrary
PDFiText7 9.x (AGPL — isolated in Aelena.FileApi.Core.Pdf)
DOCX/PPTXDocumentFormat.OpenXml 3.x
ImagesSixLabors.ImageSharp 3.x
EmailMimeKit 4.x
CLISystem.CommandLine 2.0 + Spectre.Console
gRPCGrpc.AspNetCore 2.x
LLMOpenAI-compatible HTTP client
TemplatesScriban 7.x
TokensSharpToken 2.x
SQLiteMicrosoft.Data.Sqlite + Dapper
LoggingSerilog + OpenTelemetry
TestingxUnit + AwesomeAssertions + NSubstitute

Package versions are pinned centrally in Directory.Packages.props. Nothing floats — NuGetAudit runs at low severity across the whole graph and fails the build on a known advisory.

Deferred to Separate Projects

DependencyStatusNotes
imagehashSeparate NuGetPerceptual hashing (aHash/pHash/dHash/wHash)
doclingSeparate projectIBM ML document parser — no .NET equivalent
GDALPartialUsing NetTopologySuite + LibTiff.NET instead

Releasing

Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored. The job needs id-token: write, which release.yml declares.

One-time setup on nuget.org (Account → Trusted Publishing), one policy per repo:

FieldValue
Repository Owneraelena
Repositoryfile-api
Workflow Filerelease.yml (file name only, no path)
Environmentproduction (the workflow declares it; the two must match)
Glob Patterns and PackagesAelena.FileApi.*

Create a GitHub environment named production in the repository, and add a secret NUGET_USER holding the nuget.org profile name (not an email address).

A policy is bound to one repository, so each repository needs its own.

To cut a release: set <Version> in the three packable csproj files, commit, then

git tag v0.3.0 && git push origin v0.3.0

The workflow tests, packs, re-checks the MIT/AGPL boundary, refuses to continue if the tag does not match the package version, publishes, and opens a GitHub release. workflow_dispatch runs it as a dry run without pushing.

Changelog

See CHANGELOG.md. The 0.3.0 entry documents the modernization pass: the .NET 10/11 retarget, and the bugs it turned up — including a redaction endpoint that returned unredacted documents and share links that ignored their own passwords and expiry.

License

Two licences, by package — see the licensing section above.

  • Aelena.FileApi.Core — MIT, see LICENSE. No copyleft dependencies.
  • Aelena.FileApi.Core.Pdf and the fileapi CLI — AGPL-3.0-or-later, inherited from iText 7. The repository's own source is MIT; the AGPL obligation comes from the dependency, and applies to anything that ships or serves it.

Other dependencies keep their own terms. SixLabors.ImageSharp is under the Six Labors Split License, and the relevant clause is favourable: it grants Apache 2.0 to anyone "consuming the Work as a Transitive Package Dependency". Installing Aelena.FileApi.Core brings ImageSharp in indirectly, which is exactly that — so consumers get it under Apache 2.0 whatever their size. The commercial threshold applies to a direct dependency on ImageSharp, not to users of this package.

About

Multipurpose API for operations on multiple types of files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages