How to extract data from commission statements with Sensible

Updated on
May 15, 2026
5
min read
Contributors
No items found.
Author
How to extract data from commission statements with Sensible
Table of contents
Turn documents into structured data
Get started free
Share this post

Introduction: What are commission statements?

Commission statements are carrier-issued documents that detail producer compensation on policies written, renewed, endorsed, or cancelled during a billing period. Reconciling them manually across dozens of carriers introduces keying errors and delays close. Sensible's layout-based extraction targets carrier-specific column structures and returns typed, schema-validated output per commission row through a single API endpoint.

Insurance commission statements are the operational backbone of broker and MGA back-office teams. Every carrier issues one, typically monthly, detailing what each producer earned on each policy transaction during the period. A mid-size brokerage receiving statements from 20 or more carriers each month faces hundreds of line items per statement to process: policy numbers, product types, transaction dates, premium amounts, commission rates, and net commission due. Working through that volume manually is error-prone and slow.

This post uses Sun Life's commission statement as a worked example. Every carrier has its own layout — column ordering, label conventions, subtotal placement — so the right approach is a per-carrier deterministic config: fixed column positions and consistent label text mean the same config extracts reliably every month. For the long tail of smaller carriers, a generalized LLM config handles the same fields without per-carrier work. Both run through the same API.

What we'll cover:

  • Extract the statement-level disbursement total
  • Extract commission line items using nested Sections
  • Propagate policy and broker totals into each line item row


Prerequisites

To extract from this document, take the following steps:


Extract the statement total


Sun Life places the disbursement total to the right of the text "Disbursement this period via EFT." The Row method captures the value by scanning to the right along the same horizontal line as that anchor, with tiebreaker: "last" returning the last populated cell in the row.

Here is the query we'll use:


  {
    "id": "commission_total",
    "type": {
      "id": "currency",
      "accountingNegative": "anyParentheses" /* treat values in parentheses as negative, e.g. $(56.23) */
    },
    "anchor": {
      "match": {
        "text": "Disbursement this period via EFT", /* appears on the summary page, not the detail pages */
        "type": "startsWith"
      }
    },
    "method": {
      "id": "row",
      "position": "right",
      "tiebreaker": "last" /* grabs the rightmost value in the row; defensive against document variants where Base and
  Override columns also appear on this line, in which case the rightmost value is the total */
    }
  }

Extracted value:


{
  "commission_total": {
    "source": "$2,547.43",
    "value": 2547.43,
    "unit": "$",
    "type": "currency"
  }
}

The currency type with accountingNegative: "anyParentheses" parses parenthetical negatives as negative numbers, covering statements that include chargebacks in the disbursement row. A validation rule could flag any statement where commission_total falls outside an expected range, catching anomalies before they reach the reconciliation queue.

Extract commission line items

Sun Life's statement has three nested layers: broker → policy → transaction. Sensible's Sections method mirrors this structure with three nested section definitions.



