Production-Grade Passport OCR API for C#

Extract and validate ICAO 9303 MRZ data with 99.8%+ accuracy in under 1500ms via a direct HTTP request.

Steve HarringtonUpdated 2026-01-16
Diagram showing a passport image being sent to the StructOCR API, which processes the Machine Readable Zone (MRZ) and returns structured JSON data with fields like name, passport number, and date of birth.
Figure 1: StructOCR converts raw Passport images into validated JSON data.

Why Passport OCR is Difficult

Implementing reliable passport OCR extends beyond simple text extraction. Generic engines like Tesseract fail on low-resolution or noisy images common in user-submitted scans. Passports present unique challenges: holographic overlays cause glare, angled photos introduce skew and rotation, and varied lighting creates shadows that obscure characters in the Machine Readable Zone (MRZ). Furthermore, extracting data isn't enough; it requires validation. This involves parsing the fixed-width ICAO 9303 format, correctly calculating and verifying multiple check digits (for passport number, date of birth, and expiry), and maintaining complex RegEx patterns for dozens of country-specific variations. This technical debt becomes a significant maintenance burden.

Enterprise-Grade Extraction with StructOCR

StructOCR is purpose-built for identity documents. Our API utilizes pre-trained deep learning models specifically designed for the OCRA and OCRB fonts found in ICAO 9303 compliant passports. Before extraction, our pipeline performs automatic image pre-processing, including deskewing, denoising, and glare removal. Unlike Tesseract which returns unstructured text strings, StructOCR parses the entire MRZ, validates all check digits, and returns a standardized, predictable JSON object. This eliminates the need for fragile post-processing scripts and provides immediate, actionable data.

Production Use Cases

  • Digital Onboarding (KYC): Reduce drop-off rates by pre-filling user data from Passports in < 2 seconds.
  • Fraud Prevention: Detect tampered fonts or mismatched MRZ checksums automatically.
  • Global Compliance: Handle Passports from 200+ jurisdictions without custom rules.

Implementation: Raw C# (HttpClient)

The following C# code demonstrates a complete flow using `System.Net.Http`. It correctly handles `x-api-key` authentication and deserializes both MRZ and Visual Zone (VIZ) fields into strong-typed objects.

Prerequisite: .NET Core 3.1+ or .NET 5/6/7+

CODE EXAMPLE
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

public class PassportOcrExample
{
    // 💰 Save 30%+ vs competitors. Get 200 free requests instantly:
//    👉 https://structocr.com/register
    private const string ApiKey = "YOUR_API_KEY_HERE";
    private const string ApiEndpoint = "https://api.structocr.com/v1/passport";

    private static readonly HttpClient client = new HttpClient();

    public static async Task Main(string[] args)
    {
        // Note: Supports JPG, PNG, WebP (Max 4.5MB)
        string imagePath = "passport.jpg";

        if (!File.Exists(imagePath))
        {
            Console.WriteLine($"Error: File not found at {imagePath}");
            return;
        }

        try
        {
            // 1. Prepare Payload
            byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
            string base64Image = Convert.ToBase64String(imageBytes);
            var payload = new { img = base64Image };

            // 2. Setup Request
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.Add("x-api-key", ApiKey); // Required Header

            // 3. Send POST Request
            Console.WriteLine($"Scanning passport at {ApiEndpoint}...");
            HttpResponseMessage response = await client.PostAsJsonAsync(ApiEndpoint, payload);

            // 4. Handle Response
            string responseBody = await response.Content.ReadAsStringAsync();

            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"API Error ({response.StatusCode}): {responseBody}");
                return;
            }

            // 5. Deserialize to Object
            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            var result = JsonSerializer.Deserialize<ApiResponse>(responseBody, options);

