Skip to main content

CertiLock QR Decoder API

Overview

The CertiLock QR Decoder API is a specialized ASP.NET Core Web API application that decodes QR codes containing both overt (visible) and covert (hidden, encrypted) messages. It provides a secure, authenticated REST API for uploading QR code images and extracting both layers of information.

Production URL

Base URL: https://certilock-api.scottsdalemint.com

Key Features

  • Dual-Layer QR Decoding: Extracts both visible and hidden encrypted messages from QR codes
  • Multi-Format Image Support: Accepts JPEG, PNG, BMP, GIF, WebP, and TIFF image formats
  • API Key Authentication: Secure endpoint access with configurable API keys
  • IP-Restricted Test Client: Built-in web interface for testing (restricted to whitelisted IPs)
  • x86 Architecture: Optimized for 32-bit Windows environments (required by native decoder library)
  • CORS Enabled: Supports cross-origin requests for flexible client integration

Architecture

Technology Stack

  • .NET 10.0: Latest ASP.NET Core Web API framework
  • Platform: x86 (32-bit) - required by PeltaDecoder.dll
  • Image Processing: SixLabors.ImageSharp 3.1.6 for grayscale conversion
  • Native Decoder: PeltaDecoder.dll (Windows 32-bit native library)
  • Hosting: Windows Server 2019 Standard with Plesk 18.0.77 / IIS

Core Components

PeltaApi/
├── Controllers/
│ └── QRDecoderController.cs # API endpoints (/api/qr/decode, /api/qr/health)
├── Services/
│ ├── PeltaNative.cs # P/Invoke declarations for PeltaDecoder.dll
│ ├── PeltaQRDecoder.cs # Main decoder service (singleton)
│ └── ImageConverter.cs # Image-to-grayscale conversion
├── Middleware/
│ ├── ApiKeyAuthenticationMiddleware.cs # X-API-Key validation
│ └── IpRestrictionMiddleware.cs # IP whitelist for test client
├── native/
│ └── PeltaDecoder.dll # Native QR decoder library
└── wwwroot/
└── index.html # Test client interface

Decode Workflow

graph TD
A[Image Upload] --> B[Grayscale Conversion]
B --> C[qr_decOpen]
C --> D[qr_decAnalyze]
D --> E{QR Found?}
E -->|Yes| F[qr_decGetMessage]
E -->|No| G[Error Response]
F --> H[Strip 3-byte Prefix]
H --> I[qr_decGetIndpSize]
I --> J{Covert Present?}
J -->|Yes| K[qr_decGetIndependentKeyed]
J -->|No| L[Return Overt Only]
K --> M[Decrypt Covert]
M --> N[qr_decClose]
L --> N
N --> O[JSON Response]

How It Works

  1. Image Upload: Client uploads QR code image via HTTP POST
  2. Grayscale Conversion: ImageSharp converts image to 8-bit grayscale byte array
  3. Decoder Initialization: PeltaDecoder.dll allocates decoder resources with grayscale data
  4. QR Analysis: Native decoder scans image bounds for QR code patterns
  5. Overt Message Extraction: Decodes visible QR data (strips 3-byte "]Q1" prefix)
  6. Covert Message Extraction: If present, decrypts hidden message using derived master key
  7. Metadata Extraction: Retrieves QR version and error correction level
  8. Response: Returns JSON with both messages, version, and ECC level
  9. Resource Cleanup: Decoder resources freed via qr_decClose()

API Reference

Authentication

All API endpoints (except /api/qr/health) require authentication via the X-API-Key header.

Header Format:

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

Response on Missing/Invalid Key:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
"success": false,
"error": "Invalid or missing API key"
}

Endpoints

1. Decode QR Code

Endpoint: POST /api/qr/decode

Authentication: Required (X-API-Key header)

Content-Type: multipart/form-data

Request Body:

  • file (required): QR code image file
    • Supported formats: JPEG, PNG, BMP, GIF, WebP, TIFF
    • Max size: No enforced limit (governed by server/IIS settings)

Success Response (200 OK):

{
"success": true,
"overtMessage": "Visible QR code message",
"covertMessage": "Hidden encrypted message (if present)",
"qrVersion": 5,
"eccLevel": 2
}

Field Descriptions:

  • success: Always true on successful decode
  • overtMessage: The visible QR code data (3-byte prefix removed)
  • covertMessage: Hidden encrypted payload (empty string if none)
  • qrVersion: QR code version (1-40, higher = more data capacity)
  • eccLevel: Error correction level (0=L, 1=M, 2=Q, 3=H)

Error Response (400 Bad Request):

