> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyformat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Contract Analysis

> Identify clauses and key terms with webhook-driven processing

Uses **webhooks** instead of polling — the recommended pattern for production integrations where contracts are processed asynchronously.

## Workflow fields

<Tip>
  We recommend creating this workflow in the [anyformat platform](https://app.anyformat.ai) where you can test with sample contracts and iterate on field descriptions. Copy the workflow ID to use with the API.
</Tip>

| Field                     | Type          | Description                               |
| ------------------------- | ------------- | ----------------------------------------- |
| `contract_type`           | enum          | Type of agreement                         |
| `effective_date`          | date          | When the contract takes effect            |
| `expiration_date`         | date          | When the contract expires                 |
| `auto_renewal`            | boolean       | Whether the contract renews automatically |
| `governing_law`           | string        | Jurisdiction governing the contract       |
| `termination_notice_days` | integer       | Required notice period for termination    |
| `key_clauses`             | multi\_select | Which standard clauses are present        |
| `liability_cap`           | float         | Maximum liability amount if specified     |

## Setup and submit

The workflow is created once. After that, each new contract triggers one webhook delivery when processing finishes.

<Tabs>
  <Tab title="curl" icon="terminal">
    ```bash theme={null}
    # 1. Create the workflow (once)
    curl -X POST 'https://api.anyformat.ai/v2/workflows/' \
      -H 'Content-Type: application/json' \
      -H "Authorization: Bearer $ANYFORMAT_API_KEY" \
      -d '{
        "name": "Contract Analyzer",
        "description": "Extract key terms and clauses from contracts",
        "nodes": [
          {"id": "parse_1", "type": "parse"},
          {
            "id": "extract_1",
            "type": "extract",
            "extraction_schema": {
              "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",     "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"}
              ]
            }
          }
        ],
        "edges": [{"source": "parse_1", "target": "extract_1"}]
      }'

    # 2. Register a webhook (once) — store the returned secret
    curl -X POST 'https://api.anyformat.ai/v2/webhooks/' \
      -H 'Content-Type: application/json' \
      -H "Authorization: Bearer $ANYFORMAT_API_KEY" \
      -d '{
        "url": "https://your-server.com/webhooks/anyformat",
        "events": ["extraction.completed", "extraction.failed"]
      }'
    # → { "id": "...", "secret": "..." }   ← STORE THIS, it is only returned once

    # 3. Submit a contract (no polling — the webhook will fire when done)
    curl -X POST 'https://api.anyformat.ai/v2/workflows/WORKFLOW_ID/run/' \
      -H "Authorization: Bearer $ANYFORMAT_API_KEY" \
      -F 'file=@contract.pdf'
    ```
  </Tab>

  <Tab title="TypeScript" icon="js" iconType="brands">
    ```typescript theme={null}
    import { Anyformat, Schema } from "@anyformat/sdk";

    const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });

    // 1. Build the workflow — the typed graph is assembled by the fluent builder.
    const builder = af
      .workflow("Contract Analyzer", "Extract key terms and clauses from contracts")
      .parse()
      .extract([
        Schema.enum("contract_type", "The type of legal agreement", [
          Schema.option("nda",               "Non-disclosure agreement"),
          Schema.option("service_agreement", "Service or consulting agreement"),
          Schema.option("employment",        "Employment contract"),
          Schema.option("lease",             "Lease or rental agreement"),
          Schema.option("partnership",       "Partnership agreement"),
          Schema.option("licensing",         "Licensing agreement"),
        ]),
        Schema.date("effective_date",            "Date the contract takes effect"),
        Schema.date("expiration_date",           "Date the contract expires or terminates"),
        Schema.boolean("auto_renewal",           "Whether the contract automatically renews at expiration"),
        Schema.string("governing_law",           "State or jurisdiction whose laws govern this contract"),
        Schema.integer("termination_notice_days","Number of days advance notice required to terminate"),
        Schema.multiSelect("key_clauses", "Standard contract clauses present in this agreement", [
          Schema.option("confidentiality",         "Confidentiality or NDA clause"),
          Schema.option("non_compete",             "Non-compete restriction"),
          Schema.option("indemnification",         "Indemnification or hold-harmless clause"),
          Schema.option("limitation_of_liability", "Cap on damages or liability"),
          Schema.option("force_majeure",           "Force majeure or act-of-God clause"),
          Schema.option("arbitration",             "Mandatory arbitration clause"),
          Schema.option("intellectual_property",   "IP ownership or assignment clause"),
          Schema.option("non_solicitation",        "Non-solicitation of employees or clients"),
        ]),
        Schema.float("liability_cap", "Maximum liability amount in dollars, if a cap is specified"),
      ]);

    // 2. Create the workflow + a webhook (one-time setup). Webhook endpoints
    //    aren't on the SDK surface yet — call them via fetch and store the secret.
    const workflow = await builder.create();

    const webhookResponse = await fetch("https://api.anyformat.ai/v2/webhooks/", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.ANYFORMAT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        url: "https://your-server.com/webhooks/anyformat",
        events: ["extraction.completed", "extraction.failed"],
      }),
    });
    const webhook = await webhookResponse.json();
    // STORE webhook.secret securely — it is only returned once.

    // 3. Submit each contract against the existing workflow handle. Skip .wait()
    //    — the webhook delivers the result.
    const file: File = /* a File with .name set, e.g. new File([bytes], "contract.pdf") */;
    const run = await workflow.run(file);
    console.log(`Submitted ${run.id}; waiting for webhook…`);
    ```
  </Tab>

  <Tab title="Python" icon="python" iconType="brands">
    ```python theme={null}
    import os
    import httpx
    from anyformat.sdk import Client
    from anyformat.workflow import Schema

    api_key = os.environ["ANYFORMAT_API_KEY"]
    client = Client(api_key=api_key)

    # 1. Create the workflow (once)
    workflow = (
        client.workflow("Contract Analyzer")
        .parse()
        .extract([
            Schema.enum("contract_type", "The type of legal agreement", options=[
                Schema.option("nda",               "Non-disclosure agreement"),
                Schema.option("service_agreement", "Service or consulting agreement"),
                Schema.option("employment",        "Employment contract"),
                Schema.option("lease",             "Lease or rental agreement"),
                Schema.option("partnership",       "Partnership agreement"),
                Schema.option("licensing",         "Licensing agreement"),
            ]),
            Schema.date("effective_date",            "Date the contract takes effect"),
            Schema.date("expiration_date",           "Date the contract expires or terminates"),
            Schema.boolean("auto_renewal",           "Whether the contract automatically renews at expiration"),
            Schema.string("governing_law",           "State or jurisdiction whose laws govern this contract"),
            Schema.integer("termination_notice_days","Number of days advance notice required to terminate"),
            Schema.multi_select("key_clauses", "Standard contract clauses present in this agreement", options=[
                Schema.option("confidentiality",         "Confidentiality or NDA clause"),
                Schema.option("non_compete",             "Non-compete restriction"),
                Schema.option("indemnification",         "Indemnification or hold-harmless clause"),
                Schema.option("limitation_of_liability", "Cap on damages or liability"),
                Schema.option("force_majeure",           "Force majeure or act-of-God clause"),
                Schema.option("arbitration",             "Mandatory arbitration clause"),
                Schema.option("intellectual_property",   "IP ownership or assignment clause"),
                Schema.option("non_solicitation",        "Non-solicitation of employees or clients"),
            ]),
            Schema.float("liability_cap", "Maximum liability amount in dollars, if a cap is specified"),
        ])
        .create()
    )

    # 2. Register a webhook (once). Webhook endpoints aren't on the SDK surface
    #    yet — call them via httpx and store the returned secret.
    webhook = httpx.post(
        "https://api.anyformat.ai/v2/webhooks/",
        headers={"Authorization": f"Bearer {api_key}"},
        json={
            "url": "https://your-server.com/webhooks/anyformat",
            "events": ["extraction.completed", "extraction.failed"],
        },
    ).json()
    # STORE webhook["secret"] securely — it is only returned once.

    # 3. Submit each contract. Skip .wait() — the webhook will fire when done.
    workflow.run("contract.pdf")
    ```
  </Tab>
</Tabs>

## Handle the webhook callback

When processing completes, your server receives a POST with the `collection_id` and `workflow_id`. Verify the HMAC signature, then fetch the results from `GET /v2/workflows/{workflow_id}/files/{collection_id}/results/`. See [Webhooks overview](/api-reference/webhooks/overview#payload-structure) for the full payload schema and the verification recipe.

A minimal Python (Flask) handler:

```python theme={null}
import hmac, hashlib, os, httpx
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["ANYFORMAT_WEBHOOK_SECRET"]
API_KEY = os.environ["ANYFORMAT_API_KEY"]

@app.route("/webhooks/anyformat", methods=["POST"])
def handle_webhook():
    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":
        data = event["data"]
        results = httpx.get(
            f"https://api.anyformat.ai/v2/workflows/{data['workflow_id']}"
            f"/files/{data['collection_id']}/results/",
            headers={"Authorization": f"Bearer {API_KEY}"},
        ).json()
        extraction = results["extractions"][0]["fields"]
        if "indemnification" not in extraction["key_clauses"]["value"]:
            print("WARNING: no indemnification clause")
    return jsonify({"status": "ok"}), 200
```

The TypeScript story is analogous: receive the POST in your server framework of choice (Express, Hono, Next route handler…), verify the signature with `crypto.createHmac("sha256", secret)`, then fetch the results via `fetch()` or the SDK's low-level client.

## Tips

<Warning>
  Webhook secrets are only returned at creation. Store the secret immediately. If lost, delete the webhook and create a new one.
</Warning>

* Webhooks eliminate polling overhead and rate-limit consumption.
* `integer` for notice periods lets you do calendar math directly.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bolt" href="/api-reference/webhooks/overview">
    Set up, sign, and verify webhook deliveries
  </Card>

  <Card title="Field types" icon="diagram-project" href="/concepts/field-types">
    `multi_select`, `enum`, and other field shapes
  </Card>
</CardGroup>
