Skip to content

Roly67/silver-fiesta

Repository files navigation

📄 File Conversion API

Transform documents at scale with a modern, secure REST API

.NET PostgreSQL Docker License

CI codecov Docs


Getting Started · API Reference · Architecture · Configuration



✨ Features

🔄 Document Conversion

Convert HTML, Markdown, or URLs to PDF. Convert Markdown to HTML. Convert DOCX (Word) and XLSX (Excel) to PDF using LibreOffice. Capture HTML pages or URLs as PNG, JPEG, or WebP screenshots. Render PDF pages to PNG, JPEG, or WebP images. Extract text from PDF documents. Transform images between PNG, JPEG, and WebP formats with resize and quality options.

🔐 Dual Authentication

Secure your endpoints with JWT Bearer tokens for user sessions or API keys for service-to-service communication.

🏗️ Clean Architecture

Four-layer architecture (Domain, Application, Infrastructure, API) with CQRS pattern powered by MediatR.

📊 Job Tracking

Monitor conversion progress, access history, and download results with comprehensive job management.

🔔 Webhook Notifications

Receive HTTP callbacks when conversion jobs complete or fail. Configure per-request webhook URLs for real-time integration.

🚦 Per-User Rate Limiting

Tiered rate limiting with Free, Basic, Premium, and Unlimited tiers. Admins can configure per-user limits, set custom overrides, and exempt admin users entirely.

🧹 Auto-Cleanup

Background service automatically deletes expired jobs. Configurable retention periods for completed and failed jobs prevent database bloat.

🐳 Multi-Arch Docker

Production-ready containers for both AMD64 and ARM64 architectures, available from GitHub Container Registry.

🖼️ Image Conversions

Convert images between PNG, JPEG, and WebP formats using ImageSharp. Supports resize, quality settings, and maintains aspect ratio.

📈 Prometheus Metrics

Built-in /metrics endpoint exposes conversion statistics, HTTP request metrics, and system health for Grafana dashboards.

🩺 Enhanced Health Checks

Detailed /health endpoint reports database connectivity, Chromium availability, and disk space status with degraded state detection.

📋 Production Ready

Comprehensive logging with Serilog, Swagger documentation, and zero-warning builds with StyleCop analyzers.

📦 Batch Conversions

Process up to 20 conversion requests in a single API call with partial success support and per-item error reporting.

🔗 PDF Operations

Merge multiple PDFs, split by page ranges, add watermarks, and protect with passwords using PdfSharpCore.

👑 Admin API

Role-based admin endpoints for user management, job statistics, and system monitoring. Disable users, reset API keys, and grant admin privileges.

📝 Conversion Templates

Save and reuse conversion settings with named templates. Define page sizes, margins, watermarks, and other options for consistent output.

🔭 OpenTelemetry Tracing

Distributed tracing with OpenTelemetry for end-to-end request visibility. Export traces to Jaeger, Zipkin, or any OTLP-compatible backend.

🛡️ Input Validation

Configurable file size limits, URL allowlist/blocklist for SSRF protection, and content type validation to ensure secure input handling.

📊 Usage Quotas

Per-user monthly limits on conversions and bytes processed. Admins can view and adjust quotas, and are optionally exempt from limits.

☁️ Cloud Storage

Store conversion outputs in S3-compatible storage (AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2). Backward compatible with database storage.


🚀 Getting Started

Prerequisites

Requirement Version
.NET SDK 10.0+
PostgreSQL 16+
Docker Optional

Quick Start with Docker

docker-compose up -d

API Endpoint: http://localhost:5000

The development setup uses HTTP only. For production, configure HTTPS with proper certificates.

Pull from GitHub Container Registry:

# Multi-arch image (amd64/arm64)
docker pull ghcr.io/roly67/silver-fiesta:latest
📋 Manual Setup

1. Start PostgreSQL

docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=fileconversion \
  -p 5432:5432 \
  postgres:16-alpine

2. Configure Connection String

Update appsettings.Development.json:

{
  "ConnectionStrings": {
    "Default": "Host=localhost;Database=fileconversion;Username=postgres;Password=postgres"
  }
}

3. Run the API

cd src/FileConversionApi.Api
dotnet run
🧪 Running Tests
# Run tests with coverage
dotnet test --collect:"XPlat Code Coverage"

# Generate coverage report
dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage



📡 API Reference

Authentication

POST /api/v1/auth/register Create a new account
POST /api/v1/auth/login Authenticate and receive tokens
POST /api/v1/auth/refresh Refresh an expired access token
Register / Login Request
{
  "email": "user@example.com",
  "password": "SecurePassword123!"
}
Token Response
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "base64-encoded-refresh-token",
  "tokenType": "Bearer",
  "expiresIn": 3600
}

🔑 Using JWT Bearer Tokens

Include the access token in the Authorization header for protected endpoints:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Token Type Lifetime Refresh Strategy
Access Token 60 min Use refresh token endpoint
Refresh Token 7 days Re-authenticate
JWT Claims Reference
Claim Description
sub User ID (GUID)
email User email address
jti Unique token identifier
exp Expiration timestamp

Decode tokens locally:

echo "<token>" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq

🔐 API Key Authentication

Alternative to JWT for service-to-service communication:

X-API-Key: your-api-key-here

File Conversion

POST /api/v1/convert/html-to-pdf Convert HTML/URL to PDF
POST /api/v1/convert/markdown-to-pdf Convert Markdown to PDF
POST /api/v1/convert/markdown-to-html Convert Markdown to HTML
POST /api/v1/convert/image Convert image formats (PNG, JPEG, WebP)
POST /api/v1/convert/html-to-image Convert HTML/URL to image (PNG, JPEG, WebP)
POST /api/v1/convert/pdf-to-image Convert PDF pages to image (PNG, JPEG, WebP)
POST /api/v1/convert/pdf-to-text Extract text from PDF documents
POST /api/v1/convert/docx-to-pdf Convert DOCX (Word) to PDF
POST /api/v1/convert/xlsx-to-pdf Convert XLSX (Excel) to PDF
POST /api/v1/convert/pdf/merge Merge multiple PDFs into one
POST /api/v1/convert/pdf/split Split PDF into multiple files
POST /api/v1/convert/batch Batch convert multiple files
GET /api/v1/convert/{id} Get job status
GET /api/v1/convert/{id}/download Download result
GET /api/v1/convert/history List conversion history
Convert HTML to PDF
POST /api/v1/convert/html-to-pdf
Authorization: Bearer <token>
Content-Type: application/json

{
  "htmlContent": "<html><body><h1>Hello World</h1></body></html>",
  "fileName": "document.pdf",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "pageSize": "A4",
    "landscape": false,
    "marginTop": 10,
    "marginBottom": 10,
    "marginLeft": 10,
    "marginRight": 10,
    "waitForJavaScript": true,
    "javaScriptTimeout": 30000
  }
}

Convert from URL:

{
  "url": "https://example.com",
  "fileName": "example.pdf"
}
Convert Markdown to PDF
POST /api/v1/convert/markdown-to-pdf
Authorization: Bearer <token>
Content-Type: application/json

{
  "markdown": "# Hello World\n\nThis is **bold** and *italic* text.",
  "fileName": "document.pdf",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "pageSize": "A4",
    "landscape": false,
    "marginTop": 25,
    "marginBottom": 25,
    "marginLeft": 20,
    "marginRight": 20
  }
}

Supported Markdown features:

  • Headings, paragraphs, lists
  • Bold, italic, strikethrough
  • Code blocks with syntax highlighting
  • Tables, blockquotes
  • Links and images
PDF Watermarking

Add watermarks to PDF output by including the watermark option in any PDF conversion request:

{
  "htmlContent": "<html><body><h1>My Document</h1></body></html>",
  "fileName": "document.pdf",
  "options": {
    "watermark": {
      "text": "CONFIDENTIAL",
      "fontSize": 48,
      "fontFamily": "Helvetica",
      "color": "#FF0000",
      "opacity": 0.3,
      "rotation": -45,
      "position": "Center",
      "allPages": true
    }
  }
}

Watermark Options:

Option Type Default Description
text string required The watermark text
fontSize int 48 Font size in points
fontFamily string Helvetica Font family name
color string #808080 Color in hex format
opacity double 0.3 Opacity (0.0 to 1.0)
rotation double -45 Rotation angle in degrees
position string Center Position on page
allPages bool true Apply to all pages
pageNumbers int[] null Specific pages (if allPages is false)

Position Values: Center, TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight, Tile

PDF Password Protection