Here are the queries we'll use to extract from these sections:


    {
  "fields": [
    {
      "id": "commission_total",
      "type": {
        "id": "currency",
        "accountingNegative": "anyParentheses" /* treat values in parentheses as negative, e.g. $(56.23) */
      },
      "anchor": {
        "match": {
          "text": "Disbursement this period via EFT" /* appears on the summary page, not the detail pages */,
          "type": "startsWith"
        }
      },
      "method": {
        "id": "row",
        "position": "right",
        "tiebreaker": "last" /* grabs the rightmost value in the row; defensive against document variants where Base and Override columns also appear on this line, in which case the rightmost value is the total */
      }
    },
    {
      "id": "_policies_raw" /* prefixed with _ because this intermediate sections array will be suppressed in final output */,
      "type": "sections" /* extracts repeating sections; returns each section as an object in an array */,
      "range": {
        "anchor": {
          "start": {
            "text": "Current Period Detail" /* ignore document content before this heading */,
            "type": "startsWith"
          },
          "match": {
            "text": "Policy Holder Name" /* each broker section starts at the column header row containing this label */,
            "type": "includes"
          }
        },
        "requireStop": true /* end each broker section at the stop match, not at the next anchor match — needed because "Policy Holder Name" repeats inside each section */,
        "stop": {
          "text": "Total for Broker" /* each broker section ends at this summary line */,
          "type": "startsWith"
        },
        "stopOffsetY": 0 /* stop exactly at the top boundary of "Total for Broker", with no offset */,
        "externalRange": {
          /* exposes the column header row to Intersection method fields inside each section — needed because headers appear once per broker section, not inside each policy or transaction */
          "anchor": {
            "text": "Policy Holder Name",
            "type": "includes",
            "reverse": true /* search backwards from each section's start to find the nearest preceding column header row */
          },
          "stop": {
            "text": "Policy Holder Name",
            "type": "endsWith"
          },
          "offsetY": -0.2 /* start the external range 0.2" above the anchor line to capture the full header row */,
          "stopOffsetY": 0.1 /* extend 0.1" past the stop line's top boundary to include the full header row */
        }
      },
      "fields": [
        {
          "id": "_total_for_broker" /* prefixed with _ ; used downstream via customComputation to copy broker total into each transaction */,
          "type": {
            "id": "currency",
            "accountingNegative": "anyParentheses" /* treat values in parentheses as negative, e.g. $(56.23) */
          },
          "anchor": {
            "match": {
              "text": "Total for Broker",
              "type": "startsWith"
            }
          },
          "method": {
            "id": "row",
            "position": "right",
            "tiebreaker": "first" /* extract the first currency value to the right of "Total for Broker" */
          }
        },
        {
          "id": "_policies" /* nested sections: each broker section contains one or more policy sections */,
          "type": "sections",
          "range": {
            "anchor": {
              "match": {
                "text": "Base",
                "type": "equals" /* "Base" is the Commission Type column value present on every transaction row; policies are delimited by requireStop + "Total for Policy", not by unique anchor text */
              }
            },
            "requireStop": true /* end each policy section at "Total for Policy", not at the next "Base" match */,
            "stop": {
              "text": "Total for Policy",
              "type": "startsWith"
            },
            "stopOffsetY": 0 /* stop exactly at the top boundary of "Total for Policy", with no offset */,
            "externalRange": {
              /* same external column header range as the parent — exposes headers to Intersection fields inside each policy section */
              "anchor": {
                "text": "Policy Holder Name",
                "type": "includes",
                "reverse": true
              },
              "stop": {
                "text": "Policy Holder Name",
                "type": "endsWith"
              },
              "offsetY": -0.2,
              "stopOffsetY": 0.1
            }
          },
          "fields": [
            {
              "id": "_total_for_policy" /* prefixed with _ ; used downstream via copy_to_section to stamp the policy total onto each transaction */,
              "type": {
                "id": "currency",
                "accountingNegative": "anyParentheses"
              },
              "anchor": {
                "match": {
                  "text": "Total for Policy",
                  "type": "startsWith"
                }
              },
              "method": {
                "id": "row",
                "position": "right",
                "tiebreaker": "first" /* extract the first currency value to the right of "Total for Policy" */
              }
            },
            {
              "id": "transactions" /* innermost sections: each policy section contains one or more transaction rows */,
              "type": "sections",
              "requiredFields": [
                "transaction_date",
                "product_name"
              ] /* omit a section if either field is null — filters out non-transaction rows that match the decimal anchor pattern */,
              "range": {
                "anchor": {
                  "match": {
                    "type": "regex",
                    "pattern": "\\.\\d{2}" /* each transaction row contains a decimal currency amount; used as repeating anchor since transaction rows lack a unique starting marker */
                  }
                },
                "externalRange": {
                  /* exposes the column header row to Intersection method fields — needed to resolve which column each value belongs to */
                  "anchor": {
                    "text": "Policy Holder Name",
                    "type": "includes",
                    "reverse": true
                  },
                  "stop": {
                    "text": "Policy Holder Name",
                    "type": "endsWith"
                  },
                  "offsetY": -0.2,
                  "stopOffsetY": 0.1
                }
              },
              "fields": [
                {
                  "id": "policy_name",
                  "type": "string",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Policy Holder Name" /* resolves the x-position of the "Policy Holder Name" column; the intersection with the transaction row reads the policyholder name for that specific row */,
                        "type": "equals"
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.5,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "policy_number",
                  "type": "string",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Number",
                        "type": "equals",
                        "xRangeFilter": {
                          "minX": 0.3,
                          "maxX": 1.0 /* constrain to x range 0.3"–1.0" to avoid matching other "Number" text elsewhere on the page */
                        }
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.5,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "transaction_date",
                  "type": "string",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Period",
                        "type": "equals",
                        "xRangeFilter": {
                          "minX": 4.3,
                          "maxX": 5.0 /* constrain to x range 4.3"–5.0" to disambiguate from other "Period" column headers */
                        }
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.35,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "product_name",
                  "type": "string",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}" /* defines the horizontal axis of the intersection (the row) — anchors Sensible on each transaction row */
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      /* defines the vertical axis (the column); use horizontalAnchor instead to swap which anchor defines which axis */
                      "match": {
                        "text": "Benefit",
                        "type": "equals" /* extract the value in the "Benefit" column (e.g. Dental, Vision, Critical Illness) */
                      }
                    },
                    "offsetX": 0 /* default: 0. offset the vertical line left (negative) or right (positive) in inches */,
                    "offsetY": 0 /* default: 0. offset the horizontal line up (negative) or down (positive) in inches */,
                    "width": 0.5 /* default: 0 (creates a point intersection — Sensible extracts any line overlapping the point); here, 0.5" wide region to constrain the column match */,
                    "height": 0 /* default: 0; set alongside width to define a 2D region rather than a horizontal band */,
                    "percentOverlapX": 0 /* default: 0.9 — fraction of width overlap required for a line to be inside the region; 0 accepts any overlap */,
                    "percentOverlapY": 0 /* default: 0.8 — same as percentOverlapX but for height */
                  }
                },
                {
                  "id": "premium_applied",
                  "type": "number",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Share",
                        "type": "startsWith" /* matches the "Share %" column header, which is split across two lines in the document */
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.35,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "commission_rate",
                  "type": "number",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Comm",
                        "type": "equals",
                        "xRangeFilter": {
                          "minX": 5.0,
                          "maxX": 6.5 /* two columns are headed "Comm"; this range targets the "Comm %" column (left), not "Comm Amount" (right) */
                        }
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.35,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "transaction_premium_amount",
                  "type": {
                    "id": "currency",
                    "accountingNegative": "anyParentheses"
                  },
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Premium",
                        "type": "equals",
                        "xRangeFilter": {
                          "minX": 6.0,
                          "maxX": 7.2 /* constrain to x range 6.0"–7.2" to target the "Premium Amt" column */
                        }
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.7,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "commission_amount",
                  "type": {
                    "id": "currency",
                    "accountingNegative": "anyParentheses"
                  },
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Comm",
                        "type": "equals",
                        "xRangeFilter": {
                          "minX": 7.0,
                          "maxX": 8.0 /* targets the "Comm Amount" column (right "Comm"), not the "Comm %" column (left "Comm") */
                        }
                      }
                    },
                    "offsetX": 0,
                    "offsetY": 0,
                    "width": 0.45,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "transaction_type",
                  "type": "string",
                  "anchor": {
                    "match": {
                      "type": "regex",
                      "pattern": "\\.\\d{2}"
                    }
                  },
                  "method": {
                    "id": "intersection",
                    "verticalAnchor": {
                      "match": {
                        "text": "Transaction",
                        "type": "equals"
                      }
                    },
                    "offsetX": 0.1 /* shift 0.1" right of the intersection point to avoid clipping the first character */,
                    "offsetY": 0,
                    "width": 0.45,
                    "height": 0,
                    "percentOverlapX": 0,
                    "percentOverlapY": 0
                  }
                },
                {
                  "id": "total_for_policy",
                  "method": {
                    "id": "copy_to_section" /* stamps the parent policy's total onto each transaction row so it's available without rejoining sections downstream */,
                    "source_id": "_total_for_policy"
                  }
                },
                {
                  "id": "total_for_broker",
                  "method": {
                    "id": "customComputation",
                    "jsonLogic": {
                      "var": "../../_total_for_broker" /* climb two section levels (transaction → policy → broker) to reach _total_for_broker */
                    }
                  }
                }
              ]
            }
          ]
        },
        {
          "id": "_transactions_per_broker",
          "method": {
            "id": "copy_from_sections",
            "source_field": "transactions" /* flatten the transactions arrays out of the nested _policies sections... */,
            "source_sections": "_policies" /* ...and collect them into a single array at the broker level */
          }
        }
      ]
    },
    {
      "id": "billing_detail" /* final output array: one transaction object per row, merged across all broker sections */,
      "method": {
        "id": "copy_from_sections",
        "source_field": "_transactions_per_broker" /* the per-broker flat transaction arrays assembled inside _policies_raw */,
        "source_sections": "_policies_raw" /* lift and merge those arrays from each broker section into a single top-level array */
      }
    }
  ],
  "computed_fields": [
    {
      "id": "_cleanup",
      "method": {
        "id": "suppressOutput",
        "source_ids": [
          "_policies_raw"
        ] /* remove the intermediate nested sections structure from output; only billing_detail and commission_total appear in the final result */
      }
    }
  ]
}