{
"success": false,
"error": "Unsupported image format. Supported: JPEG, PNG, BMP, GIF, WebP, TIFF"
}
{
"success": false,
"error": "No QR code detected in the image"
}

Error Response (500 Internal Server Error):

{
"success": false,
"error": "Failed to process image: [specific error message]"
}

2. Health Check

Endpoint: GET /api/qr/health

Authentication: Not required (public endpoint)

Success Response (200 OK):

{
"status": "healthy",
"service": "Pelta QR Decoder API",
"version": "1.0.0",
"platform": "x86"
}

Use Cases:

  • Load balancer health checks
  • Monitoring system availability
  • Deployment verification
  • Platform architecture confirmation

Usage Examples

cURL

Decode QR Code:

curl -X POST "https://certilock-api.scottsdalemint.com/api/qr/decode" \
-H "X-API-Key: your-api-key-here" \
-F "file=@/path/to/qrcode.png"

Health Check:

curl "https://certilock-api.scottsdalemint.com/api/qr/health"

PowerShell

Decode QR Code:

$apiKey = "your-api-key-here"
$imagePath = "C:\path\to\qrcode.png"
$uri = "https://certilock-api.scottsdalemint.com/api/qr/decode"

$headers = @{
"X-API-Key" = $apiKey
}

$form = @{
file = Get-Item -Path $imagePath
}

$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Form $form
$response | ConvertTo-Json

Health Check:

Invoke-RestMethod -Uri "https://certilock-api.scottsdalemint.com/api/qr/health"

C# / .NET

Decode QR Code:

using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your-api-key-here");

var form = new MultipartFormDataContent();
var fileContent = new ByteArrayContent(File.ReadAllBytes(@"C:\path\to\qrcode.png"));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
form.Add(fileContent, "file", "qrcode.png");

var response = await client.PostAsync(
"https://certilock-api.scottsdalemint.com/api/qr/decode",
form
);

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);

JavaScript / Fetch API

Decode QR Code:

const apiKey = "your-api-key-here";
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];

const formData = new FormData();
formData.append('file', file);

fetch('https://certilock-api.scottsdalemint.com/api/qr/decode', {
method: 'POST',
headers: {
'X-API-Key': apiKey
},
body: formData
})
.then(response => response.json())
.then(data => {
console.log('Overt Message:', data.overtMessage);
console.log('Covert Message:', data.covertMessage);
console.log('QR Version:', data.qrVersion);
console.log('ECC Level:', data.eccLevel);
})
.catch(error => console.error('Error:', error));

Python

Decode QR Code:

import requests

api_key = "your-api-key-here"
url = "https://certilock-api.scottsdalemint.com/api/qr/decode"

headers = {
"X-API-Key": api_key
}

with open("/path/to/qrcode.png", "rb") as file:
files = {"file": file}
response = requests.post(url, headers=headers, files=files)

if response.status_code == 200:
data = response.json()
print(f"Overt Message: {data['overtMessage']}")
print(f"Covert Message: {data['covertMessage']}")
print(f"QR Version: {data['qrVersion']}")
print(f"ECC Level: {data['eccLevel']}")
else:
print(f"Error: {response.status_code}")
print(response.json())

Configuration

appsettings.json

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"PeltaApi": "Information"
}
},
"AllowedHosts": "*",
"ApiKeys": [
"your-production-api-key-here",
"your-secondary-api-key-here"
],
"IpRestriction": {
"AllowedIps": [
"12.235.66.14",
"50.221.25.7"
]
}
}

Configuration Sections

ApiKeys:

  • Array of valid API keys for authentication
  • Add/remove keys as needed for client management
  • Keys are case-sensitive
  • No length restrictions (recommend 32+ characters)

IpRestriction.AllowedIps:

  • Whitelist for test client access (/ and /index.html)
  • Does NOT affect API endpoints (accessible from any IP with valid API key)
  • IPv4 addresses only
  • Empty array = no restrictions (not recommended for production)

Test Client

Access

URL: https://certilock-api.scottsdalemint.com/

IP Restriction: Only accessible from whitelisted IPs (configured in IpRestriction.AllowedIps)

Features

  • Drag-and-drop or click-to-browse file upload
  • Real-time configuration of API endpoint and key
  • Visual display of decoded results
  • Raw JSON response viewer
  • Support for all image formats (JPEG, PNG, BMP, GIF, WebP, TIFF)

Usage

  1. Navigate to the root URL from a whitelisted IP
  2. Enter API endpoint: https://certilock-api.scottsdalemint.com/api/qr/decode
  3. Enter your API key
  4. Upload or drag-drop a QR code image
  5. Click "Upload & Decode"
  6. View results: overt message, covert message, QR metadata

Deployment