Encrypt PDF output with passwords and set permissions by including the passwordProtection option in any PDF conversion request:

{
  "htmlContent": "<html><body><h1>Secure Document</h1></body></html>",
  "fileName": "document.pdf",
  "options": {
    "passwordProtection": {
      "userPassword": "viewerpass123",
      "ownerPassword": "adminpass456",
      "allowPrinting": true,
      "allowCopyingContent": false,
      "allowModifying": false,
      "allowAnnotations": false
    }
  }
}

Password Protection Options:

Option Type Default Description
userPassword string required Password to open/view the PDF
ownerPassword string null Password for full access (defaults to userPassword)
allowPrinting bool true Allow printing the document
allowCopyingContent bool true Allow copying text/images
allowModifying bool false Allow modifying the document
allowAnnotations bool false Allow adding annotations

Notes:

  • The userPassword is required to enable encryption
  • If ownerPassword is not specified, it defaults to the userPassword
  • Owner password grants full access regardless of permission settings
  • Can be combined with watermarking for additional document protection
PDF Merge

Merge multiple PDF documents into a single PDF:

POST /api/v1/convert/pdf/merge
Authorization: Bearer <token>
Content-Type: application/json

{
  "pdfDocuments": [
    "base64-encoded-pdf-1",
    "base64-encoded-pdf-2",
    "base64-encoded-pdf-3"
  ],
  "fileName": "merged.pdf",
  "webhookUrl": "https://example.com/webhooks/conversion"
}

Notes:

  • At least two PDF documents are required
  • PDFs are merged in the order provided
  • Output is a single merged PDF file
PDF Split

Split a PDF document into multiple PDFs:

POST /api/v1/convert/pdf/split
Authorization: Bearer <token>
Content-Type: application/json

{
  "pdfData": "base64-encoded-pdf",
  "fileName": "document.pdf",
  "options": {
    "pageRanges": ["1-3", "5", "7-10"],
    "splitIntoSinglePages": false
  },
  "webhookUrl": "https://example.com/webhooks/conversion"
}

Split Options:

Option Type Default Description
pageRanges string[] null Page ranges to extract (e.g., "1-3", "5")
splitIntoSinglePages bool false Split into individual pages

Notes:

  • Output is a ZIP file containing the split PDFs
  • If no options specified, defaults to splitting into single pages
  • Page numbers are 1-based
  • Page ranges use format "start-end" (e.g., "1-5") or single page (e.g., "3")
Convert Markdown to HTML
POST /api/v1/convert/markdown-to-html
Authorization: Bearer <token>
Content-Type: application/json

{
  "markdown": "# Hello World\n\nThis is **bold** and *italic* text.",
  "fileName": "document.html",
  "webhookUrl": "https://example.com/webhooks/conversion"
}

Returns styled HTML with professional CSS including:

  • Typography and code syntax highlighting
  • Table styling and blockquotes
  • Responsive design
Convert Image Formats
POST /api/v1/convert/image
Authorization: Bearer <token>
Content-Type: application/json

{
  "imageData": "base64-encoded-image-data",
  "sourceFormat": "png",
  "targetFormat": "jpeg",
  "fileName": "photo.png",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "imageWidth": 800,
    "imageHeight": 600,
    "imageQuality": 85
  }
}

Supported formats: PNG, JPEG, WebP, GIF, BMP

Options:

Option Description
imageWidth Target width in pixels (maintains aspect ratio)
imageHeight Target height in pixels (maintains aspect ratio)
imageQuality Quality 1-100 (for JPEG/WebP)
Convert HTML to Image (Screenshot)

Capture HTML content or a URL as a PNG, JPEG, or WebP screenshot:

POST /api/v1/convert/html-to-image
Authorization: Bearer <token>
Content-Type: application/json

{
  "htmlContent": "<html><body><h1>Hello World</h1></body></html>",
  "fileName": "screenshot.html",
  "targetFormat": "png",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "fullPage": true,
    "viewportWidth": 1920,
    "viewportHeight": 1080
  }
}

Convert from URL:

{
  "url": "https://example.com",
  "targetFormat": "jpeg",
  "fileName": "webpage.html",
  "options": {
    "fullPage": false,
    "viewportWidth": 1280,
    "viewportHeight": 720
  }
}

Supported output formats: PNG (default), JPEG, WebP

Options:

Option Type Default Description
fullPage bool true Capture full scrollable page or just the viewport
viewportWidth int 1920 Browser viewport width in pixels
viewportHeight int 1080 Browser viewport height in pixels

Notes:

  • Uses Puppeteer (headless Chromium) for rendering
  • JavaScript is executed before capture
  • Can be combined with webhook notifications for async processing
Convert PDF to Image

Render PDF pages to PNG, JPEG, or WebP images:

POST /api/v1/convert/pdf-to-image
Authorization: Bearer <token>
Content-Type: application/json

{
  "pdfData": "base64-encoded-pdf-data",
  "fileName": "document.pdf",
  "targetFormat": "png",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "dpi": 150,
    "pageNumber": 1,
    "pdfPassword": "optional-password",
    "imageQuality": 90
  }
}

Supported output formats: PNG (default), JPEG, WebP

Options:

Option Type Default Description
dpi int 150 Resolution in dots per inch (72-600)
pageNumber int null Specific page to render (1-based). If null, all pages are rendered
pdfPassword string null Password for encrypted PDFs
imageQuality int 90 Output quality (1-100, for JPEG/WebP)

Notes:

  • Single page PDFs or requests with pageNumber return an image file
  • Multi-page PDFs (without pageNumber) return a ZIP file containing all pages
  • Pages are named page_0001.{format}, page_0002.{format}, etc.
  • Uses PDFtoImage (PDFium) for high-quality rendering
Extract Text from PDF

Extract text content from PDF documents:

POST /api/v1/convert/pdf-to-text
Authorization: Bearer <token>
Content-Type: application/json

{
  "pdfData": "base64-encoded-pdf-data",
  "fileName": "document.pdf",
  "pageNumber": 1,
  "password": "optional-password",
  "webhookUrl": "https://example.com/webhooks/conversion"
}

Options:

Option Type Default Description
pageNumber int null Specific page to extract (1-based). If null, extracts from all pages
password string null Password for encrypted PDFs

Notes:

  • Returns a plain text (.txt) file with the extracted content
  • Multi-page PDFs include page separators (--- Page N ---)
  • Uses PdfPig library for accurate text extraction
  • Preserves paragraph structure and word order
Convert DOCX to PDF
POST /api/v1/convert/docx-to-pdf
Authorization: Bearer <token>
Content-Type: application/json

{
  "documentData": "base64-encoded-docx-data",
  "fileName": "report.docx",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "watermark": {
      "text": "DRAFT",
      "opacity": 0.2
    },
    "passwordProtection": {
      "userPassword": "secret123"
    }
  }
}

Notes:

  • DOCX file should be base64 encoded
  • Supports all PDF options (watermark, password protection)
  • Uses LibreOffice for high-fidelity conversion
Convert XLSX to PDF
POST /api/v1/convert/xlsx-to-pdf
Authorization: Bearer <token>
Content-Type: application/json

{
  "spreadsheetData": "base64-encoded-xlsx-data",
  "fileName": "financial-report.xlsx",
  "webhookUrl": "https://example.com/webhooks/conversion",
  "options": {
    "watermark": {
      "text": "CONFIDENTIAL",
      "opacity": 0.3
    }
  }
}

Notes:

  • XLSX file should be base64 encoded
  • Supports all PDF options (watermark, password protection)
  • Uses LibreOffice for high-fidelity conversion
Batch Conversion

Convert multiple files in a single request (max 20 items):

POST /api/v1/convert/batch
Authorization: Bearer <token>
Content-Type: application/json

{
  "items": [
    {
      "type": "html-to-pdf",
      "htmlContent": "<html><body>Document 1</body></html>",
      "fileName": "doc1.html"
    },
    {
      "type": "markdown-to-pdf",
      "markdown": "# Document 2",
      "fileName": "doc2.md"
    },
    {
      "type": "image",
      "imageData": "base64-encoded-data",
      "sourceFormat": "png",
      "targetFormat": "jpeg"
    }
  ],
  "webhookUrl": "https://example.com/webhook"
}

Response (200 OK):

{
  "totalItems": 3,
  "successCount": 3,
  "failureCount": 0,
  "results": [
    {
      "index": 0,
      "success": true,
      "job": { "id": "...", "status": "Completed", ... }
    },
    {
      "index": 1,
      "success": true,
      "job": { "id": "...", "status": "Completed", ... }
    },
    {
      "index": 2,
      "success": true,
      "job": { "id": "...", "status": "Completed", ... }
    }
  ]
}