            if (result?.Success == true && result.Data != null)
            {
                var data = result.Data;
                Console.WriteLine("✅ Extraction Successful!");
                
                // Identity Data
                Console.WriteLine($"Passport #: {data.PassportNumber}");
                Console.WriteLine($"Name:       {data.GivenNames} {data.Surname}");
                Console.WriteLine($"Nation:     {data.Nationality} ({data.CountryCode})");

                // Visual Zone Data (Unique to StructOCR)
                Console.WriteLine($"Birth Place:{data.PlaceOfBirth}");
                Console.WriteLine($"Issued At:  {data.PlaceOfIssue}");
                
                // Dates
                Console.WriteLine($"DOB:        {data.DateOfBirth} ({data.Sex})");
                Console.WriteLine($"Expiry:     {data.DateOfExpiry}");
            }
            else
            {
                Console.WriteLine($"Extraction Failed: {result?.Error}");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Unexpected Error: {e.Message}");
        }
    }
}

// --- Data Models matching the Schema ---
public class ApiResponse
{
    public bool Success { get; set; }
    public PassportData Data { get; set; }
    public string Error { get; set; }
}

public class PassportData
{
    [JsonPropertyName("passport_number")]
    public string PassportNumber { get; set; }
    
    public string Surname { get; set; }
    
    [JsonPropertyName("given_names")]
    public string GivenNames { get; set; }
    
    public string Nationality { get; set; }
    
    [JsonPropertyName("country_code")]
    public string CountryCode { get; set; }
    
    [JsonPropertyName("date_of_birth")]
    public string DateOfBirth { get; set; }
    
    public string Sex { get; set; }
    
    [JsonPropertyName("date_of_expiry")]
    public string DateOfExpiry { get; set; }

    // VIZ Fields (Visual Inspection Zone)
    [JsonPropertyName("place_of_birth")]
    public string PlaceOfBirth { get; set; }
    
    [JsonPropertyName("place_of_issue")]
    public string PlaceOfIssue { get; set; }
}

Technical Specs

  • Latency: < 5s (Average)
  • Uptime: 98.5% SLA
  • Security: AES-256 Encryption & SOC2 Compliant
  • Input: JPG, PNG, WebP (Base64 Encoded)
  • Max File Size: 4.5MB
  • Output: JSON (Structured Data)

Key Features

  • Visual Extraction (VIZ): Parses non-MRZ data fields like Place of Birth and Issuing Authority.
  • Global Support: Optimized for 195+ countries, handling complex backgrounds and holograms.
  • Date Normalization: Returns all dates (Birth, Issue, Expiry) in a standardized YYYY-MM-DD format.

Sample JSON Output

StructOCR returns a normalized JSON object, regardless of the input image angle or quality.

{
  "success": true,
  "data": {
    "type": "passport",
    "country_code": "USA",
    "nationality": "UNITED STATES",
    "passport_number": "E12345678",
    "surname": "DOE",
    "given_names": "JOHN",
    "sex": "M",
    "date_of_birth": "1990-01-01",
    "place_of_birth": "NEW YORK, USA",
    "date_of_issue": "2020-01-01",
    "date_of_expiry": "2030-01-01",
    "place_of_issue": "PASSPORT AGENCY"
  }
}

Frequently Asked Questions

How does StructOCR compare to AWS Textract or Google Vision?

Generic OCR services like AWS Textract or Google Vision are powerful but return raw lines of text. They do not understand the specific structure of an ICAO 9303 passport MRZ. Developers are still required to write and maintain complex parsers and validation logic. StructOCR is a specialized model that performs extraction, parsing, and validation in one step, returning a structured JSON object with clearly defined fields.

Do you store the uploaded images?

No. All images are processed in-memory and are permanently deleted immediately after the OCR process completes. We do not store any personally identifiable information (PII) from your requests.

How do you handle blurry or low-quality images?

Our API includes an automatic image enhancement engine that applies de-blurring and sharpening filters before the OCR process begins. For best results, we recommend images with a minimum resolution of 300 DPI.

More OCR Tutorials

Precise Data Extraction and Seamless Integration with AI-powered OCR API.

Empower your solutions with automated data extraction by integrating best-in class StructOCR via API seamlessly.

No credit card required • Full API access included