Proof of Address OCR

Extract recipient and issuer information from utility bills, bank statements, payslips, and more.

Using Mindee's Proof of Address API, you can automatically extract key information about the recipient or the issuer of a document to help you automate customer onboarding or KYC processes:

  • Issuer Name
  • Issuer Address
  • Issuer Company Registrations numbers
  • Recipient Name
  • Recipient Address
  • Recipient Company Registration numbers
  • Issuance Date
  • Dates
  • Currency
  • Language
  • Orientation

The Proof of Address OCR API supports documents from any geographies and languages.

Set up the API

Before making any API calls, you need to have created your API key.

  1. You'll need to get a utility bill, or any document containing an address block. You can use the following bill for your tests:
824

Mindee proof of address OCR example document

  1. Access your Proof of Address OCR API by clicking on the corresponding product card in the Document Catalog.

  1. From the left navigation, go to documentation > API reference, you'll find sample code in popular languages and the command line.
1902

Proof of address OCR documentation

API_KEY='my-api-key-here'
ACCOUNT='mindee'
ENDPOINT='proof_address'
VERSION='1'
FILE_PATH='/path/to/your/file.png'

# Maximum amount of retries to get the result of a queue
MAX_RETRIES=10

# Delay between requests
DELAY=6

# Enqueue the document for async parsing
QUEUE_RESULT=$(curl -sS --request POST \
  -H "Authorization: Token $API_KEY" \
  -H "Content-Type: multipart/form-data" \
  -F "document=@$FILE_PATH" \
  "https://api.mindee.net/v1/products/$ACCOUNT/$ENDPOINT/v$VERSION/predict_async")

# Status code sent back from the server
STATUS_CODE=$(echo "$QUEUE_RESULT" | grep -oP "[\"|']status_code[\"|']:[\s][\"|']*[a-zA-Z0-9-]*" | rev | cut --complement -f2- -d" " | rev)

# Check that the document was properly queued
if [ -z "$STATUS_CODE" ] || [ "$STATUS_CODE" -gt 399 ] || [ "$STATUS_CODE" -lt 200 ]
then
  if [ -z "$STATUS_CODE" ]
  then
    echo "Request couldn't be processed."
    exit 1
  fi
  echo "Error $STATUS_CODE was returned by API during enqueuing. "

  # Print the additional details, if there are any:
  ERROR=$(echo "$QUEUE_RESULT" | grep -oP "[\"|']error[\"|']:[\s]\{[^\}]*" | rev | cut --complement -f2- -d"{" | rev)
  if [ -z "$ERROR" ]
  then
    exit 1
  fi

  # Details on the potential error:
  ERROR_CODE=$(echo "$ERROR" | grep -oP "[\"|']code[\"|']:[\s]\"[^(\"|\')]*" | rev | cut --complement -f2- -d"\"" | rev)
  MESSAGE=$(echo "$QUEUE_RESULT" | grep -oP "[\"|']message[\"|']:[\s]\"[^(\"|\')]*" | rev | cut --complement -f2- -d"\"" | rev)
  DETAILS=$(echo "$QUEUE_RESULT" | grep -oP "[\"|']details[\"|']:[\s]\"[^(\"|\')]*" | rev | cut --complement -f2- -d"\"" | rev)
  echo "This was the given explanation:"
  echo "-------------------------"
  echo "Error Code: $ERROR_CODE"
  echo "Message: $MESSAGE"
  echo "Details: $DETAILS"
  echo "-------------------------"
  exit 1