Supported types: html-to-pdf, markdown-to-pdf, markdown-to-html, image


Health & Monitoring

GET /health Detailed health status (DB, Chromium, disk)
GET /metrics Prometheus metrics endpoint
Health Check Response
{
  "status": "Healthy",
  "totalDuration": "00:00:00.1234567",
  "entries": {
    "database": {
      "status": "Healthy",
      "description": "PostgreSQL connection successful"
    },
    "chromium": {
      "status": "Healthy",
      "description": "Chromium is available for PDF generation"
    },
    "disk_space": {
      "status": "Healthy",
      "description": "Disk space: 50.2 GB free (62.5%)"
    }
  }
}

Status values: Healthy, Degraded, Unhealthy

Prometheus Metrics

The /metrics endpoint exposes:

Metric Type Description
conversion_requests_total Counter Total conversion requests by format and status
conversion_duration_seconds Histogram Conversion duration by format
http_requests_total Counter HTTP requests by method, path, status
http_request_duration_seconds Histogram HTTP request duration

Example Prometheus scrape config:

scrape_configs:
  - job_name: 'fileconversion-api'
    static_configs:
      - targets: ['localhost:5000']
    metrics_path: '/metrics'

Usage Quotas

GET /api/v1/quota Get current user's quota
Quota Response
GET /api/v1/quota
Authorization: Bearer <token>

Response:
{
  "year": 2026,
  "month": 1,
  "conversionsUsed": 45,
  "conversionsLimit": 1000,
  "remainingConversions": 955,
  "bytesProcessed": 52428800,
  "bytesLimit": 1073741824,
  "remainingBytes": 1021313024,
  "isQuotaExceeded": false,
  "updatedAt": "2026-01-13T12:00:00Z"
}

Quota exceeded response (HTTP 429):

{
  "type": "https://httpstatuses.com/429",
  "title": "Quota Exceeded",
  "status": 429,
  "detail": "Monthly conversion limit exceeded: 1000/1000 conversions used."
}

Admin API

GET /api/v1/admin/users List all users (paginated)
GET /api/v1/admin/users/{id} Get user details
POST /api/v1/admin/users/{id}/disable Disable a user
POST /api/v1/admin/users/{id}/enable Enable a user
POST /api/v1/admin/users/{id}/reset-api-key Reset user's API key
POST /api/v1/admin/users/{id}/grant-admin Grant admin privileges
POST /api/v1/admin/users/{id}/revoke-admin Revoke admin privileges
GET /api/v1/admin/stats Get job statistics
GET /api/v1/admin/users/{id}/quota Get user's current quota
GET /api/v1/admin/users/{id}/quota/history Get user's quota history
PUT /api/v1/admin/users/{id}/quota Update user's quota limits
GET /api/v1/admin/users/{id}/rate-limits Get user's rate limit settings
PUT /api/v1/admin/users/{id}/rate-limits/tier Set user's rate limit tier
PUT /api/v1/admin/users/{id}/rate-limits/override/{policy} Set per-policy override
DELETE /api/v1/admin/users/{id}/rate-limits/overrides Clear all rate limit overrides
GET /api/v1/admin/rate-limits/tiers List available rate limit tiers
Admin Endpoints (requires Admin role)

Get Job Statistics:

GET /api/v1/admin/stats
Authorization: Bearer <admin-token>

Response:
{
  "totalJobs": 1500,
  "completedJobs": 1200,
  "failedJobs": 50,
  "pendingJobs": 250,
  "totalUsers": 45,
  "successRate": 80.0
}

List Users:

GET /api/v1/admin/users?page=1&pageSize=20
Authorization: Bearer <admin-token>

