Skip to content
Docuoria

Developers

A deterministic PDF extraction engine for .NET.

Docuoria is a stateless, in-process .NET 10 library. Same PDF, same template, same options, same bytes out. One NuGet package, a typed template builder, and results you switch on.

Quickstart

Register, build a template, execute

Targets .NET 10. A test in the SDK suite, ReadmeWalkthroughTests, compiles and runs this walkthrough, so it matches the shipped API.
$ dotnet add package Docuoria
csharp
using Microsoft.Extensions.DependencyInjection;
using Docuoria.Configuration;
using Docuoria.Contracts;
using Docuoria.MatchRules;
using Docuoria.Models;
using Docuoria.Output.Csv;
using Docuoria.Pipeline.Extraction;
using Docuoria.Pipeline.Publish;
using Docuoria.Registration;
using Docuoria.Results;

var services = new ServiceCollection();
services.AddDocuoriaEngine(b => b.AddBuiltInMatchRules().AddCsvOutputGenerator());
var engine = services.BuildServiceProvider().GetRequiredService<IDocuoriaEngine>();

var schema = new RecordDefinition("Invoice", new FieldDefinition[]
{
    new PrimitiveFieldDefinition("vendor", FieldType.String, isRequired: true),
});
var template = TemplateBuilder.Create("quickstart", new DataModel(schema))
    .WithMatchRule<FileNameMatchRule, FileNameMatchRuleConfiguration>(
        new FileNameMatchRuleConfiguration { Pattern = "**/*.pdf", Threshold = 1m })
    .ExtractWith<ExtractionStep, ExtractionStepConfiguration>(new ExtractionStepConfiguration(new IFieldMapping[]
    {
        new FieldMapping("vendor", FieldType.String, MetadataFieldExtractionSource.Standard(MetadataField.Title)),
    }))
    .PublishWith<PublishStep, PublishStepConfiguration>(new PublishStepConfiguration())
    .Build();

await using var pdf = File.OpenRead("invoice.pdf");
var result = await engine.ExecuteTemplateAsync<CsvOutputGenerator, CsvGeneratorOptions>(pdf, template, new CsvGeneratorOptions());
if (result is SucceededResult ok)
    Console.WriteLine(System.Text.Encoding.UTF8.GetString(ok.Output.Payload.Span));
csharp
switch (result)
{
    case SucceededResult success:   // success.Output.Payload, success.Output.ContentType
    case FailedResult failure:      // failure.StepIdentifier, failure.ErrorMessage
    case RejectedResult rejection:  // rejection.Reason, rejection.Detail
}

The full README on NuGet covers collection extraction, transforms, composite match rules, diagnostics, dry-run, and template storage.

Design

Six principles the API holds to

PrincipleStatement
DeterministicSame PDF, same template, same generator options produce the same bytes.
StatelessNo per-invocation state. Register the engine as a singleton and call it concurrently.
Immutable pipelineEach step returns a new record; nothing is mutated in place.
Typed at the call siteStep and configuration pairs, and generator and options pairs, are enforced by generic constraints. A mismatch does not compile.
No PDF library leakagePdfPig types never appear in public contracts.
Private by defaultEvery PDF-consuming method takes a Stream. Nothing is uploaded.

Engine surface

Nine methods on IDocuoriaEngine

MethodDoes
EvaluateMatchRuleAsyncScores one match rule against a PDF. Returns confidence in [0, 1] and IsMatch against the threshold.
ExecuteTemplateAsync<TGenerator, TOptions>Runs the full pipeline and renders the sealed record with the chosen generator.
InspectAsyncRead-only projection: page count, metadata, flattened text per page, text blocks with bounds, capped table previews.
TestPatternAsyncRuns a regex against the flattened text. Returns matches, gaps, and the nearest miss with its break index.
TestGroupsAsyncTests each capture group of a regex separately.
DryRunAsyncRuns extraction and transformation without output. Returns the projection, diagnostics, and a completeness report.
EvaluateMatchAsyncEvaluates a template’s match rule, requirements, and mapping coverage. Returns a recommendation: strong, partial, or no-match.
ClassifyAsyncScores every stored template and returns the best match, or null.
ClassifyRankedAsyncScores every stored template and returns the ranked list.

Capabilities

What a template is made of

Seven match rules

RuleScores
FileNameMatchRuleGlob or substring match on the file name
MetadataMatchRulePDF metadata: author, title, subject, keywords
TextPatternMatchRuleToken or regex hits across all pages
TextAnchorMatchRuleText presence and its position on the page
PageGeometryMatchRulePage count, dimensions, orientation
TableMatchRuleTable structure: rows, columns, cell content
CompositeMatchRuleAND, OR, and NOT over child rules

