Developers
A deterministic PDF extraction engine for .NET.
Quickstart
Register, build a template, execute
$ dotnet add package Docuoriausing 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));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
| Principle | Statement |
|---|---|
| Deterministic | Same PDF, same template, same generator options produce the same bytes. |
| Stateless | No per-invocation state. Register the engine as a singleton and call it concurrently. |
| Immutable pipeline | Each step returns a new record; nothing is mutated in place. |
| Typed at the call site | Step and configuration pairs, and generator and options pairs, are enforced by generic constraints. A mismatch does not compile. |
| No PDF library leakage | PdfPig types never appear in public contracts. |
| Private by default | Every PDF-consuming method takes a Stream. Nothing is uploaded. |
Engine surface
Nine methods on IDocuoriaEngine
| Method | Does |
|---|---|
EvaluateMatchRuleAsync | Scores 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. |
InspectAsync | Read-only projection: page count, metadata, flattened text per page, text blocks with bounds, capped table previews. |
TestPatternAsync | Runs a regex against the flattened text. Returns matches, gaps, and the nearest miss with its break index. |
TestGroupsAsync | Tests each capture group of a regex separately. |
DryRunAsync | Runs extraction and transformation without output. Returns the projection, diagnostics, and a completeness report. |
EvaluateMatchAsync | Evaluates a template’s match rule, requirements, and mapping coverage. Returns a recommendation: strong, partial, or no-match. |
ClassifyAsync | Scores every stored template and returns the best match, or null. |
ClassifyRankedAsync | Scores every stored template and returns the ranked list. |
Capabilities
What a template is made of
Seven match rules
| Rule | Scores |
|---|---|
FileNameMatchRule | Glob or substring match on the file name |
MetadataMatchRule | PDF metadata: author, title, subject, keywords |
TextPatternMatchRule | Token or regex hits across all pages |
TextAnchorMatchRule | Text presence and its position on the page |
PageGeometryMatchRule | Page count, dimensions, orientation |
TableMatchRule | Table structure: rows, columns, cell content |
CompositeMatchRule | AND, OR, and NOT over child rules |
Five extraction sources and a fallback
| Source | Reads |
|---|---|
TextPatternExtractionSource | Regex or token against flattened text; single value or every match |
TextAnchorExtractionSource | Text within a bounding box on the page |
MetadataFieldExtractionSource | A PDF info dictionary field or a raw XMP key |
TableCellExtractionSource | One table cell, by ordinal position or header name |
TableRowsExtractionSource | Every data row of a detected table |
FallbackExtractionSource | A primary source, then a second source when the primary returns null |
Five pipeline steps
| Step | Role |
|---|---|
ExtractionStep | First. Seeds the record from the PDF through extraction sources |
TransformationStep | Trim, cast, format, rename, compute, and per-element transforms |
RetrievalStep | Adds data from HTTP or database sources |
PythonStep | Runs a user-supplied Python 3.12 script |
PublishStep | Last. Validates against the schema and seals the output |
Two output generators
| Format | Generator | Content type | Collections |
|---|---|---|---|
| CSV | CsvOutputGenerator | text/csv | One row per collection element; scalar fields repeat on each row |
| JSON | JsonOutputGenerator | application/json | Natural 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
| Group | Scripts |
|---|---|
| Discovery and authoring | inspect, test-pattern, test-groups, validate-template, schema-info |
| Pipeline | dry-run, execute, batch-execute |
| Classification | classify, evaluate-match |
| Template store | list-templates, load-template, save-template |
| Batch and change safety | survey, regression-check |
| Licensing | license-status, license-acquire, license-set, license-remove |
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
| Method | Route | Authentication | Purpose |
|---|---|---|---|
GET | /api/health | Anonymous | Liveness probe |
POST | /api/templates | Function key | Create a template |
GET | /api/templates | Function key | List templates |
GET | /api/templates/{id} | Function key | Load a template |
PUT | /api/templates/{id} | Function key | Create or replace a template |
DELETE | /api/templates/{id} | Function key | Delete a template |
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.