else

  echo "File sent, starting to retrieve from server..."

  # Get the document's queue ID
  QUEUE_ID=$(echo "$QUEUE_RESULT" | grep -oP "[\"|']id[\"|']:[\s][\"|'][a-zA-Z0-9-]*" | rev | cut --complement -f2- -d"\"" | rev)

  # Amount of attempts to retrieve the parsed document were made
  TIMES_TRIED=1

  # Try to fetch the file until we get it, or until we hit the maximum amount of retries
  while [ "$TIMES_TRIED" -lt "$MAX_RETRIES" ]
  do
    # Wait for a bit at each step
    sleep $DELAY

    # Note: we use -L here because the location of the file might be behind a redirection
    PARSED_RESULT=$(curl -sS -L \
      -H "Authorization: Token $API_KEY" \
      "https://api.mindee.net/v1/products/$ACCOUNT/$ENDPOINT/v$VERSION/documents/queue/$QUEUE_ID")

    # Isolating the job (queue) & the status to monitor the document
    JOB=$(echo "$PARSED_RESULT" | grep -ioP "[\"|']job[\"|']:[\s]\{[^\}]*" | rev | cut --complement -f2- -d"{" | rev)
    QUEUE_STATUS=$(echo "$JOB" | grep -ioP "[\"|']status[\"|']:[\s][\"|'][a-zA-Z0-9-]*" | rev | cut --complement -f2- -d"\"" | rev)
    if [ "$QUEUE_STATUS" = "completed" ]
    then
      # Print the result
      echo "$PARSED_RESULT"

      # Optional: isolate the document:
      # DOCUMENT=$(echo "$PARSED_RESULT" | grep -ioP "[\"|']document[\"|']:[\s].*([\"|']job[\"|'])" | rev | cut -f2- -d"," | rev)
      # echo "{$DOCUMENT}"

      # Remark: on compatible shells, fields can also be extracted through the use of tools like jq:
      # DOCUMENT=$(echo "$PARSED_RESULT" | jq '.["document"]')
      exit 0
    fi
    TIMES_TRIED=$((TIMES_TRIED+1))
  done
fi

echo "Operation aborted, document not retrieved after $TIMES_TRIED tries"
exit 1
from mindee import Client, AsyncPredictResponse, product

# Init a new client
mindee_client = Client(api_key="my-api-key-here")

# Add the corresponding endpoint (document). Set the account_name to "mindee" if you are using OTS.
my_endpoint = mindee_client.create_endpoint(
    account_name="mindee",
    endpoint_name="proof_address",
    version="1"
)

# Load a file from disk
input_doc = mindee_client.source_from_path("/path/to/the/file.ext")

# Parse the file.
# The endpoint must be specified since it cannot be determined from the class.
result: AsyncPredictResponse = mindee_client.enqueue_and_parse(
    product.GeneratedV1,
    input_doc,
    endpoint=my_endpoint
)

# Print a brief summary of the parsed data
print(result.document)

# # Iterate over all the fields in the document
# for field_name, field_values in result.document.inference.prediction.fields.items():
#     print(field_name, "=", field_values)
const mindee = require("mindee");
// for TS or modules:
// import * as mindee from "mindee";

// Init a new client
const mindeeClient = new mindee.Client({ apiKey: "my-api-key-here" });

// Load a file from disk
const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext");

// Create a custom endpoint for your product
const customEndpoint = mindeeClient.createEndpoint(
  "proof_address",
  "mindee",
  "1" // Defaults to "1"
);

// Parse the file asynchronously.
const asyncApiResponse = mindeeClient.enqueueAndParse(
  mindee.product.GeneratedV1,
  inputSource,
  { endpoint: customEndpoint }
);

// Handle the response Promise
asyncApiResponse.then((resp) => {
  // print a string summary
  console.log(resp.document.toString());
});
require 'mindee'

# Init a new client
mindee_client = Mindee::Client.new(api_key: 'my-api-key-here')

# Load a file from disk
input_source = mindee_client.source_from_path('/path/to/the/file.ext')

# Initialize a custom endpoint for this product
custom_endpoint = mindee_client.create_endpoint(
  account_name: 'mindee',
  endpoint_name: 'proof_address',
  version: '1'
)

# Parse the file
result = mindee_client.enqueue_and_parse(
  input_source,
  Mindee::Product::Generated::GeneratedV1,
  endpoint: custom_endpoint
)