Extracted values (policy- and broker-level fields and transaction details):


{
  "commission_total": {
    "source": "$2,547.43",
    "value": 2547.43,
    "unit": "$",
    "type": "currency"
  },
  "billing_detail": [
    {
      "policy_name": null,
      "policy_number": null,
      "transaction_date": { "type": "string", "value": "Oct-25" },
      "product_name": { "type": "string", "value": "Life" },
      "premium_applied": { "source": "100.00", "value": 100, "type": "number" },
      "commission_rate": { "source": "10.00", "value": 10, "type": "number" },
      "transaction_premium_amount": { "source": "$130.76", "value": 130.76, "unit": "$", "type": "currency" },
      "commission_amount": { "source": "$13.08", "value": 13.08, "unit": "$", "type": "currency" },
      "transaction_type": { "type": "string", "value": "Payment" },
      "total_for_policy": { "source": "$130.76", "value": 130.76, "unit": "$", "type": "currency" },
      "total_for_broker": { "source": "$29,477.70", "value": 29477.7, "unit": "$", "type": "currency" }
    },
    {
      "policy_name": null,
      "policy_number": null,
      "transaction_date": { "type": "string", "value": "Oct-25" },
      "product_name": { "type": "string", "value": "Dental" },
      "premium_applied": { "source": "100.00", "value": 100, "type": "number" },
      "commission_rate": { "source": "5.00", "value": 5, "type": "number" },
      "transaction_premium_amount": { "source": "$4,285.18", "value": 4285.18, "unit": "$", "type": "currency" },
      "commission_amount": { "source": "$214.26", "value": 214.26, "unit": "$", "type": "currency" },
      "transaction_type": { "type": "string", "value": "Payment" },
      "total_for_policy": { "source": "$4,417.92", "value": 4417.92, "unit": "$", "type": "currency" },
      "total_for_broker": { "source": "$29,477.70", "value": 29477.7, "unit": "$", "type": "currency" }
    },
    {
      "policy_name": null,
      "policy_number": null,
      "transaction_date": { "type": "string", "value": "Oct-25" },
      "product_name": { "type": "string", "value": "Life" },
      "premium_applied": { "source": "100.00", "value": 100, "type": "number" },
      "commission_rate": { "source": "6.00", "value": 6, "type": "number" },
      "transaction_premium_amount": { "source": "$1,897.92", "value": 1897.92, "unit": "$", "type": "currency" },
      "commission_amount": { "source": "$113.87", "value": 113.87, "unit": "$", "type": "currency" },
      "transaction_type": { "type": "string", "value": "Payment" },
      "total_for_policy": { "source": "$6,886.63", "value": 6886.63, "unit": "$", "type": "currency" },
      "total_for_broker": { "source": "$29,477.70", "value": 29477.7, "unit": "$", "type": "currency" }
    }
  ]
}