Response:
{
  "items": [
    {
      "id": "...",
      "email": "user@example.com",
      "isActive": true,
      "isAdmin": false,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "page": 1,
  "pageSize": 20,
  "totalCount": 45,
  "totalPages": 3
}

Conversion Templates

GET /api/v1/templates List user's templates (optional ?targetFormat= filter)
GET /api/v1/templates/{id} Get template details
POST /api/v1/templates Create new template
PUT /api/v1/templates/{id} Update template
DELETE /api/v1/templates/{id} Delete template
Template Examples

Create Template:

POST /api/v1/templates
Authorization: Bearer <token>

{
  "name": "A4 Landscape Report",
  "description": "Standard report format with company watermark",
  "targetFormat": "pdf",
  "options": {
    "pageSize": "A4",
    "landscape": true,
    "marginTop": 25,
    "marginBottom": 25,
    "watermark": {
      "text": "CONFIDENTIAL",
      "opacity": 0.2
    }
  }
}

List Templates:

GET /api/v1/templates?targetFormat=pdf
Authorization: Bearer <token>

Response:
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "A4 Landscape Report",
    "description": "Standard report format with company watermark",
    "targetFormat": "pdf",
    "options": { ... },
    "createdAt": "2026-01-13T12:00:00Z"
  }
]

Supported target formats: pdf, html, png, jpeg, webp, gif, bmp




🏛️ Architecture

┌─────────────────────────────────────────────────────────────────┐
│                          API Layer                              │
│              Controllers · Middleware · Configuration           │
├─────────────────────────────────────────────────────────────────┤
│                      Application Layer                          │
│            Commands · Queries · DTOs · Interfaces               │
├─────────────────────────────────────────────────────────────────┤
│                     Infrastructure Layer                        │
│          EF Core · Repositories · External Services             │
├─────────────────────────────────────────────────────────────────┤
│                        Domain Layer                             │
│            Entities · Value Objects · Domain Errors             │
└─────────────────────────────────────────────────────────────────┘
Project Structure
src/
├── FileConversionApi.Domain/          # Entities, value objects, errors
├── FileConversionApi.Application/     # Commands, queries, interfaces, DTOs
├── FileConversionApi.Infrastructure/  # EF Core, repositories, services
└── FileConversionApi.Api/             # Controllers, middleware, config

tests/
├── FileConversionApi.UnitTests/       # Unit tests
└── FileConversionApi.IntegrationTests/# Integration tests
Layer Responsibilities
Layer Responsibility Dependencies
Domain Business entities, value objects, domain errors None
Application Use cases (CQRS), DTOs, interface definitions Domain
Infrastructure Repositories, external services, data access Application, Domain
API Controllers, middleware, configuration All layers



⚙️ Configuration

JWT Settings
{
  "JwtSettings": {
    "Secret": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!",
    "Issuer": "FileConversionApi",
    "Audience": "FileConversionApi",
    "TokenExpirationMinutes": 60,
    "RefreshTokenExpirationDays": 7
  }
}
Puppeteer Settings
{
  "PuppeteerSettings": {
    "ExecutablePath": null,
    "Timeout": 30000
  }
}

Note: When ExecutablePath is null, PuppeteerSharp automatically downloads a compatible Chromium version. This is the recommended approach for Docker deployments.

LibreOffice Settings
{
  "LibreOfficeSettings": {
    "ExecutablePath": null,
    "TimeoutMs": 60000,
    "TempDirectory": null
  }
}
Setting Default Description
ExecutablePath null Path to LibreOffice executable (null = use system default)
TimeoutMs 60000 Conversion timeout in milliseconds
TempDirectory null Temp directory for conversion files (null = system temp)

Note: LibreOffice is used for DOCX and XLSX to PDF conversions. In Docker deployments, LibreOffice is installed automatically. For local development on Linux, install with: apt-get install libreoffice-writer libreoffice-calc.

Webhook Settings
{
  "WebhookSettings": {
    "TimeoutSeconds": 30,
    "MaxRetries": 3,
    "RetryDelayMilliseconds": 1000
  }
}

Note: Webhook notifications are sent when conversion jobs complete or fail. Failed webhook calls are retried automatically.

