Commercial submission intake is document work: ACORD 125s and 126s, loss runs, supplemental applications, most of them PDFs, some of them photographs of paper. Before anything lands in the AMS, someone reads those documents and keys what they find. Amazon Textract's Queries feature is the most direct tool we know for the reading step: you ask the document a plain-English question and get back an answer with a confidence score and a location on the page.
This tutorial walks through calling Textract Queries from Python against an ACORD form, pairing the answers with the questions, and deciding what to trust. It assumes an AWS account and basic Python.
How Queries work
Textract's AnalyzeDocument API takes a document plus a list of feature types. With the QUERIES feature you also pass natural-language questions, each with an alias. The response is a flat list of Block objects; each question comes back as a QUERY block linked to a QUERY_RESULT block that carries the answer text, a confidence score, and the answer's position on the page. The Queries response documentation shows the exact shape.
Two properties make this a good fit for ACORD forms. First, you do not need a template per form version; the question "What is the named insured?" survives layout changes that break coordinate-based extraction. Second, the confidence score gives you an honest routing signal: high-confidence answers can flow onward, low-confidence ones go to a person.
Set up
Install and authenticate
python -m venv .venv && source .venv/bin/activate
pip install boto3
Credentials come from your usual AWS configuration (environment variables, a profile, or an instance role). The calling identity needs the textract:AnalyzeDocument IAM permission.
Make the first call
Synchronous AnalyzeDocument takes a single-page document of up to 10 MB. That suits page-at-a-time processing; for a full multi-page submission packet, use the asynchronous StartDocumentAnalysis variant with the same queries configuration.
import boto3
textract = boto3.client("textract", region_name="us-east-1")
with open("acord-125-page1.png", "rb") as f:
document = f.read()
response = textract.analyze_document(
Document={"Bytes": document},
FeatureTypes=["QUERIES"],
QueriesConfig={
"Queries": [
{"Text": "What is the named insured?", "Alias": "NAMED_INSURED"},
{"Text": "What is the proposed effective date?", "Alias": "EFFECTIVE_DATE"},
{"Text": "What is the applicant's mailing address?", "Alias": "MAILING_ADDRESS"},
{"Text": "What is the FEIN?", "Alias": "FEIN"},
]
},
)
Read the response
Pair questions with answers
Answers are linked to questions by block relationships, not by list position, so index the blocks by id and follow each query's ANSWER relationship:
def query_answers(blocks):
by_id = {b["Id"]: b for b in blocks}
answers = {}
for block in blocks:
if block["BlockType"] != "QUERY":
continue
alias = block["Query"]["Alias"]
answers[alias] = None
for rel in block.get("Relationships", []):
if rel["Type"] == "ANSWER":
result = by_id[rel["Ids"][0]]
answers[alias] = {
"text": result.get("Text"),
"confidence": result.get("Confidence"),
}
return answers
print(query_answers(response["Blocks"]))
A question Textract cannot answer simply has no linked result, which is why the code seeds None: an unanswered query is a signal, not an error, and your pipeline should treat it as "route to a human", never as an empty string.
Choose a confidence policy
Decide, per field, what confidence is enough, and route everything below it to review. A date feeding a renewal deadline deserves a stricter threshold than a contact name. Whatever thresholds you pick, log the score alongside the extracted value so a reviewer can see why something was queued.
Queries that work on ACORD forms
Question phrasing matters more than anything else, and phrasings that echo the form's own labels do best. A starting set for an ACORD 125:
| Query text | Alias | Where it lands in the AMS |
|---|---|---|
| What is the named insured? | NAMED_INSURED | Account or prospect name |
| What is the applicant's mailing address? | MAILING_ADDRESS | Account address |
| What is the proposed effective date? | EFFECTIVE_DATE | Policy effective date |
| What is the FEIN? | FEIN | Account tax id |
| What is the description of operations? | OPERATIONS | Risk narrative |
Treat the aliases as your stable contract: downstream code keys off the alias, and you can rephrase the question text freely while you tune.
Where this fits in an agency pipeline
Extraction is the first third of quote intake. The rest is reconciliation (the ACORD says one thing, the loss runs another), AMS write-back through your management system's API, and the gap list a producer actually wants. And whatever the confidence scores say, a licensed person approves what goes out; the scores decide how much checking a document needs, not whether checking happens.
This is the pipeline our Quote-Intake Agent work builds out. If you are wiring one up for your own agency and want a senior engineer alongside, contact us.