policy_name and policy_number are redacted in the sample document.

The preceding queries use three levels of nested Sections to model the document's hierarchy: broker → policy → transaction.

Broker

The outermost _policies_raw section groups all content under a broker, starting at the column header row that contains "Policy Holder Name" and ending at "Total for Broker."

Policy

Inside each broker section, _policies further subdivides by policyholder: since every transaction row contains "Base" in the Commission Type column, what actually delimits each policy is the Require Stop parameter + "Total for Policy" stop, not unique anchor text.

Transactions

The innermost transactions sections treat each individual transaction row as its own mini-document, anchoring on any line containing a decimal currency amount — and using the Required Fields parameter to filter out non-transaction rows that happen to match that pattern.

Putting it Together

Because the column header row appears once per broker section rather than repeating inside each policy or transaction, all three section levels use the External Range parameter to expose it; this makes the Intersection method work for the transaction-level extracted fields, each of which resolves its value by finding where a transaction row crosses a named column header. For fields whose column header appears twice (both commission columns are labeled "Comm"), the X Range Filter parameter constrains the match to the correct x-coordinate range.

Notably, policy_name reads from the "Policy Holder Name" column at each transaction row's position — it's not a section-level label being propagated down, but a value read directly from the table for each row.

Flattening the nested sections

The Copy to Section method stamps each policy's subtotal onto its transaction rows, the Custom Computation method with ../../_total_for_broker climbs two section levels to attach the broker total, and the Copy from Sections method flattens all transaction arrays up to the broker level for easier downstream consumption.  The billing_detail and _cleanup fields flatten out and clean up the data even more.