# Print a full summary of the parsed data in RST format
puts result.document
using Mindee;
using Mindee.Input;
using Mindee.Http;
using Mindee.Product.Generated;

string apiKey = "my-api-key-here";
string filePath = "/path/to/the/file.ext";

// Construct a new client
MindeeClient mindeeClient = new MindeeClient(apiKey);

// Load an input source as a path string
// Other input types can be used, as mentioned in the docs
var inputSource = new LocalInputSource(filePath);

// Set the endpoint configuration
CustomEndpoint endpoint = new CustomEndpoint(
    endpointName: "proof_address",
    accountName: "mindee",
    version: "1"
);

// Call the product asynchronously with auto-polling
var response = await mindeeClient
    .EnqueueAndParseAsync<GeneratedV1>(inputSource, endpoint);

// Print a summary of all the predictions
System.Console.WriteLine(response.Document.ToString());

// Print only the document-level predictions
// System.Console.WriteLine(response.Document.Inference.Prediction.ToString());
import com.mindee.MindeeClient;
import com.mindee.input.LocalInputSource;
import com.mindee.parsing.common.AsyncPredictResponse;
import com.mindee.product.generated.GeneratedV1;
import com.mindee.http.Endpoint;
import java.io.File;
import java.io.IOException;

public class SimpleMindeeClient {

  public static void main(String[] args) throws IOException, InterruptedException {
    String apiKey = "my-api-key-here";
    String filePath = "/path/to/the/file.ext";

    // Init a new client
    MindeeClient mindeeClient = new MindeeClient(apiKey);

    // Load a file from disk
    LocalInputSource inputSource = new LocalInputSource(new File(filePath));

    // Configure the endpoint
    Endpoint endpoint = new Endpoint(
        "proof_address",
        "mindee",
        "1"
    );

    // Parse the file asynchronously
    AsyncPredictResponse<GeneratedV1> response = mindeeClient.enqueueAndParse(
        GeneratedV1.class,
        endpoint,
        inputSource
    );

    // Print a summary of the response
    System.out.println(response.toString());

    // Print a summary of the predictions
//  System.out.println(response.getDocumentObj().toString());

    // Print the document-level predictions
//    System.out.println(response.getDocumentObj().getInference().getPrediction().toString());

    // Print the page-level predictions
//    response.getDocumentObj().getInference().getPages().forEach(
//        page -> System.out.println(page.toString())
//    );
  }

}
  • Replace my-api-key-here with your new API key, or use the select an API key feature and it will be filled automatically.
  • Copy and paste the sample code of your desired choice in your application, code environment, terminal etc.
  • Replace /path/to/your/file/png with the path to your document.

❗️

Always remember to replace your API key!

  1. Run your code. You will receive a JSON response with the document details.

API Response

Below is the full sample JSON response you get when you call the API. Since the response is quite verbose, we will walk through the fields section by section.

{
  "api_request": {
    "error": {},
    "resources": [
      "document"
    ],
    "status": "success",
    "status_code": 201,
    "url": "http://api.mindee.net/v1/products/mindee/proof_of_address/v1/predict"
  },
  "document": {
    "id":  "ecdbe7bd-1037-47a5-87a8-b90d49475a1f",
    "name": "sample_invoce.jpeg",
    "n_pages": 1,
    "is_rotation_applied": true,
    "inference": {
      "started_at": "2021-05-06T16:37:28",
      "finished_at": "2021-05-06T16:37:29",
      "processing_time": 1.125,
      "pages": [
        {
          "id": 0,
          "orientation": {"value": 0},
          "prediction": { .. },
          "extras": {}
        }
      ],
      "prediction": { .. },
      "extras": {}
    }
  }
}

