---
title: Assess a working tree's impact
keywords:
  - forge
  - source control
  - git
  - impact
  - review
---

# Assess a working tree's impact

Groups the changed files of one checkout into product areas and writes one sentence per area saying what changed there. Use it to summarise a working tree before review or commit. It acts for a person: it requires a bearer token whose user holds the `use_forge` capability in the acting organization, and an organization API key, which carries no user, is refused.

Every sent path belongs to exactly one area, so the areas' `files` always sum to the number of rows sent. Files the assessment could not place in a product area are returned together in a final area with id `ungrouped`; it is never dropped. All counts are computed from the rows you send, never estimated.

Identical inputs — the same branch, head and rows — return the same `fingerprint` and are answered from cache for seven days without generating a new assessment. The cache is per person: two people sending the same tree each get their own assessment. The `name` and `why` text is written in English.

**Which organization pays.** A new assessment is billed to the acting organization. When the checkout belongs to a Forge project group, send the group's Forge id as `project_group_id`: if the group bills to an organization (its own or its nearest registered ancestor's, see [List project groups](../forge-compute/list-project-groups.md)), the assessment is billed there instead, whatever the acting organization is, and shown under the group on that organization's [usage report](../analytics---usage-detail/get-usage-detail-report.md). You must still be able to use Forge there: otherwise it is refused with `403 FORGE_PROJECT_GROUP_ORGANIZATION_UNAVAILABLE` and never billed somewhere else. A group that bills to no organization is billed to the acting organization as before and shown under the group; a group that is not registered (or was deleted) is ignored. The cache is kept per billed organization too, and an identical request already being generated is joined rather than repeated, so its spend stays under the group the first request named.

{% api-container %}
{% api-content %}

{% endpoint method="POST" path="https://api.aitronos.com/v1/forge/git/impact" /%}

#### Body Parameters

**`worktree_path`** string <em style="color: #ef4444 !important; font-weight: 600 !important; font-style: normal !important;">required</em>

Absolute path of the checkout on the desktop, at most 4,096 characters. It identifies the tree to the caller; it is not used to generate the assessment and is not part of the fingerprint.

**`branch`** string <em style="color: #9ca3af !important; font-weight: 500 !important; font-style: normal !important;">optional</em>

Checked-out branch, at most 1,024 characters, or `null` on a detached head. An empty or blank string is refused.

**`head`** string <em style="color: #9ca3af !important; font-weight: 500 !important; font-style: normal !important;">optional</em>

Commit id of `HEAD` as 40 (SHA-1) or 64 (SHA-256) hex characters, or `null` on a branch with no commits yet. Case is ignored.

**`files`** array <em style="color: #ef4444 !important; font-weight: 600 !important; font-style: normal !important;">required</em>

Every changed file to assess, at most 5,000 (and the whole request body at most 10 MB). An empty list returns `areas: []` without generating anything. Each row:

- **`path`** string, required — path relative to the repository root with forward slashes, as git prints it; at most 4,096 characters. Each path may appear once. An absolute path (leading `/`) and a path with an empty, `.` or `..` segment (`./a`, `a//b`, `a/../b`) are refused; a single trailing `/`, as a host lists an untracked folder, is allowed.
- **`added`** integer or `null`, optional — lines added, from 0 to 1,000,000,000. `null` means the count was not read (binary file, over a size cap, read failed) — unknown, never 0.
- **`removed`** integer or `null`, optional — lines removed, `null` when unknown.
- **`untracked`** boolean, optional (default `false`) — `true` for a file git does not track yet.

Types are strict: a count sent as a float, a string or a boolean, or a flag sent as anything but `true`/`false`, is refused rather than converted.

**`project_group_id`** string <em style="color: #9ca3af !important; font-weight: 500 !important; font-style: normal !important;">optional</em>

Forge's own id for the project group the checkout belongs to, at most 128 characters (an empty or blank value is the same as leaving it out), as registered with [Register or update a project group](../forge-compute/put-project-group.md). The assessment is then billed to the organization that group bills to (`403 FORGE_PROJECT_GROUP_ORGANIZATION_UNAVAILABLE` when you can no longer use Forge there) and shown under the group on its usage report. It is not part of the fingerprint. A group that is not registered is ignored.

---

## Returns

**`fingerprint`** string — stable SHA-256 hex of the branch, the head and every row (order-independent). The same inputs always produce the same fingerprint.

