Transform documents at scale with a modern, secure REST API
Getting Started · API Reference · Architecture · Configuration
|
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. |
Secure your endpoints with JWT Bearer tokens for user sessions or API keys for service-to-service communication. |
|
Four-layer architecture (Domain, Application, Infrastructure, API) with CQRS pattern powered by MediatR. |
Monitor conversion progress, access history, and download results with comprehensive job management. |
|
Receive HTTP callbacks when conversion jobs complete or fail. Configure per-request webhook URLs for real-time integration. |
Tiered rate limiting with Free, Basic, Premium, and Unlimited tiers. Admins can configure per-user limits, set custom overrides, and exempt admin users entirely. |
|
Background service automatically deletes expired jobs. Configurable retention periods for completed and failed jobs prevent database bloat. |
Production-ready containers for both AMD64 and ARM64 architectures, available from GitHub Container Registry. |
|
Convert images between PNG, JPEG, and WebP formats using ImageSharp. Supports resize, quality settings, and maintains aspect ratio. |
Built-in |
|
Detailed |
Comprehensive logging with Serilog, Swagger documentation, and zero-warning builds with StyleCop analyzers. |
|
Process up to 20 conversion requests in a single API call with partial success support and per-item error reporting. |
Merge multiple PDFs, split by page ranges, add watermarks, and protect with passwords using PdfSharpCore. |
|
Role-based admin endpoints for user management, job statistics, and system monitoring. Disable users, reset API keys, and grant admin privileges. |
Save and reuse conversion settings with named templates. Define page sizes, margins, watermarks, and other options for consistent output. |
|
Distributed tracing with OpenTelemetry for end-to-end request visibility. Export traces to Jaeger, Zipkin, or any OTLP-compatible backend. |
Configurable file size limits, URL allowlist/blocklist for SSRF protection, and content type validation to ensure secure input handling. |
|
Per-user monthly limits on conversions and bytes processed. Admins can view and adjust quotas, and are optionally exempt from limits. |
Store conversion outputs in S3-compatible storage (AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2). Backward compatible with database storage. |
| Requirement | Version |
|---|---|
| .NET SDK | 10.0+ |
| PostgreSQL | 16+ |
| Docker | Optional |
docker-compose up -dAPI Endpoint:
http://localhost:5000The 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-alpine2. 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 ./coveragePOST |
/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
}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 | jqAlternative to JWT for service-to-service communication:
X-API-Key: your-api-key-here
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
userPasswordis required to enable encryption - If
ownerPasswordis not specified, it defaults to theuserPassword - 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
pageNumberreturn 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
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'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."
}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
}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
┌─────────────────────────────────────────────────────────────────┐
│ 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 |
JWT Settings
{
"JwtSettings": {
"Secret": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!",
"Issuer": "FileConversionApi",
"Audience": "FileConversionApi",
"TokenExpirationMinutes": 60,
"RefreshTokenExpirationDays": 7
}
}Puppeteer Settings
{
"PuppeteerSettings": {
"ExecutablePath": null,
"Timeout": 30000
}
}Note: When
ExecutablePathisnull, 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-Afterheader.
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:latestand setOtlpEndpointtohttp://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 settingEnabled: 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.
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"]
}
}