Five extraction sources and a fallback

SourceReads
TextPatternExtractionSourceRegex or token against flattened text; single value or every match
TextAnchorExtractionSourceText within a bounding box on the page
MetadataFieldExtractionSourceA PDF info dictionary field or a raw XMP key
TableCellExtractionSourceOne table cell, by ordinal position or header name
TableRowsExtractionSourceEvery data row of a detected table
FallbackExtractionSourceA primary source, then a second source when the primary returns null

Five pipeline steps

StepRole
ExtractionStepFirst. Seeds the record from the PDF through extraction sources
TransformationStepTrim, cast, format, rename, compute, and per-element transforms
RetrievalStepAdds data from HTTP or database sources
PythonStepRuns a user-supplied Python 3.12 script
PublishStepLast. Validates against the schema and seals the output

Two output generators

FormatGeneratorContent typeCollections
CSVCsvOutputGeneratortext/csvOne row per collection element; scalar fields repeat on each row
JSONJsonOutputGeneratorapplication/jsonNatural nesting; arrays at any depth

XML is on the roadmap.

Data model

  • Six field types: String, Number, Integer, Boolean, Date, Timestamp.
  • Nested records and ordered collections at any depth.
  • Required fields enforced at the publish step.
  • TemplateBuilder.Build() rejects schema mismatches before runtime.

Results

  • SucceededResult: the sealed output and its content type.
  • FailedResult: the step identifier and the error.
  • RejectedResult: InvalidPdf, MalformedTemplate, UnknownOutputGenerator, or GeneratorRejected, with detail.
  • Dry-run variants of each, with a completeness report on success.

CLI scripts

Nineteen dotnet-script verbs with a fixed contract

The scripts are the surface the AI skill uses. They suit CI pipelines and any agent that needs a typed, stateless interface over the engine.
GroupScripts
Discovery and authoringinspect, test-pattern, test-groups, validate-template, schema-info
Pipelinedry-run, execute, batch-execute
Classificationclassify, evaluate-match
Template storelist-templates, load-template, save-template
Batch and change safetysurvey, regression-check
Licensinglicense-status, license-acquire, license-set, license-remove
shell
dotnet tool install -g dotnet-script
dotnet script scripts/classify.csx -- --pdf invoice.pdf --store-path C:/work/templates
  • Success: one line of JSON on stdout, exit 0.
  • Error: {"error":{"code","message","detail"}} on stderr, non-zero exit.
  • Exit 2: invalid arguments, or the run completed but was incomplete. The stream that carries the JSON tells you which.
  • Exit 3: the licence needs attention.
  • Unknown flags are rejected with the list of valid ones.

Template store

A self-hostable API for a shared template store

Docuoria.Api is an Azure Functions host that serves templates over HTTP. Every engine pointed at one host shares its templates. Using it needs the Pro plan. Pro customers get the host from Sidub: inquiries@sidub.net.
MethodRouteAuthenticationPurpose
GET/api/healthAnonymousLiveness probe
POST/api/templatesFunction keyCreate a template
GET/api/templatesFunction keyList templates
GET/api/templates/{id}Function keyLoad a template
PUT/api/templates/{id}Function keyCreate or replace a template
DELETE/api/templates/{id}Function keyDelete a template
csharp
services.AddDocuoriaEngine(b => b.AddApiTemplateStore(
    new Uri("https://templates.example.com/"),
    new ApiTemplateStoreCredentials { FunctionKey = "<KEY>" }));

Request bodies are template JSON. The host rejects any other content type with 415 and stores no PDF bytes; PrivacyInvariantTests fails the build if that changes.

Licensing

Licence terms and runtime enforcement

Terms

The SDK and the template store host are licensed under the Sidub Proprietary Software License Agreement: the Free License covers personal and non-commercial use, and the Subscription License, the Pro plan, covers commercial use. The installer packages and the skill package files are MIT. Volume and OEM terms by contact. Full text: License Agreement.

Enforcement in a host

Register licensing with AddDocuoriaLicensing. Enforcement is Auto by default: transparent until a credential exists, enforced from then on. DOCUORIA_ENFORCEMENT=Disabled turns licensing and its network calls off for development. The licence terms apply to every use, whichever mode is set.

Rate limits per plan are on the pricing page. Two further limits, for HTTP retrieval and the Python step, apply to hosts that register those steps: 30 and 10 per 60 seconds on Free, 300 and 120 on Pro.

Version 1.1.3, on NuGet and npm.

The plugin repository on GitHub carries the release history and the integrity manifest.