**`generated_at`** string — when this assessment was generated (UTC, ISO 8601). A cached answer keeps its original time.

**`areas`** array — the product areas, largest first; `ungrouped`, when present, is always last. Empty only when `files` was empty — a failure is always an error response, never an empty list. Each area:

- **`id`** string — kebab-case id derived from the name, unique within the response. `ungrouped` is reserved for files the assessment did not place.
- **`name`** string — short area name, e.g. `Billing`.
- **`why`** string — one sentence saying what changed for this area.
- **`files`** integer — changed files in this area.
- **`folders`** array of `{path, files}` — the folders the area's files are counted under, most files first. The counted files sit in the folder or below it; `.` is the repository root. No file is counted under two folders.
- **`loudest`** array of `{path, added, removed, untracked}` — up to five of the area's biggest changes, echoed from the request rows: files with known line counts first, largest first, then files whose counts are unknown.

```json
{
  "fingerprint": "8d1f0c6b2a9e4f37b5c8d0e2a4f6b8c1d3e5f7a9b0c2d4e6f8a1b3c5d7e9f0a2",
  "generated_at": "2026-09-25T10:00:00Z",
  "areas": [
    {
      "id": "billing",
      "name": "Billing",
      "why": "Invoice totals and tax rounding changed, with new invoice tests.",
      "files": 3,
      "folders": [
        {"path": "app/billing", "files": 2},
        {"path": "app/billing/tests", "files": 1}
      ],
      "loudest": [
        {"path": "app/billing/tests/test_invoice.py", "added": 55, "removed": 0, "untracked": false},
        {"path": "app/billing/invoice.py", "added": 40, "removed": 2, "untracked": false},
        {"path": "app/billing/tax.py", "added": 3, "removed": 1, "untracked": false}
      ]
    }
  ]
}
```

## Errors

Every failure is an error response whose `message` is a sentence to show the person, and whose `code` is one of:

| Status | Code | When |
|--------|------|------|
| 401 | `AUTHENTICATION_REQUIRED` | No credential, or a credential with no user (an organization API key). |
| 403 | `INSUFFICIENT_PERMISSIONS` | The user does not hold `use_forge` in the acting organization. |
| 403 | `FORGE_PROJECT_GROUP_ORGANIZATION_UNAVAILABLE` | `project_group_id` names a group that bills to an organization where you can no longer use Forge (you left it, lost `use_forge` there, or the credential is not granted it). `message` names it ("This project group is billed to Client Workspace, where you can no longer use Forge. Choose another organization for the group."); `details` carries `client_id` and `organization_id`. Nothing is generated or billed elsewhere. |
| 413 | `FORGE_IMPACT_TOO_MANY_FILES` | More than 5,000 rows. `details.observed_count` and `details.limit_count` carry the figures. Checked before the rows' contents. |
| 413 | `REQUEST_TOO_LARGE` | The request body is over 10 MB. |
| 422 | `FORGE_IMPACT_INVALID_REQUEST` | A malformed tree: a repeated, empty, absolute, over-long or non-normalised path, a line count below 0 or above 1,000,000,000, a `head` that is not 40 or 64 hex characters, a blank `branch` or `worktree_path`, or text that cannot be encoded as UTF-8. `details.field` names the offending field (e.g. `files[3].path`) and `details.reason` says why; the path itself is never echoed. |
| 422 | `VALIDATION_ERROR` | A body of the wrong shape or type: `files` missing or not an array, a row without `path`, a count or flag of the wrong type, an unknown field, a `project_group_id` longer than 128 characters. Checked after authentication and before the `FORGE_IMPACT_*` checks. |
| 429 | `RATE_LIMIT_EXCEEDED` | The organization's usage safeguard is refusing new AI work for now. `retry_after` says when to retry. |
| 502 | `FORGE_IMPACT_GENERATION_FAILED` | The assessment could not be generated (the model was unavailable or busy), or came back unusable. Not cached — retrying tries again. |
| 503 | `FORGE_IMPACT_UNAVAILABLE` | The assessment cache could not be read. Refused rather than generating an uncached assessment; `retry_after` says when to retry. |
| 504 | `FORGE_IMPACT_TIMED_OUT` | Still generating after 60 seconds. The work keeps running and is cached when it completes, so retrying the same tree is usually immediate. |


