October 2, 2026
How to Validate Uploaded Invoices Against Business Rules in C#
Imagine we’re building a supplier portal where folks submit invoices to our system.

By Cloudmersive
2 min read
Receiving a document is straightforward enough — but making sure we receive the right document takes a bit more planning.
Supplies might mistakenly upload quotes instead of invoices, or they might send invoices without crucial details like the invoice number or total.
Our portal receives both sets of documents just fine, but it leaves the next person or workflow to untangle the problem we ignored.
Here's a groundbreaking idea: let's catch those issues before we approve submissions.
We'll use Cloudmersive Document AI to evaluate the contents of each upload, and we'll write some code for our C# application to make the acceptance decision.
Defining what belongs in our workflow
For our invoice portal, we'll keep the requirements simple.
We want documents to be final supplier invoices with supplier names, invoice numbers, and totals. Invoices are generally useless without that information.
We'll use the PolicyRule model from the Cloudmersive.APIClient.NETCore.DocumentAI 3.0.0 to describe those requirements in natural language.
Here, we'll use two DENY rules to identify documents we refuse to accept.
private static List<PolicyRule> CreateRules() => new()
{
new PolicyRule(
ruleId: "invoice-type",
ruleType: "DENY",
ruleDescription:
"Reject documents that are not final supplier invoices. " +
"Reject quotations, estimates, pro forma invoices and drafts."),
new PolicyRule(
ruleId: "required-details",
ruleType: "DENY",
ruleDescription:
"Reject documents missing a supplier name, an invoice number, " +
"or a total amount due.")
};private static List<PolicyRule> CreateRules() => new()
{
new PolicyRule(
ruleId: "invoice-type",
ruleType: "DENY",
ruleDescription:
"Reject documents that are not final supplier invoices. " +
"Reject quotations, estimates, pro forma invoices and drafts."),
new PolicyRule(
ruleId: "required-details",
ruleType: "DENY",
ruleDescription:
"Reject documents missing a supplier name, an invoice number, " +
"or a total amount due.")
};Checking the uploaded document
Now we'll bring those rules into our upload application.
We'll copy the received file into a byte array and include it in a DocumentPolicyRequest.
The configured _policyApi client then evaluates that request through ApplyRulesAsync().
using var buffer = new MemoryStream();
await file.CopyToAsync(buffer, cancellationToken);
var request = new DocumentPolicyRequest(
inputFile: buffer.ToArray(),
rules: CreateRules());
var result = await _policyApi.ApplyRulesAsync(
request, cancellationToken);using var buffer = new MemoryStream();
await file.CopyToAsync(buffer, cancellationToken);
var request = new DocumentPolicyRequest(
inputFile: buffer.ToArray(),
rules: CreateRules());
var result = await _policyApi.ApplyRulesAsync(
request, cancellationToken);We're basically just asking whether the document meets our requirements, rather than taking a filename like final invoice.pdf at its word.
Making the acceptance decision
The API response we get includes a CleanResult and any RuleViolations that might've been identified.
In the context of this API, "CleanResult": true just means the document complies with our business policies, and "CleanResult": false means the opposite.
We'll build it into our code that we require a passing result without reported violations; otherwise, the document stays unapproved:
if (result is null)
return PolicyUnavailable();
if (!result.CleanResult || result.RuleViolations is { Count: > 0 })
{
return UnprocessableEntity(new
{
accepted = false,
status = "policy_rejected",
ruleIds = result.RuleViolations?
.Where(violation => violation is not null)
.Select(violation => violation.RuleId)
.ToArray() ?? Array.Empty<string>()
});
}
return Ok(new { accepted = true, status = "policy_passed" });if (result is null)
return PolicyUnavailable();
if (!result.CleanResult || result.RuleViolations is { Count: > 0 })
{
return UnprocessableEntity(new
{
accepted = false,
status = "policy_rejected",
ruleIds = result.RuleViolations?
.Where(violation => violation is not null)
.Select(violation => violation.RuleId)
.ToArray() ?? Array.Empty<string>()
});
}
return Ok(new { accepted = true, status = "policy_passed" });We've also separated "unavailable" checks from "rule failures" through PolicyUnavailable(). API failures return 503.
Conclusion
With just a few lines of code, we've asked C# portals an important question: does this submission belong in a downstream invoice workflow?
Invoices are just one good example of documents that benefit from content validation before processing — we can apply this same logic to contracts, resumes, and other documents that require a minimum set of information to move be useful.
AI is great at enforcing criteria.