Extract the product name

Let’s take a closer look at one of the preceding queries. Each commission row carries a product name (Dental, Vision, Life, Accident, and others) in the "Benefit" column. The Intersection method extracts it by pairing the decimal row anchor with a vertical anchor targeting the "Benefit" column header.

Here is the query we'll use:


{
    "id": "product_name",
    "type": "string",
    "anchor": {
      "match": {
        "type": "regex",
        "pattern": "\\.\\d{2}" /* defines the horizontal axis of the intersection (the row) — anchors Sensible on each
  transaction row */
      }
    },
    "method": {
      "id": "intersection",
      "verticalAnchor": { /* defines the vertical axis (the column); use horizontalAnchor instead to swap which anchor
  defines which axis */
        "match": {
          "text": "Benefit",
          "type": "equals" /* extract the value in the "Benefit" column (e.g. Dental, Vision, Critical Illness) */
        }
      },
      "offsetX": 0, /* default: 0. offset the vertical line left (negative) or right (positive) in inches */
      "offsetY": 0, /* default: 0. offset the horizontal line up (negative) or down (positive) in inches */
      "width": 0.5, /* default: 0 (creates a point intersection — Sensible extracts any line overlapping the point); here,
  0.5" wide region to constrain the column match */
      "height": 0, /* default: 0; set alongside width to define a 2D region rather than a horizontal band */
      "percentOverlapX": 0, /* default: 0.9 — fraction of width overlap required for a line to be inside the region; 0
  accepts any overlap */
      "percentOverlapY": 0  /* default: 0.8 — same as percentOverlapX but for height */
    }
  }

The preceding query ensures each transaction gets labeled with a product name, for example  Accident or  Critical Illness. For an example of extracted product names in output, see the previous billing_details query’s output.

percentOverlapX: 0 constrains extraction to the column's horizontal bounds, preventing spillover into adjacent columns. The same Intersection pattern, with different vertical anchors, extracts commission_rate, premium_applied, transaction_premium_amount, and transaction_type — each targeting its specific column header by label text.

Extract more data

Sensible can extract any field present on a commission statement. The examples above cover ten fields. A complete config can also pull broker name, producer code, statement date, chargeback amounts, net commission due by policy, and other data. Sensible's open-source configuration library includes a prebuilt commission statement config to use as a starting point. To build custom fields or add carrier variants, the SenseML reference covers every available extraction method. To have Sensible's team handle configuration, testing, and ongoing maintenance, managed services gets you fully set up.

Connect Sensible to your workflow

Once your SenseML config is set up, there are several ways to integrate commission statement extraction into your application or process.

Python SDK

The Sensible Python SDK wraps the extraction API for Python applications. Install with pip and pass a file path or URL to get back the full API response:


  pip install sensibleapi


import os
import json
from sensibleapi import SensibleSDK

sensible = SensibleSDK(os.environ["SENSIBLE_API_KEY"])

request = sensible.extract(
    path="./commission_statement.pdf",  # replace with path to your document
    document_type="commission_statements",
    environment="production"
)

results = sensible.wait_for(request)

try:
    print(json.dumps(results, indent=2))
except Exception:
    print(results)

Save the script as extract_commission_statement.py. Run it from the command line:


python extract_commission_statement.py

After running the script, you should see the following output:

Sample response for commission statement:


{
  "id": "abc123def456",
  "created": "2026-05-01T00:00:00.000Z",
  "status": "SUCCEEDED",
  "type": "commission_statements",
  "configuration": "sun_life",
  "configuration_version": 1,
  "parsed_document": {
    "commission_total": { "source": "$2,547.43", "value": 2547.43, "unit": "$", "type": "currency" },
    "billing_detail": [
      {
        "policy_name": null,
        "policy_number": null,
        "transaction_date": { "type": "string", "value": "Oct-25" },
        "product_name": { "type": "string", "value": "Life" },
        "premium_applied": { "source": "100.00", "value": 100, "type": "number" },
        "commission_rate": { "source": "10.00", "value": 10, "type": "number" },
        "transaction_premium_amount": { "source": "$130.76", "value": 130.76, "unit": "$", "type": "currency" },
        "commission_amount": { "source": "$13.08", "value": 13.08, "unit": "$", "type": "currency" },
        "transaction_type": { "type": "string", "value": "Payment" },
        "total_for_policy": { "source": "$130.76", "value": 130.76, "unit": "$", "type": "currency" },
        "total_for_broker": { "source": "$29,477.70", "value": 29477.7, "unit": "$", "type": "currency" }
      }
    ]
    [...] // response abridged
  }
}

policy_name and policy_number are redacted from the sample output. The full response includes all 27 commission rows.

For async processing at volume, configure a webhook instead of polling with wait_for. See the Python SDK docs for the full reference.

Other integrations

Sensible's MCP server connects extraction to AI coding tools like Claude for natural-language querying without writing API calls. The REST API supports both synchronous and asynchronous (webhook) patterns for language-agnostic integration. For no-code workflows, the Zapier connector routes extracted data into Google Sheets, Airtable, Slack, or any connected app.

Frequently asked questions

What fields can be extracted from a commission statement?

Sensible extracts commission total, policy name, policy number, transaction date, product type, premium applied, commission rate, transaction premium amount, commission amount, transaction type, policy subtotal, and broker total per line item. A complete config also pulls broker name, producer code, statement date, chargeback amounts, and net commission due by policy.

How accurate is automated commission statement extraction?

For carrier-specific layout configs, accuracy ties directly to the consistency of the carrier's format. Sun Life's fixed column positions and consistent label text allow deterministic Intersection extraction that returns the same result on every statement; each field maps to a known column position, so a mismatch is immediately traceable.

Can Sensible handle commission statements from multiple carriers?

Sensible builds a separate layout-specific config for each carrier. Each config targets that carrier's column positions and label conventions, and Sensible routes each statement to the correct config automatically via document fingerprinting. All configs run through the same API endpoint.

What format does extracted commission data come out in?

Sensible returns JSON with typed values: currency amounts as numbers with a unit field, plain numeric fields (commission rate, premium share) as numbers, and line items as arrays of row objects. The schema is consistent across every statement run through the same config.

Start extracting

The Sun Life config shown above extends to additional carriers in the same commission_statements document type — each carrier gets its own named config, and Sensible routes statements to the correct one automatically.

Commission statements are one document type in the insurance distribution workflow. Sensible also handles ACORD forms, declaration pages, and loss runs through the same API, so carrier document processing runs through a single extraction pipeline regardless of document type.

Sign up for a free 2-week trial to run the config against your own Sun Life statements. Talk to our team to scope carrier coverage across your full statement mix.

Jason Auh
Jason Auh
Turn documents into structured data
Get started free
Share this post

Turn documents into structured data

Stop relying on manual data entry. With Sensible, claim back valuable time, your ops team will thank you, and you can deliver a superior user experience. It’s a win-win.