{% /api-content %}
{% api-examples %}
{% code-group mode="dropdown" %}
```bash {% title="cURL" %}
curl -X POST "https://api.aitronos.com/v1/forge/git/impact" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "worktree_path": "/Users/dev/shop",
        "branch": "feature/invoice-tax",
        "head": "3f9c2a7e1b4d8f6a0c5e9b2d7f1a3c8e6b4d0f2a",
        "files": [
          {"path": "app/billing/invoice.py", "added": 40, "removed": 2, "untracked": false},
          {"path": "app/billing/tax.py", "added": 3, "removed": 1, "untracked": false},
          {"path": "app/billing/tests/test_invoice.py", "added": 55, "removed": 0, "untracked": false},
          {"path": "docs/guide/billing.md", "added": null, "removed": null, "untracked": true},
          {"path": "README.md", "added": 1, "removed": 1, "untracked": false}
        ],
        "project_group_id": "grp-acme-shop"
      }'
```

```python {% title="Python SDK" %}
import os

from aitronos import Aitronos

# The route acts for a person: authenticate with the user's access token.
# The server answers or times out at 60 s, so wait a little longer than that.
client = Aitronos(
    api_key="your-api-key",
    token=os.environ["AITRONOS_TOKEN"],
    timeout=90,
)
assessment = client.forge.assess_git_impact(
    worktree_path="/Users/dev/shop",
    branch="feature/invoice-tax",
    head="3f9c2a7e1b4d8f6a0c5e9b2d7f1a3c8e6b4d0f2a",
    files=[
        {"path": "app/billing/invoice.py", "added": 40, "removed": 2, "untracked": False},
        {"path": "docs/guide/billing.md", "added": None, "removed": None, "untracked": True},
    ],
)
for area in assessment.areas:
    print(area.name, area.files, area.why)
```

```python {% title="Python" %}
import os
import requests

token = os.environ["AITRONOS_TOKEN"]
response = requests.post(
    "https://api.aitronos.com/v1/forge/git/impact",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "worktree_path": "/Users/dev/shop",
        "branch": "feature/invoice-tax",
        "head": "3f9c2a7e1b4d8f6a0c5e9b2d7f1a3c8e6b4d0f2a",
        "files": [
            {"path": "app/billing/invoice.py", "added": 40, "removed": 2, "untracked": False},
            {"path": "docs/guide/billing.md", "added": None, "removed": None, "untracked": True},
        ],
        # Optional: bill the assessment to the project group's organization.
        "project_group_id": "grp-acme-shop",
    },
    timeout=90,
)
if response.ok:
    for area in response.json()["areas"]:
        print(area["name"], area["files"], area["why"])
else:
    print(response.json()["error"]["message"])
```

```javascript {% title="JavaScript" %}
const response = await fetch("https://api.aitronos.com/v1/forge/git/impact", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AITRONOS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    worktree_path: "/Users/dev/shop",
    branch: "feature/invoice-tax",
    head: "3f9c2a7e1b4d8f6a0c5e9b2d7f1a3c8e6b4d0f2a",
    files: [
      { path: "app/billing/invoice.py", added: 40, removed: 2, untracked: false },
      { path: "docs/guide/billing.md", added: null, removed: null, untracked: true },
    ],
    project_group_id: "grp-acme-shop",
  }),
});
const body = await response.json();
if (response.ok) {
  for (const area of body.areas) console.log(area.name, area.files, area.why);
} else {
  console.log(body.error.message);
}
```
{% /code-group %}

---

**Response:**

{% tabs %}
{% tab label="200 OK" %}

```json
{
  "fingerprint": "8d1f0c6b2a9e4f37b5c8d0e2a4f6b8c1d3e5f7a9b0c2d4e6f8a1b3c5d7e9f0a2",
  "generated_at": "2026-09-25T10:00:00Z",
  "areas": [
    {
      "id": "billing",
      "name": "Billing",
      "why": "Invoice totals and tax rounding changed, with new invoice tests.",
      "files": 3,
      "folders": [
        {"path": "app/billing", "files": 2},
        {"path": "app/billing/tests", "files": 1}
      ],
      "loudest": [
        {"path": "app/billing/tests/test_invoice.py", "added": 55, "removed": 0, "untracked": false},
        {"path": "app/billing/invoice.py", "added": 40, "removed": 2, "untracked": false},
        {"path": "app/billing/tax.py", "added": 3, "removed": 1, "untracked": false}
      ]
    },
    {
      "id": "documentation",
      "name": "Documentation",
      "why": "A billing guide was added and the readme was touched.",
      "files": 2,
      "folders": [
        {"path": ".", "files": 1},
        {"path": "docs/guide", "files": 1}
      ],
      "loudest": [
        {"path": "README.md", "added": 1, "removed": 1, "untracked": false},
        {"path": "docs/guide/billing.md", "added": null, "removed": null, "untracked": true}
      ]
    }
  ]
}
```

