Skip to content

Extracting Terms From a Vendor Master Agreement

9 min read · updated August 11, 2026

A master services agreement is not a contract you can flatten into a row. It is half a contract. The other half lives in every statement of work executed under it, and a clause near the front tells you which half wins when they disagree. Extract the MSA on its own and you get a record that is confidently wrong for every engagement it governs.

What makes a master agreement hard

Every other document in a procurement pile answers questions about itself. An invoice states its own total. A purchase order names its own vendor. A master agreement instead states defaults that some other document is expected to override, and it usually says so explicitly: fees are “as set out in the applicable Statement of Work”, the term is “as set out herein unless a Statement of Work specifies otherwise”, the services are “those described in each Statement of Work”.

So the answer to “what does this contract pay?” is not in the document you were handed. A model asked to extract contract_value from an MSA will either return null, which is correct and useless, or find a number somewhere in an exhibit and return that, which is worse. The unit of extraction is not the document. It is the pair.

The second difficulty is that the MSA is the durable half. SOWs are signed and expire on a cadence of months; the MSA is signed once and survives a decade. A contract repository that stores merged records loses the ability to answer the question people actually ask during a renegotiation, which is “which of our terms are in the master and therefore apply to everything, and which did somebody negotiate into one SOW?”

The order-of-precedence clause is the schema

Almost every MSA contains a conflicts clause, and the two common drafting patterns produce opposite extraction behaviour. Pattern one:

In the event of a conflict between this Agreement and any
Statement of Work, the terms of this Agreement shall control.

Under that wording, an SOW term that contradicts the MSA is inoperative, and a merged record that took the SOW value has recorded something that is not the deal. Pattern two:

In the event of a conflict between this Agreement and any
Statement of Work, this Agreement shall control, except where the
Statement of Work expressly identifies the Section of this Agreement
it is intended to supersede.

That second form is the one that matters, because it makes the precedence decision depend on the text of the other document. Whether the SOW’s payment-terms clause wins depends on whether it contains a phrase like “notwithstanding Section 6.2 of the Agreement”. You cannot answer that from the MSA alone, and you cannot answer it from the SOW alone either.

The practical consequence is that the precedence clause should be extracted first, as a field in its own right, and it should be extracted as a classification rather than as prose: msa_controls, sow_controls, msa_controls_unless_expressly_superseded, or none_found. That value decides how the rest of the pipeline resolves conflicts, so it belongs at the top of the record, not buried as clause text somebody reads later.

Store terms as a list of observations rather than a single flat object. Each observation carries the value, the document it came from, the clause number and the page — the per-field audit trail that makes a later disagreement resolvable. The effective term is then a function computed over the observations and the precedence rule, not something the extractor decided:

{
  "agreement_id": "MSA-2024-0117",
  "precedence": "msa_controls_unless_expressly_superseded",
  "terms": [
    { "field": "payment_days", "value": 45,
      "source": { "doc": "MSA-2024-0117", "clause": "6.2", "page": 7 },
      "expressly_supersedes": null },
    { "field": "payment_days", "value": 30,
      "source": { "doc": "SOW-2025-0042", "clause": "5", "page": 2 },
      "expressly_supersedes": "6.2" }
  ]
}

Now the record survives a disagreement. Both values are present, both are attributable, and the resolution is reproducible: the SOW value wins here because it names the section. A merged record would have stored 30 or 45 with no way to explain which, and a lawyer reading it would have had to open both PDFs anyway — at which point the extraction bought nothing.

The terms that are actually asked for

Contract-review teams ask a fairly stable set of questions of a master agreement, and it is worth naming them rather than extracting whatever headings the document happens to have:

  • Effective date, and it is not the signature date. Many MSAs say “effective as of” a date that precedes the last signature, sometimes by months. Both dates exist in the document and they are different fields. Term arithmetic runs off the effective date; audit questions run off the signature date.
  • Initial term and renewal mechanics. Length, whether renewal is automatic, and the notice window. The window is date arithmetic counted backwards and has its own page.
  • Termination rights, split three ways. For convenience (with a notice period), for cause (usually with a cure period, commonly 30 days), and immediate on insolvency. Three different fields with three different notice values; collapsing them into termination_notice_days destroys the distinction that matters.
  • Limitation of liability, and its carve-outs. The cap plus the list of things excluded from it — typically breach of confidentiality, indemnity obligations, gross negligence and wilful misconduct. The carve-out list is the operative half.
  • Governing law and venue. Two fields, not one. They frequently disagree, and an arbitration clause makes venue mean something different again.
  • Assignment and change of control. Whether consent is required, and whether an intra-group reorganisation is carved out.

Liability caps are formulas, not amounts

The single most common modelling error in this document type is extracting a liability cap as a currency amount. Caps are usually written as expressions:

...shall not exceed the greater of (a) the total fees paid or
payable by Customer under the applicable Statement of Work during
the twelve (12) months preceding the event giving rise to the
claim, or (b) US$250,000.

There is no number to extract. There is a formula whose value depends on a date, on which SOW the claim arises under, and on payment history held in a different system. Storing 250000 is wrong in every engagement whose trailing-twelve-month fees exceed that, which for a large vendor is most of them. The right shape is a small structured expression — a basis (fees_paid, fees_payable, fixed), a lookback in months, a scope (per_sow, per_agreement), a floor, and an aggregator (greater_of, lesser_of) — which a downstream system can evaluate when it has the numbers.

The same treatment applies to multipliers: “the fees paid in the twelve months preceding” and “two times the fees paid in the twelve months preceding” differ by one word that a summarising extraction routinely drops. This is a good argument for extracting first and reasoning afterwards: pull the clause text verbatim, then classify it in a second pass, so that the multiplier is never at the mercy of a paraphrase.

Where a flat extraction goes wrong

Two caps, both real. A general cap in the MSA and a higher super-cap in one SOW for a high-risk workstream. A schema with a scalar liability_cap field forces the extractor to pick, and whichever it picks it has silently discarded a negotiated term.

Defined terms that shift meaning. “Fees” is usually a defined term that expressly excludes pass-through expenses and taxes. A cap expressed in Fees is therefore smaller than a cap expressed in amounts invoiced, and the difference lives in a definitions section forty pages away from the cap.

Incorporation by reference. MSAs routinely pull in documents that are not attached: a security exhibit at a URL, a DPA, an acceptable-use policy the vendor may amend unilaterally. Extraction should record the reference as a field with its own resolution status rather than pretending the document is complete.

Amendments. The signed PDF you were handed may be superseded by a two-page amendment that changes one section number. The record needs an amendment chain, and the effective value of a term is the latest amendment that touched it. This is why per-field provenance is worth the extra schema complexity: an amendment adds observations rather than requiring a re-extraction of everything.

Signature pages that prove less than they look like they do. A counter-signature block can be blank, dated later than the effective date, or signed by someone whose authority is the actual question. Whether a document is executed is a separate determination from what it says; see extracting signatories from a signed PDF.

The through-line is that a master agreement is a document about other documents. Any schema that cannot represent “this term, from that document, subject to this rule” will produce records that look complete and cannot be defended. Start from the precedence clause, store observations rather than values, and let the effective term be computed.