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

# Blame

> Attribute every line of a file to the commit that introduced it, with landing attribution on each range, over HTTP.

The blame endpoint returns, for every line of a file, the commit that introduced that line. No clone is needed. Each of those commits carries the same [landing attribution](/git/commits#landing-attribution) as the history listing: the operation that landed it (`push`, `promote`, or `merge`) and the actor that ran it.

<Note>
  In a **clone**, `git blame` works as usual; Tensorlake is a normal remote. This endpoint is for dashboards, code review tools, and agents that need line-level attribution without a checkout.
</Note>

## Blame over HTTP

```text theme={null}
GET /project/{project}/repos/{repo}/blame/{path}
```

`{path}` is the file's repository path, given literally (e.g. `blame/src/app.rs`). Authenticate with a short-lived Git credential over HTTP Basic auth (a read-scoped credential is enough):

```bash theme={null}
TOKEN=$(tl git token agent-outputs --json | jq -r .token)

curl -u t:$TOKEN \
  "https://git.tensorlake.ai/project/project_9f3c2a1b/repos/agent-outputs/blame/src/app.rs?ref=main"
```

```json theme={null}
{
  "repo": "project_9f3c2a1b/agent-outputs",
  "commit": "1c4d9a2f7e5b...",
  "ref_name": "refs/heads/main",
  "path": "src/app.rs",
  "line_count": 3,
  "ranges": [
    {
      "start_line": 1,
      "line_count": 1,
      "commit": {
        "oid": "4f8c2a17b9d3...",
        "subject": "initial app",
        "author_name": "Agent User",
        "author_email": "agent@example.com",
        "author_at_secs": 1755990000,
        "landing": {
          "via": "push",
          "actor": { "name": "user:diptanu", "kind": "human" }
        }
      }
    },
    {
      "start_line": 2,
      "line_count": 2,
      "commit": {
        "oid": "1c4d9a2f7e5b...",
        "subject": "add parser notes",
        "author_name": "Agent User",
        "author_email": "agent@example.com",
        "author_at_secs": 1756000000,
        "landing": {
          "via": "promote",
          "actor": { "name": "api-key:key_7d2f9a1c", "kind": "agent" },
          "source_workspace_id": "3f9a2b7e1c4d",
          "source_snapshot": "8b21f6a9c3d5..."
        }
      }
    }
  ],
  "truncated": false
}
```

### Query Parameters

| Parameter | Meaning                                                                                               |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `ref`     | Branch name, tag name, full `refs/...` name, or a full 40-hex commit. Defaults to the default branch. |

A bare name resolves as a branch first, then as a tag. The response's `ref_name` field reports the ref that resolved (`refs/heads/main`, `refs/tags/v1.0`, or absent when `ref` was a commit oid). An unknown ref, or a path that does not exist at that ref, returns `404`.

### Response Fields

| Field                 | Meaning                                                                                                                                |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `commit`              | The commit the blame was computed at (the resolved `ref`).                                                                             |
| `ref_name`            | The ref that resolved, when `ref` named one.                                                                                           |
| `line_count`          | Total lines in the file at `commit`.                                                                                                   |
| `ranges`              | Consecutive line ranges, in order, covering the file.                                                                                  |
| `ranges[].start_line` | 1-based first line of the range.                                                                                                       |
| `ranges[].line_count` | Lines in the range.                                                                                                                    |
| `ranges[].commit`     | The commit that introduced these lines, with author fields and `landing`. Absent for lines that were not attributed (see `truncated`). |
| `truncated`           | The walk stopped early; unattributed ranges are returned without a `commit` field.                                                     |
| `unblameable`         | Present only when the file itself cannot be line-blamed (see below).                                                                   |

## Limits and Partial Results

The endpoint reports incomplete or inapplicable results explicitly instead of returning wrong attribution:

* **`unblameable: "not_a_file"`** — the path is a directory at `ref`.
* **`unblameable: "binary_or_too_large"`** — the file is binary or larger than the 256 KiB content cap. The file's size and object id are still available from the [tree and file endpoints](/git/repository-sdks); blame returns no line ranges for it.
* **`truncated: true`** — the walk stopped before every line was attributed. This happens when the history is deeper than the scan cap (10,000 commits), the file was changed more times than the diff cap (512), the walk reached a binary or over-cap version of the file, or the time budget elapsed. Lines attributed before the stop are returned normally; the rest are in ranges without a `commit` field.
* **`425 Too Early`** — the file's content at the tip is still being indexed after a recent push. Retry after a short delay. The history endpoints use the same status for the same condition.

## How It Works

Blame walks the file's changes newest-first over the same changed-path index that
[file history](/git/commits#file-and-directory-history) uses, so commits that did not change
the file are skipped without reading any content. At each change it diffs the file against the
parent version and maps unattributed lines backward; a line is attributed to the commit that
introduced it. Attribution comes from these content diffs, not from commit metadata.

## Next Steps

<CardGroup cols={2}>
  <Card title="Commits & History" icon="clock-rotate-left" href="/git/commits">
    Branch history with landing attribution, file and directory filters, and per-commit diffs.
  </Card>

  <Card title="Repository Mounts" icon="folder-tree" href="/git/workspace-mounts">
    The snapshot and promote workflow that produces attributed history.
  </Card>

  <Card title="Repository SDKs" icon="rectangle-code" href="/git/repository-sdks">
    Repository operations and the operation log from Python or TypeScript.
  </Card>

  <Card title="Authentication" icon="key" href="/git/authentication">
    Short-lived Git credentials, scopes, and token lifetime.
  </Card>
</CardGroup>