{% /tab %}
{% tab label="403 Forbidden" %}

    ```json
    {
      "success": false,
      "error": {
        "code": "FORGE_PROJECT_GROUP_ORGANIZATION_UNAVAILABLE",
        "message": "This project group is billed to Client Workspace, where you can no longer use Forge. Choose another organization for the group.",
        "system_message": "Forge project group bills to an organization the caller cannot use: 'org_1a2b3c4d5e6f4a7b8c9d0e1f2a3b4c5d'",
        "type": "authorization_error",
        "status": 403,
        "details": {
          "client_id": "grp-old-client",
          "organization_id": "org_1a2b3c4d5e6f4a7b8c9d0e1f2a3b4c5d"
        },
        "trace_id": "trace_abc",
        "timestamp": "2026-10-09T10:00:00Z"
      }
    }
    ```

{% /tab %}
{% tab label="413 Payload Too Large" %}

    ```json
    {
      "success": false,
      "error": {
        "code": "FORGE_IMPACT_TOO_MANY_FILES",
        "message": "This change is too large to assess: 6,200 changed files, and the limit is 5,000. Commit or split part of it and try again.",
        "system_message": "6200 changed files sent; one assessment covers at most 5000",
        "type": "client_error",
        "status": 413,
        "details": {
          "field": "files",
          "max_files": 5000,
          "limit_count": 5000,
          "limit_noun": "files",
          "observed_count": 6200
        },
        "trace_id": "trace_abc",
        "timestamp": "2026-09-25T10:00:00Z"
      }
    }
    ```

{% /tab %}
{% tab label="422 Unprocessable Entity" %}

    ```json
    {
      "success": false,
      "error": {
        "code": "FORGE_IMPACT_INVALID_REQUEST",
        "message": "The list of changed files could not be read. Refresh the panel and try again.",
        "system_message": "Invalid impact request: files[3].path repeats a path sent earlier",
        "type": "validation_error",
        "status": 422,
        "details": {
          "field": "files[3].path",
          "reason": "repeats a path sent earlier"
        },
        "trace_id": "trace_abc",
        "timestamp": "2026-09-25T10:00:00Z"
      }
    }
    ```

{% /tab %}
{% tab label="502 Bad Gateway" %}

    ```json
    {
      "success": false,
      "error": {
        "code": "FORGE_IMPACT_GENERATION_FAILED",
        "message": "The impact assessment could not be generated. Try again.",
        "system_message": "Impact assessment could not be generated: unusable_output",
        "type": "bad_gateway",
        "status": 502,
        "details": {
          "reason": "unusable_output"
        },
        "trace_id": "trace_abc",
        "timestamp": "2026-09-25T10:00:00Z"
      }
    }
    ```

{% /tab %}
{% tab label="504 Gateway Timeout" %}

    ```json
    {
      "success": false,
      "error": {
        "code": "FORGE_IMPACT_TIMED_OUT",
        "message": "The impact assessment is taking longer than expected. Try again in a moment.",
        "system_message": "Impact assessment still running after 60s; it will be cached when it completes",
        "type": "gateway_timeout",
        "status": 504,
        "details": {
          "wait_seconds": 60.0
        },
        "trace_id": "trace_abc",
        "timestamp": "2026-09-25T10:00:00Z"
      }
    }
    ```

{% /tab %}
{% /tabs %}
{% /api-examples %}
{% /api-container %}

---

## Related Resources

- [Publish a page](./publish-artifact.md)
- [List published pages](./list-artifacts.md)
- [Create a skill link](./create-skill-share.md)
- [Send a Forge notification to your phones](./send-push-notification.md)
- [List project groups](../forge-compute/list-project-groups.md)
- [Register or update a project group](../forge-compute/put-project-group.md)
- [Get usage detail report](../analytics---usage-detail/get-usage-detail-report.md)
