Extract data from an invoice

Step by step guide to read an invoice into JSON with TsgcAIDocumentExtractor.

Introduction

This guide reads a PDF invoice with TsgcAIDocumentExtractor and the built in invoice preset, checks the confidence of the extracted fields and falls back to a local OCR when the model can not read the file.

 

1. Configure the chat and the extractor

The extractor sends the document through a TsgcAIChat. Pick a provider that accepts PDF documents (OpenAI, Anthropic or Gemini) and a model with vision support. Select dpInvoice as preset: no schema needs to be written.

 

uses
  SysUtils, sgcAI, sgcAI_Chat, sgcAI_DocumentExtractor;

procedure ExtractInvoice(const aFileName: string);
var
  oChat: TsgcAIChat;
  oExtractor: TsgcAIDocumentExtractor;
  vJSON: string;
begin
  oChat := TsgcAIChat.Create(nil);
  oExtractor := TsgcAIDocumentExtractor.Create(nil);
  Try
    oChat.Provider := aicpOpenAI;
    oChat.ChatOptions.ApiKey := 'API_KEY';
    oChat.ChatOptions.Model := 'gpt-4o-mini';

    oExtractor.AIChat := oChat;
    oExtractor.Preset := dpInvoice;
    oExtractor.MinConfidence := 0.8;

    vJSON := oExtractor.ExtractFile(aFileName);
    WriteLn(vJSON);
    WriteLn(Format('Lowest confidence: %.2f',
      [oExtractor.LastResult.Confidence]));
  Finally
    oExtractor.Free;
    oChat.Free;
  End;
end;

 

2. Read the result

ExtractFile returns the data object as JSON, the same text as LastResult.Data. With the invoice preset it has these members, and every value the invoice does not show is null:

 

 

Parse the text with any JSON library. A shortened answer looks like this:

 

{"invoice_number":"F-2026-0142","issue_date":"2026-09-01","due_date":"2026-10-01",
 "currency":"EUR","supplier":{"name":"Acme SL","tax_id":"B12345678","address":null},
 ...,"subtotal":100,"tax_total":21,"total":121,"payment_terms":null,"iban":null}

 

3. Check the confidence

The model returns a confidence between 0 and 1 for every field it extracted. LastResult.Confidence is the lowest one, LastResult.FieldConfidence returns the value of one path, and OnLowConfidence is raised for every field under MinConfidence. Use them to send doubtful invoices to a manual review. The value is the self assessment of the model, not a guarantee.

 

procedure TForm1.oExtractorLowConfidence(Sender: TObject;
  const aField: string; aConfidence: Double);
begin
  Memo1.Lines.Add(Format('Review %s (%.2f)', [aField, aConfidence]));
end;

// after ExtractFile
if oExtractor.LastResult.FieldConfidence('total') < 0.9 then
  Memo1.Lines.Add('Check the total by hand');

 

4. Handle invalid data

The data is validated against the invoice schema. When it does not validate, the model is asked once more with the list of errors (ValidationRetries). If it is still invalid, EsgcAIDocumentExtractor is raised with the errors in its message. Set RaiseOnInvalid to False to receive the data anyway and read LastResult.Valid and LastResult.ValidationErrors instead. Transport and provider errors of the chat are raised as they are.

 

try
  vJSON := oExtractor.ExtractFile('invoice.pdf');
except
  on E: EsgcAIDocumentExtractor do
    WriteLn('Not extracted: ' + E.Message);
end;

 

5. Scanned invoices and the local OCR

A scanned invoice is an image, which every provider accepts. When the model can not read it, for example because the selected model has no vision support, link a TsgcAIOCRTesseract or a TsgcAIOCRWindows and set OCRMode to domFallback: the failed request raises OnOCRFallback and the data is extracted from the text recognised locally. domAlways skips the vision request, so a text only model can be used. The local OCR accepts images only, and it is available in Delphi, C++Builder and .NET. TsgcAIOCRTesseract needs Tesseract installed on the machine.

 

oOCR := TsgcAIOCRTesseract.Create(nil);
oOCR.Language := 'eng';
oExtractor.OCR := oOCR;
oExtractor.OCRMode := domFallback;

vJSON := oExtractor.ExtractFile('scanned_invoice.png');
if oExtractor.LastResult.UsedOCR then
  WriteLn('Extracted from the OCR text');

 

Or use the OCR built into Windows 10 and later, with nothing to install. Its Language is a BCP-47 tag such as en-US, or empty for the languages of the user profile, and the OCR language pack must be installed in Windows.

 

oWinOCR := TsgcAIOCRWindows.Create(nil);
oWinOCR.Language := 'en-US';
oExtractor.OCR := oWinOCR;
oExtractor.OCRMode := domFallback;

 

Your own fields

To add a field the preset does not have, copy the preset schema with GetPresetSchema, add the field, and set Preset to dpCustom. With OpenAI keep the strict rules: list the new field in required and give it a nullable type.

 

See Also