Rate Limiting Settings
{
  "RateLimiting": {
    "EnableRateLimiting": true,
    "ExemptAdmins": true,
    "UserSettingsCacheSeconds": 300,
    "StandardPolicy": {
      "PermitLimit": 100,
      "WindowMinutes": 60
    },
    "ConversionPolicy": {
      "PermitLimit": 50,
      "WindowMinutes": 60
    },
    "AuthPolicy": {
      "PermitLimit": 10,
      "WindowMinutes": 15
    },
    "Tiers": {
      "Free": {
        "StandardPolicy": { "PermitLimit": 100, "WindowMinutes": 60 },
        "ConversionPolicy": { "PermitLimit": 20, "WindowMinutes": 60 }
      },
      "Basic": {
        "StandardPolicy": { "PermitLimit": 500, "WindowMinutes": 60 },
        "ConversionPolicy": { "PermitLimit": 100, "WindowMinutes": 60 }
      },
      "Premium": {
        "StandardPolicy": { "PermitLimit": 2000, "WindowMinutes": 60 },
        "ConversionPolicy": { "PermitLimit": 500, "WindowMinutes": 60 }
      },
      "Unlimited": {
        "StandardPolicy": { "PermitLimit": 100000, "WindowMinutes": 60 },
        "ConversionPolicy": { "PermitLimit": 10000, "WindowMinutes": 60 }
      }
    }
  }
}
Setting Default Description
ExemptAdmins true Admin users bypass all rate limits
UserSettingsCacheSeconds 300 Cache duration for per-user settings

Rate Limit Tiers:

Tier Standard Policy Conversion Policy
Free 100 req/hr 20 req/hr
Basic 500 req/hr 100 req/hr
Premium 2000 req/hr 500 req/hr
Unlimited 100000 req/hr 10000 req/hr

Policies:

Policy Endpoints Notes
standard GET endpoints Per-user limits based on tier
conversion POST conversion Per-user limits based on tier
auth Authentication IP-based (not tier-based)

Note: Users start with the Free tier by default. Admins can upgrade user tiers or set custom per-user overrides that supersede tier defaults. When rate limited, the API returns HTTP 429 with a Retry-After header.

Job Cleanup Settings
{
  "JobCleanup": {
    "Enabled": true,
    "RunIntervalMinutes": 60,
    "CompletedJobRetentionDays": 7,
    "FailedJobRetentionDays": 30,
    "BatchSize": 100
  }
}
Setting Default Description
Enabled true Enable/disable the cleanup service
RunIntervalMinutes 60 How often to run cleanup
CompletedJobRetentionDays 7 Days to keep completed jobs
FailedJobRetentionDays 30 Days to keep failed jobs (longer for debugging)
BatchSize 100 Max jobs to delete per run

Note: The cleanup service runs as a background hosted service. Failed jobs are retained longer to allow for debugging issues.

OpenTelemetry Settings
{
  "OpenTelemetry": {
    "EnableTracing": true,
    "ServiceName": "FileConversionApi",
    "OtlpEndpoint": "http://localhost:4317",
    "ExportToConsole": false,
    "SamplingRatio": 1.0
  }
}
Setting Default Description
EnableTracing true Enable/disable distributed tracing
ServiceName FileConversionApi Service name in traces
OtlpEndpoint null OTLP exporter endpoint (e.g., Jaeger, Zipkin)
ExportToConsole false Output traces to console (dev only)
SamplingRatio 1.0 Trace sampling ratio (0.0-1.0)

Instrumented operations:

  • HTTP requests (ASP.NET Core)
  • Outbound HTTP calls (HttpClient)
  • Database queries (Entity Framework Core)
  • Conversion operations (custom spans)

Note: To view traces, run Jaeger locally: docker run -d -p 4317:4317 -p 16686:16686 jaegertracing/all-in-one:latest and set OtlpEndpoint to http://localhost:4317.

Admin Seed Settings
{
  "AdminSeed": {
    "Enabled": true,
    "Email": "admin@fileconversionapi.local",
    "Password": "Admin123!",
    "SkipIfAdminExists": true
  }
}
Setting Default Description
Enabled true Enable/disable admin seeding on startup
Email admin@fileconversionapi.local Default admin email address
Password Admin123! Default admin password (change in production!)
SkipIfAdminExists true Skip seeding if any admin user already exists

Security Note: Change the default password in production using environment variables: AdminSeed__Password. Disable admin seeding after initial setup by setting Enabled: false.

Usage Quotas Settings
{
  "UsageQuotas": {
    "Enabled": true,
    "DefaultMonthlyConversions": 1000,
    "DefaultMonthlyBytes": 1073741824,
    "AdminMonthlyConversions": 0,
    "AdminMonthlyBytes": 0,
    "ExemptAdmins": true
  }
}
Setting Default Description
Enabled true Enable/disable quota enforcement
DefaultMonthlyConversions 1000 Default monthly conversion limit per user
DefaultMonthlyBytes 1073741824 Default monthly bytes limit (1GB)
AdminMonthlyConversions 0 Admin monthly conversion limit (0 = unlimited)
AdminMonthlyBytes 0 Admin monthly bytes limit (0 = unlimited)
ExemptAdmins true Exempt admin users from quota checks