You can find the prediction within the prediction key found in document > inference > prediction` for document-level predictions: it contains the different fields extracted at the document level, meaning that for multi-pages PDFs, we reconstruct a single document object using all the pages.

Each predicted field may contain one or several values:

  • a polygon highlighting the information location
  • a page_id where the information was found (document level only)
{
  "prediction": {
    "recipient_company_registrations": [
     {
      "confidence": 0.99,
      "page_id": 0,
      "polygon": [[ 0.515, 0.962 ], [ 0.59, 0.962 ], [ 0.59, 0.973 ], [ 0.515, 0.973 ]],
      "type": "SIRET",
      "value": "XXX81125600010"
    },
    {
      "confidence": 0.99,
      "page_id": 0,
      "polygon": [[ 0.658, 0.963 ], [ 0.729, 0.963 ], [ 0.729, 0.973 ], [ 0.658, 0.973 ]],
      "type": "VAT NUMBER",
      "value": "FR44837811XXX"
      }
    ],
    "recipient_name": {
      "confidence": 0.84,
      "page_id": 0,
      "polygon": [[0.035, 0.284], [0.098, 0.284], [0.098, 0.296], [0.035, 0.296]],
      "value": "JIRO DOI"
    },
    "recipient_address": {
      "confidence": 0.3,
      "page_id": 0,
      "polygon": [[0.035, 0.304], [0.214, 0.304], [0.214, 0.353], [0.035, 0.353]],
      "value": "1954 Bloon Street West Toronto, ON, M6P 3K9 Canada"
    },
    "issuer_company_registrations":[
      {
      "confidence": 0.84,
      "page_id": 0,
      "polygon": [[0.113, 0.251], [0.206, 0.251], [0.206, 0.266], [0.113, 0.266]],
      "type": "TIN",
      "value": "736952710"
   		 }
    ],
    "dates": [
      {
        "confidence": 0.99,
        "page_id": 0,
        "polygon": [[0.842, 0.305], [0.931, 0.305], [0.931, 0.319], [0.842, 0.319]],
        "value": "2018-09-25"
    	}
    ],
    "issuance_date": {
      "confidence": 0.99,
      "page_id": 0,
      "polygon": [[0.842, 0.305], [0.931, 0.305], [0.931, 0.319], [0.842, 0.319]],
      "value": "2018-09-25"
    },
    "issuer_name": {
      "confidence": 0.72,
      "page_id": 0,
      "polygon": [[0.164, 0.087], [0.4, 0.087], [0.4, 0.147], [0.164, 0.147]],
      "value": "TURNPIKE DESIGNS CO."
    },
    "issuer_address": {
      "confidence": 0.49,
      "page_id": 0,
      "polygon": [[0.756, 0.128], [0.964, 0.128], [0.964, 0.162], [0.756, 0.162]],
      "value": "156 University Ave, Toronto ON, Canada M5H 2H7"
    }
  }
}

For each document, the following fields are extracted.

  • Document Type
  • Issuer Company Registrations
  • Supplier Company Registrations
  • Recipient Address
  • Issuer Name
  • Recipient Name
  • Locale
  • Issuer Address
  • Dates
  • Issue Date

Recipient Information

  • recipient_name: In the JSON response, we have the value of the recipient name as found on the document.
{
  "recipient_name": {
    "confidence": 0.84,
    "page_id": 0,
    "polygon": [[0.035, 0.284], [0.098, 0.298], [0.098, 0.296], [0.035, 0.296]],
    "value": "JIRO DOI"
  }
}
  • recipient_address: In the JSON response, we have the value of the recipient address as found on the document.
{
  "recipient_address": {
    "confidence": 0.3,
    "page_id": 0,
    "polygon": [[0.035, 0.304], [0.214, 0.304], [0.214, 0.353], [0.035, 0.0353]],
    "value": "1954 Bloon Street West Toronto, ON, M6P 3K9 Canada"
  }
}
{
  "recipient_company_registrations": [
    {
      "confidence": 0.99,
      "page_id": 0,
      "polygon": [[ 0.515, 0.962 ], [ 0.59, 0.962 ], [ 0.59, 0.973 ], [ 0.515, 0.973 ]],
      "type": "SIRET",
      "value": "XXX81125600010"
    },
    {
      "confidence": 0.99,
      "page_id": 0,
      "polygon": [[ 0.658, 0.963 ], [ 0.729, 0.963 ], [ 0.729, 0.973 ], [ 0.658, 0.973 ]],
      "type": "VAT NUMBER",
      "value": "FR44837811XXX"
    }
  ]
}

Issuer Information

{
  "issuer_company_registrations": [
    {
    "confidence": 0.99,
    "page_id": 0,
    "polygon": [[0.515, 0.962], [0.59, 0.962], [0.59, 0.973], [0.515, 0.973]],
    "type": "SIRET",
    "value": "XXX81125600010"
 		 },

		{
    "confidence": 0.99,
    "page_id": 0,
    "polygon": [[0.658, 0.963], [0.729, 0.963], [0.729, 0.973], [0.658, 0.973]],
    "type": "VAT",
    "value": "FR44837811XXX"
  	}
 ]
}
  • issuer_name: In the JSON response below, we have the value of the issuer name as written in the document.
{
  "issuer_name": {
    "confidence": 0.11,
    "page_id": 0,
    "polygon": [[0.165, 0.089], [0.385, 0.089], [0.385, 0.145], [0.165, 0.145]],
    "value": "DESIGNS TURNPIKE CO"
  }
}
  • issuer_address: In the JSON response, we have the value of the issuer address as found on the document.
{
  "issuer_address": {
    "confidence": 0.49,
    "page_id": 0,
    "polygon": [[0.756, 0.128], [0.964, 0.128], [0.964, 0.162], [0.756, 0.162]],
    "value": "156 University Ave, Toronto ON, Canada M5H 2H7"
  }
}

Dates

  • Issuance_date: In the JSON response below, we have the value of the issuance date in an ISO format (yyyy-mm-dd).
{
  "issuance_date": {
    "confidence": 0.99,
    "page_id": 0,
    "polygon": [[0.84, 0.305], [0.932, 0.305], [0.932, 0.318], [0.84, 0.318]],
    "value": "2018-09-25"
  }
}
  • dates: In the JSON response below, we have the list of all dates extracted in the document in an ISO format(yyyy-mm-dd).
{
  "due_date": {
    "confidence": 0.86,
    "page_id": 0,
    "polygon": [[0.841, 0.323], [0.941, 0.323], [0.941, 0.338], [0.841, 0.338]],
    "raw": "Upon receipt",
    "value": "2018-09-25"
  }
}

Locale

  • locale: In the JSON response, we have the currency and language found on the document.
    • language (String): Language code in ISO 639-1 format as seen on the document. The following language codes are supported: ca, de, en, es, fr, it, nl and pt.
    • currency (String): Currency code in ISO 4217 format as seen on the document. The following country codes are supported: USD, EUR, GBP, CAD, CHF, AED, AUD, BRL, CNY, COP, CZK, DKK, GNF, HKD, HUF, JPY, NOK, NZD, PLN, SEK, SGD, XPF
{
  "locale": {
    "confidence": 0.94,
    "currency": "CAD",
    "language": "en"
  }
}

Orientation

  • orientation: The orientation field is only available at the page level as it describes whether the page image should be rotated to be upright. The rotation value is also conveniently available in the JSON response at:
    document > inference > pages [ ] > orientation > value.
    If the page requires rotation for correct display, the orientation field gives a prediction among these 3 possible outputs:
    • 0 degree: the page is already upright
    • 90 degrees: the page must be rotated clockwise to be upright
    • 270 degrees: the page must be rotated counterclockwise to be upright

In our example, the receipt doesn't require any rotation.

{
  "orientation": {
    "confidence": 0.99,
    "degrees": 0
  }
}

📘

All polygon fields across the JSON response are already rotated accordingly!