Server Requirements

  • Operating System: Windows Server 2016+ (deployed on Windows Server 2019)
  • .NET Runtime: .NET 10.0 Runtime (x86 version required)
  • Web Server: IIS with ASP.NET Core Module V2
  • Hosting Platform: Plesk 18.0.77

IIS Configuration

Application Pool Settings:

  • .NET CLR Version: No Managed Code
  • Enable 32-Bit Applications: True ⚠️ (critical for PeltaDecoder.dll)
  • Pipeline Mode: Integrated
  • Identity: ApplicationPoolIdentity

Build & Publish

From the project directory:

dotnet publish -c Release -p:Platform=x86 -o publish

Important: Always use -p:Platform=x86 flag to ensure 32-bit compilation.

Deployment Steps

  1. Build and publish the application (see command above)

  2. Upload publish/ folder contents to Plesk httpdocs directory

  3. Verify all files are present:

    • PeltaApi.dll
    • PeltaApi.exe
    • appsettings.json
    • native/PeltaDecoder.dll
    • wwwroot/index.html
    • All dependency DLLs
  4. Configure IIS Application Pool in Plesk:

    • Navigate to Hosting Settings
    • Check "Enable 32-bit applications"
    • Set .NET version to "No Managed Code"
  5. Update appsettings.json with production API keys

  6. Restart application pool

  7. Verify health endpoint:

    curl https://certilock-api.scottsdalemint.com/api/qr/health

File Structure in Production

httpdocs/
├── PeltaApi.exe
├── PeltaApi.dll
├── appsettings.json
├── web.config (auto-generated)
├── native/
│ └── PeltaDecoder.dll
├── wwwroot/
│ └── index.html
└── [dependency DLLs]

Security

API Key Management

  • Store API keys in appsettings.json (not in code)
  • Use strong, randomly generated keys (32+ characters recommended)
  • Rotate keys periodically
  • Different keys per client/environment for tracking and revocation