Note: Quotas reset monthly. When a user exceeds their quota, conversion requests return HTTP 429 with a detailed error message. Admins can view and adjust user quotas via the Admin API.

Input Validation Settings
{
  "InputValidation": {
    "Enabled": true,
    "MaxFileSizeBytes": 52428800,
    "MaxHtmlContentBytes": 10485760,
    "MaxMarkdownContentBytes": 5242880,
    "UrlValidation": {
      "Enabled": true,
      "UseAllowlist": false,
      "BlockPrivateIpAddresses": true,
      "Blocklist": ["localhost", "127.0.0.1", "10.*", "192.168.*"]
    },
    "ContentTypeValidation": {
      "Enabled": true
    }
  }
}
Setting Default Description
Enabled true Enable/disable input validation globally
MaxFileSizeBytes 52428800 Max file upload size (50MB)
MaxHtmlContentBytes 10485760 Max HTML content size (10MB)
MaxMarkdownContentBytes 5242880 Max Markdown content size (5MB)

URL Validation Settings:

Setting Default Description
UrlValidation.Enabled true Enable URL validation for HTML conversion
UrlValidation.UseAllowlist false Use allowlist mode (true) or blocklist mode (false)
UrlValidation.BlockPrivateIpAddresses true Block private/internal IP addresses (SSRF protection)
UrlValidation.Allowlist [] Allowed URL patterns (when UseAllowlist=true)
UrlValidation.Blocklist [...] Blocked URL patterns including localhost, private IPs

Content Type Validation:

Setting Default Description
ContentTypeValidation.Enabled true Enable content type validation
AllowedHtmlContentTypes text/html, text/plain, application/xhtml+xml Allowed MIME types for HTML
AllowedMarkdownContentTypes text/markdown, text/plain, text/x-markdown Allowed MIME types for Markdown
AllowedImageContentTypes image/jpeg, image/png, image/gif, image/webp, image/bmp, image/tiff Allowed MIME types for images

Security Note: The default blocklist includes localhost, loopback addresses, private IP ranges (10.x, 172.16-31.x, 192.168.x), link-local addresses, and cloud metadata endpoints to protect against SSRF attacks.

Cloud Storage Settings
{
  "CloudStorage": {
    "Enabled": false,
    "ServiceUrl": "https://s3.amazonaws.com",
    "BucketName": "file-conversion-outputs",
    "AccessKey": "",
    "SecretKey": "",
    "Region": "us-east-1",
    "ForcePathStyle": false,
    "PresignedUrlExpirationMinutes": 60
  }
}
Setting Default Description
Enabled false Enable/disable cloud storage (false = use database)
ServiceUrl https://s3.amazonaws.com S3 endpoint URL
BucketName file-conversion-outputs Storage bucket name
AccessKey AWS/S3 access key
SecretKey AWS/S3 secret key
Region us-east-1 AWS region
ForcePathStyle false Use path-style URLs (required for MinIO)
PresignedUrlExpirationMinutes 60 Presigned URL expiration time

S3-Compatible Providers:

Provider ServiceUrl Example ForcePathStyle
AWS S3 https://s3.amazonaws.com false
MinIO http://localhost:9000 true
DigitalOcean Spaces https://nyc3.digitaloceanspaces.com false
Cloudflare R2 https://account-id.r2.cloudflarestorage.com false

Note: When cloud storage is enabled, conversion outputs are stored in S3 instead of the database. Existing jobs with database storage continue to work (backward compatible). The job cleanup service automatically deletes cloud storage objects when jobs expire.




🚨 Error Handling

The API uses RFC 7807 Problem Details for standardized error responses:

{
  "type": "https://tools.ietf.org/html/rfc7807",
  "title": "Validation Error",
  "status": 400,
  "detail": "One or more validation errors occurred.",
  "errors": {
    "email": ["Email is required"]
  }
}



📄 License

MIT © 2026


Built with ❤️ using .NET 10 and Clean Architecture

About

Production-ready .NET 10 File Conversion API with Clean Architecture, JWT authentication, HTML-to-PDF conversion using PuppeteerSharp, and comprehensive CI/CD pipelines.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors