Skip to main content

Contract Analysis

Extract key terms, dates, and clause presence from contracts and legal agreements. This recipe uses webhooks instead of polling, which is the recommended approach for production integrations where you process contracts asynchronously.

Workflow Fields

We recommend creating this workflow in the anyformat platform where you can test with sample contracts and iterate on field descriptions. Copy the workflow ID to use with the API.
FieldTypeDescription
contract_typeenumType of agreement
effective_datedateWhen the contract takes effect
expiration_datedateWhen the contract expires
auto_renewalbooleanWhether the contract renews automatically
governing_lawstringJurisdiction governing the contract
termination_notice_daysintegerRequired notice period for termination
key_clausesmulti_selectWhich standard clauses are present
liability_capfloatMaximum liability amount if specified

Field Configuration

{
  "fields": [
    {
      "name": "contract_type",
      "description": "The type of legal agreement",
      "data_type": "enum",
      "enum_options": [
        {"name": "nda", "description": "Non-disclosure agreement"},
        {"name": "service_agreement", "description": "Service or consulting agreement"},
        {"name": "employment", "description": "Employment contract"},
        {"name": "lease", "description": "Lease or rental agreement"},
        {"name": "partnership", "description": "Partnership agreement"},
        {"name": "licensing", "description": "Licensing agreement"}
      ]
    },
    {"name": "effective_date", "description": "Date the contract takes effect", "data_type": "date"},
    {"name": "expiration_date", "description": "Date the contract expires or terminates", "data_type": "date"},
    {"name": "auto_renewal", "description": "Whether the contract automatically renews at expiration", "data_type": "boolean"},
    {"name": "governing_law", "description": "State or jurisdiction whose laws govern this contract", "data_type": "string"},
    {"name": "termination_notice_days", "description": "Number of days advance notice required to terminate the contract", "data_type": "integer"},
    {
      "name": "key_clauses",
      "description": "Standard contract clauses that are present in this agreement",
      "data_type": "multi_select",
      "enum_options": [
        {"name": "confidentiality", "description": "Confidentiality or NDA clause"},
        {"name": "non_compete", "description": "Non-compete restriction"},
        {"name": "indemnification", "description": "Indemnification or hold-harmless clause"},
        {"name": "limitation_of_liability", "description": "Cap on damages or liability"},
        {"name": "force_majeure", "description": "Force majeure or act-of-God clause"},
        {"name": "arbitration", "description": "Mandatory arbitration clause"},
        {"name": "intellectual_property", "description": "IP ownership or assignment clause"},
        {"name": "non_solicitation", "description": "Non-solicitation of employees or clients"}
      ]
    },
    {"name": "liability_cap", "description": "Maximum liability amount in dollars, if a cap is specified", "data_type": "float"}
  ]
}

Set Up Webhooks

Instead of polling, register a webhook to receive a notification when processing finishes.
import requests

API_KEY = "YOUR_API_KEY"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

# Create a webhook (one-time setup)
response = requests.post(
    "https://api.anyformat.ai/v2/webhooks/",
    headers=headers,
    json={
        "url": "https://your-server.com/webhooks/anyformat",
        "events": ["extraction.completed", "extraction.failed"]
    }
)

webhook = response.json()
# Store this secret securely — it is only returned once
print(f"Webhook secret: {webhook['secret']}")

Process a Document

curl -X POST 'https://api.anyformat.ai/v2/workflows/YOUR_WORKFLOW_ID/run/' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -F 'file=@contract.pdf'

Handle Webhook Events

When processing completes, your server receives a POST request. The payload includes the extraction_id and workflow_id — use the extraction ID to fetch results. See the webhook payload documentation for the full schema.
# Example Flask webhook handler
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

WEBHOOK_SECRET = "your_webhook_secret_here"
API_KEY = "your_api_key_here"

@app.route("/webhooks/anyformat", methods=["POST"])
def handle_webhook():
    # Verify signature
    signature = request.headers.get("X-Webhook-Signature", "")
    expected = hmac.new(
        WEBHOOK_SECRET.encode(), request.data, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(f"sha256={expected}", signature):
        return jsonify({"error": "invalid signature"}), 401

    event = request.json

    if event["event"] == "extraction.completed":
        extraction_id = event["data"]["extraction_id"]

        # Fetch the results
        response = requests.get(
            f"https://api.anyformat.ai/v2/files/{extraction_id}/extraction/",
            headers={"Authorization": f"Bearer {API_KEY}"}
        )
        results = response.json()

        # Process contract data
        clauses = results["key_clauses"]["value"]
        auto_renews = results["auto_renewal"]["value"]
        expires = results["expiration_date"]["value"]

        print(f"Contract expires {expires}, auto-renewal: {auto_renews}")
        print(f"Clauses found: {', '.join(clauses)}")

        # Flag contracts missing key protections
        if "indemnification" not in clauses:
            print("WARNING: No indemnification clause found")

    elif event["event"] == "extraction.failed":
        extraction_id = event["data"]["extraction_id"]
        print(f"Processing failed: {extraction_id}")

    return jsonify({"status": "ok"}), 200

Example Response

{
  "contract_type": {"value": "service_agreement", "confidence": 94},
  "effective_date": {"value": "2024-01-01", "confidence": 96},
  "expiration_date": {"value": "2024-12-31", "confidence": 95},
  "auto_renewal": {"value": true, "confidence": 91},
  "governing_law": {"value": "State of Delaware", "confidence": 93},
  "termination_notice_days": {"value": 30, "confidence": 88},
  "key_clauses": {
    "value": ["confidentiality", "indemnification", "limitation_of_liability", "force_majeure", "arbitration"],
    "confidence": 86
  },
  "liability_cap": {"value": 500000.00, "confidence": 84}
}

Tips

multi_select returns an array of all matching options. It is ideal for detecting which clauses are present, since a contract can contain many clauses simultaneously.
Webhook secrets are only returned when the webhook is created. Store the secret immediately. If lost, delete the webhook and create a new one.
  • Webhooks eliminate polling overhead and rate limit consumption. One webhook handles all processing.
  • boolean fields work well for yes/no contract features (auto-renewal, exclusivity, right of first refusal).
  • integer for notice periods gives you a number you can use in calendar calculations directly.
  • For multi-page contracts, processing handles all pages automatically.

Next Steps

Webhooks

Set up and manage webhook subscriptions

Field Types

Learn about multi_select, enum, and other field types