IP Restrictions

  • Test client (/ and /index.html) protected by IP whitelist
  • API endpoints (/api/qr/*) accessible from any IP with valid key
  • Configure whitelist in IpRestriction.AllowedIps section
  • Changes require application restart

HTTPS

  • Always use HTTPS in production
  • API keys transmitted in HTTP headers (vulnerable if unencrypted)
  • SSL certificate managed via Plesk
  • Let's Encrypt automatic renewal configured

Input Validation

  • File type validation (MIME type and extension)
  • Image format validation via ImageSharp
  • Maximum file size governed by IIS settings (default: 30MB)
  • Malicious file rejection via image processing layer

Troubleshooting

Common Issues

404 Not Found on Root (/)

Cause: Middleware order incorrect or wwwroot missing

Solution:

  • Verify UseDefaultFiles() comes before UseStaticFiles() in Program.cs
  • Verify wwwroot/index.html exists in publish folder
  • Check IIS physical path points to correct directory

401 Unauthorized

Cause: Missing or invalid API key

Solution:

  • Verify X-API-Key header is present in request
  • Check key matches value in appsettings.json (case-sensitive)
  • Ensure API key is not empty or whitespace
  • Check for extra spaces or special characters

403 Forbidden on Test Client

Cause: IP address not whitelisted

Solution:

  • Add your IP to IpRestriction.AllowedIps in appsettings.json
  • Restart application via Plesk
  • Verify your actual public IP (use https://whatismyip.com)
  • Check for IPv4 vs IPv6 issues

500 Internal Server Error - "Failed to load PeltaDecoder.dll"

Cause: 32-bit DLL cannot load in 64-bit process

Solution:

  • Enable 32-bit applications in IIS Application Pool (Plesk Hosting Settings)
  • Verify platform target is x86 in .csproj
  • Rebuild with -p:Platform=x86 flag
  • Check native/PeltaDecoder.dll exists in httpdocs

No QR Code Detected

Cause: Image quality, size, or contrast issues

Solution:

  • Ensure QR code is clearly visible and in focus
  • Try images with higher resolution (minimum 300x300px recommended)
  • Verify image is not rotated or distorted
  • Check QR code has sufficient contrast (dark on light background)
  • Ensure QR code occupies significant portion of image

Performance

Benchmarks

  • Average decode time: 50-150ms (depends on image size and QR complexity)
  • Throughput: 10-20 requests/second (single instance)
  • Memory usage: ~50-100MB base + ~5-10MB per concurrent request
  • Startup time: ~2-3 seconds (decoder initialization)

Optimization Tips

  • Use compressed images (JPEG with 85% quality recommended)
  • Resize large images before upload (max 2000x2000px sufficient)
  • Implement client-side caching for repeated decodes
  • Consider load balancing for high-traffic scenarios
  • Monitor memory usage under sustained load

Scaling

  • Vertical: Increase server CPU/RAM for more concurrent requests
  • Horizontal: Deploy multiple instances behind load balancer (session-less API)
  • CDN: Use CDN for test client static assets (wwwroot/*)
  • Caching: Implement Redis for response caching if decode patterns repeat

Monitoring & Logging

Health Monitoring

Implement automated health checks via monitoring service or cron job:

# Example health check script
#!/bin/bash
response=$(curl -s -o /dev/null -w "%{http_code}" https://certilock-api.scottsdalemint.com/api/qr/health)
if [ $response -ne 200 ]; then
echo "API health check failed: HTTP $response"
# Send alert
fi

Log Levels

  • Information: API requests, decoder initialization, platform detection
  • Warning: Invalid images, decode failures, IP restriction blocks
  • Error: Decoder errors, image processing failures
  • Critical: Startup failures, decoder initialization errors

Log Location

  • Console: Visible in Plesk application logs
  • EventLog: Windows Event Viewer (Application)
  • Plesk Logs: /var/www/vhosts/scottsdalemint.com/logs/

Monitoring Metrics

Track these key metrics:

  • Request rate (requests/second)
  • Average response time
  • Error rate (4xx, 5xx responses)
  • Memory usage
  • CPU utilization
  • Disk I/O (image uploads)

Maintenance

Updating the Application

  1. Build new version locally:

    dotnet publish -c Release -p:Platform=x86 -o publish
  2. Stop IIS application pool in Plesk

  3. Backup current appsettings.json file

  4. Replace all files in httpdocs (except appsettings.json)

  5. Restore appsettings.json or merge new settings

  6. Restart application pool

  7. Verify health endpoint and test decode

Backup Strategy

Weekly Backups:

  • Application files (via Plesk backup)
  • appsettings.json configuration
  • Deployment scripts

Daily Monitoring:

  • Health endpoint checks
  • Error log review
  • Performance metrics

Support Contacts

For issues or questions:

  • Developer: Rhino Group Development Team
  • Server: Rhino Group DevOps
  • API Access: Contact client administrator

Technical Details

Native Library Integration

The application uses P/Invoke to call native C functions from PeltaDecoder.dll:

Key Functions:

  • qr_000000000000() - Master key derivation
  • qr_decOpen() - Initialize decoder with grayscale image
  • qr_decAnalyze() - Scan image for QR code
  • qr_decGetMessage() - Extract overt message
  • qr_decGetIndependentKeyed() - Extract covert message
  • qr_decClose() - Free decoder resources

Memory Management:

  • Decoder handles allocated via qr_decOpen()
  • Must call qr_decClose() in finally block to prevent leaks
  • Grayscale byte array passed by reference to native code
  • Unsafe pointer operations handled by P/Invoke layer

Image Processing Pipeline

  1. Upload: Multipart form data received
  2. Validation: Check content type and file signature
  3. Load: ImageSharp loads image into Rgba32 format
  4. Grayscale: Mutate operation converts to grayscale
  5. Extract: ProcessPixelRows extracts byte data row-major
  6. Decode: Pass byte array to native decoder
  7. Cleanup: Dispose image resources

Error Handling

The application uses try-catch blocks at multiple levels:

  • Controller Level: HTTP exception handling
  • Service Level: Decoder operation error handling
  • Native Level: P/Invoke error codes
  • Image Level: ImageSharp exception handling

All errors are logged with context and returned as structured JSON responses.


Repository Information

  • Repository: Z:\Repos\pelta-api
  • Solution: PeltaApi.sln
  • Project: PeltaApi/PeltaApi.csproj
  • Target Framework: net10.0
  • Platform: x86

Project Structure

pelta-api/
├── PeltaApi/
│ ├── Controllers/
│ ├── Middleware/
│ ├── Services/
│ ├── native/
│ │ └── PeltaDecoder.dll
│ ├── wwwroot/
│ │ └── index.html
│ ├── Program.cs
│ ├── appsettings.json
│ ├── PeltaApi.csproj
│ └── Agents.MD
├── PeltaApi.sln
└── README.md

Version History

Version 1.0.0 (June 2026)

  • Initial production release
  • QR code decoding with overt/covert message extraction
  • API key authentication
  • IP-restricted test client
  • CORS support
  • x86 platform targeting
  • Health check endpoint
  • Comprehensive error handling
  • Production deployment on Plesk/IIS


License

Proprietary software owned by Scottsdale Mint / Rhino Group.

PeltaDecoder.dll: Proprietary QR decoder library with covert message support
SixLabors.ImageSharp: MIT License (image processing)
ASP.NET Core: MIT License (web framework)