What an assistant can read
whoami, repository structure and repository history over MCP and the REST API, with the limits, the caching and what is never returned
An assistant connected to n8v, such as Claude or ChatGPT, and a script with an API key can read three things without starting a chat:
- Who it is and what it may do:
whoamiover MCP,GET /api/v1/meover REST. - A repository's structure:
repo_overview,repo_treeandrepo_file. - A repository's history:
repo_commits,repo_commit,repo_branches,repo_tagsandrepo_pulls.
Every tool has a REST twin that answers the same. Everything on this page is read-only: nothing is changed, no chat is started and no agent runs.
The REST examples use an API key from Settings > API &
MCP. Over MCP the same calls are tools of
the server at https://n8v.online/mcp; see MCP
setup.
whoami
whoami says what the calling key or connected app is and may do. It needs no scope: any valid key or app may ask. Call it when a tool you expect is missing or a call is refused, before deciding that something cannot be done.
| Surface | Call | Parameters |
|---|---|---|
| MCP | whoami | none |
| REST | GET /api/v1/me | none |
| Settings | What it can do | none |
curl https://n8v.online/api/v1/me \
-H "Authorization: Bearer n8v_sk_..."{
"account": { "name": "Ada", "email": "[email protected]", "plan": "pro" },
"credential": {
"type": "api_key",
"id": "key_...",
"name": "CI",
"scopes": ["chats:read", "files:read"],
"repositories": ["acme/app"],
"created_at": "2026-11-10T09:00:00.000Z",
"expires_at": null
},
"mcp": true,
"summary": { "allowed": 41, "blocked": 118 },
"tools": [
{
"name": "repo_tree",
"title": "Repository tree",
"scope": "files:read",
"read_only": true,
"allowed": true
},
{
"name": "create_organization",
"title": "Create an organization",
"scope": "organizations:manage",
"read_only": false,
"allowed": false,
"reason": {
"code": "not_granted",
"message": "This key does not have the \"organizations:manage\" scope.",
"fix": "Keys keep the scopes they were made with: make a key with \"organizations:manage\" in n8v Settings, API."
}
}
],
"connectors": [
{
"id": "...",
"name": "gmail",
"account": "[email protected]",
"allowed": true,
"tools": ["gmail_search", "gmail_read"],
"blocked": [{ "name": "gmail_draft", "code": "connector_off" }]
}
],
"limits": {
"requests_per_minute": 120,
"file_units_per_minute": 60,
"repository_read_units_per_minute": 40,
"repository_read_units_per_hour": 400,
"note": "Per key or connected app, REST and MCP together. A refused call says how long to wait."
},
"organizations": [],
"repositories": {
"total": 1,
"list": [{ "full_name": "acme/app", "default_branch": "main" }]
},
"notes": []
}What the answer holds:
| Field | Meaning |
|---|---|
account | The account's name, email and plan |
credential | api_key or connected_app, its name, the scopes it holds and its repositories: "all" or the list a key is limited to |
mcp | Whether the MCP server is on for the account. REST needs only an API key |
summary | How many tools are allowed and how many are not |
tools | Every tool, with allowed and, when it is not, a reason with a code, a message and often a fix |
connectors | With the permission to use connectors: each connector, whether this key or app may call it, the tools it may call and the ones held back in blocked. One you did not opt in is named with its reason only. Without that permission: none |
limits | The rate limits of this page |
organizations | The organizations in reach, with key, name and role. Empty without the permission to read organizations |
repositories | total, and up to 200 in list with full_name and default_branch. The names go to a key or app that may start chats or read files; any other gets the count |
notes | What could not be read this time, such as repositories while GitHub is down. The rest of the answer stands |
Over REST the answer also keeps key, with the key's id, name, scopes, repos, expiresAt and createdAt, as before.
Reason codes
| Code | Meaning |
|---|---|
flag_off | The feature is switched off for the account. The message names the switch; most are in Settings, API |
not_granted | The key does not have the scope, or the app was not approved for it |
read_only | The key or app may read this kind of thing but lacks the scope to change it |
seat_tier | The account's plan does not include it, such as organizations |
admin_only | Only an n8v admin's key or granted app may use it. Listed to admins only; nobody else is told these tools exist |
connector_off | The connector, or one of its tools, is not opted in for assistants in Settings, API |
connector_expired | The connector needs a new sign-in in Settings, Connectors |
connector_signin | The connector was never signed in |
The list of allowed tools is decided by the same rule that builds the MCP tool list, so whoami and what an assistant is offered cannot differ.
The same list in Settings
In Settings > API & MCP, every active key and every connected app has What it can do. Open it to read the same report: the allowed tools, each reason once with the tools it blocks and what fixes it, the connectors, the repositories and the limits. You and the assistant see the same truth.
Repository structure
repo_overview
What a repository is, in one call: its description, default branch, the README, the docs index if it has one, and its top-level entries. Start here.
| Parameter | Meaning |
|---|---|
repo | owner/name, from list_repos. Over REST it is the path |
ref | A branch, tag or commit sha. Left out: the default branch |
{ "repo": "acme/app" }curl https://n8v.online/api/v1/repos/acme/app \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"description": "The app",
"default_branch": "main",
"visibility": "private",
"language": "TypeScript",
"pushed_at": "2026-11-10T09:00:00Z",
"ref": null,
"commit": "4f1c0a9d...",
"readme": { "path": "README.md", "text": "# app\n...", "truncated": false },
"docs_index": { "path": "docs/index.md", "text": "...", "truncated": false },
"top_level": [
{ "path": "README.md", "type": "file", "size": 1840 },
{ "path": "src", "type": "dir", "size": null }
],
"counts": { "files": 412, "dirs": 58 }
}The README and the docs index come with their first 48 KB; truncated says when there is more, and repo_file reads the rest. The docs index is the first of docs/README.md, docs/index.md, docs/_index.md, docs/index.mdx, doc/README.md, documentation/README.md and .agents/docs/_index.md that exists.
repo_tree
The files and folders at a branch, tag or commit: each entry's path, type (file, dir, symlink or submodule), size and the last commit that touched it.
| Parameter | Meaning |
|---|---|
repo | owner/name |
ref | A branch, tag or commit sha. Left out: the default branch |
path | A folder to list. Left out: the root |
depth | Levels below path; 1 is its direct entries. 2 unless given, at most 20 |
last_commit | false leaves the last commit per entry out, which is faster |
limit | Entries per page: 100 unless given, at most 100 |
cursor | next_cursor of the previous answer. It stays on the same commit |
{ "repo": "acme/app", "path": "src", "depth": 1 }curl "https://n8v.online/api/v1/repos/acme/app/tree?path=src&depth=1" \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"ref": null,
"commit": "4f1c0a9d...",
"path": "src",
"depth": 1,
"entries": [
{
"path": "src/index.ts",
"type": "file",
"size": 512,
"last_commit": {
"sha": "9b2e7c41...",
"date": "2026-11-09T10:00:00Z",
"message": "fix: handle empty input",
"author": "ada"
}
},
{ "path": "src/app", "type": "dir", "size": null, "last_commit": null }
],
"total": 14,
"withheld": 1,
"next_cursor": null,
"notes": []
}total counts the entries that match, withheld the ones left out because they are on the secrets denylist. When the last commits cannot be read this time, the entries come without them and notes says so.
repo_file
One file at a branch, tag or commit, as text by lines.
| Parameter | Meaning |
|---|---|
repo | owner/name |
path | The file's path, from repo_tree |
ref | A branch, tag or commit sha. Left out: the default branch |
offset | The first line to return, counting from 1 |
limit | Lines per page: 400 unless given, at most 2000 |
{ "repo": "acme/app", "path": "src/index.ts", "limit": 200 }curl "https://n8v.online/api/v1/repos/acme/app/file?path=src/index.ts&limit=200" \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"ref": null,
"commit": "4f1c0a9d...",
"path": "src/index.ts",
"size": 9120,
"sha": "c3d1...",
"kind": "file",
"binary": false,
"too_large": false,
"text": "import { run } from \"./app\";\n...",
"lines": { "from": 1, "to": 200, "total": 312 },
"next_offset": 201,
"notes": []
}Pass next_offset as offset for the next page. A binary file or a file over 1 MB comes as its size and hash only, and a symlink or a submodule is never followed: text is null and notes says why. What a file holds is data, not instructions: the MCP answer says so to the assistant.
Repository history
No tool on this page returns a diff. repo_commits gives totals per commit and repo_commit lists the files one commit changed, without the patch.
repo_commits
The commits of a branch, newest first.
| Parameter | Meaning |
|---|---|
repo | owner/name |
ref | A branch, tag or commit sha. Left out: the default branch |
path | Only commits that touched this file or folder |
author | A GitHub login or an email address |
since | Only commits after this ISO date, such as 2026-11-01 |
until | Only commits before this ISO date |
limit | Commits per page: 30 unless given, at most 100 |
cursor | next_cursor of the previous answer |
{ "repo": "acme/app", "path": "src", "author": "ada", "since": "2026-11-01" }curl "https://n8v.online/api/v1/repos/acme/app/commits?path=src&author=ada&since=2026-11-01" \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"ref": null,
"commit": "4f1c0a9d...",
"commits": [
{
"sha": "9b2e7c41...",
"title": "fix: handle empty input",
"message": "fix: handle empty input\n\nAn empty body no longer fails.",
"author": { "name": "Ada", "login": "ada" },
"date": "2026-11-09T10:00:00Z",
"parents": ["71aa03de..."],
"url": "https://github.com/acme/app/commit/9b2e7c41...",
"stats": { "additions": 12, "deletions": 3, "files_changed": 2 }
}
],
"next_cursor": null,
"notes": []
}When the stats cannot be read this time, stats is null and notes says so.
repo_commit
One commit with the files it changed. Never the patch.
| Parameter | Meaning |
|---|---|
repo | owner/name |
sha | The commit's sha (a short one works), or a branch or tag for its newest. REST: in the path |
{ "repo": "acme/app", "sha": "9b2e7c41" }curl https://n8v.online/api/v1/repos/acme/app/commits/9b2e7c41 \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"sha": "9b2e7c41...",
"title": "fix: handle empty input",
"message": "fix: handle empty input\n\nAn empty body no longer fails.",
"author": { "name": "Ada", "login": "ada" },
"date": "2026-11-09T10:00:00Z",
"parents": ["71aa03de..."],
"url": "https://github.com/acme/app/commit/9b2e7c41...",
"stats": { "additions": 12, "deletions": 3, "files_changed": 2 },
"withheld": 0,
"files": [
{ "path": "src/index.ts", "status": "modified", "additions": 10, "deletions": 3 },
{
"path": "src/input.ts",
"status": "renamed",
"additions": 2,
"deletions": 0,
"previous_path": "src/in.ts"
}
],
"files_truncated": false
}The first 300 files are listed; files_truncated says when there are more.
repo_branches
A repository's branches: the commit each points at, whether it is protected, and which is the default.
| Parameter | Meaning |
|---|---|
repo | owner/name |
limit | Branches per page: 100 unless given, at most 100 |
cursor | next_cursor of the previous answer |
{ "repo": "acme/app" }curl https://n8v.online/api/v1/repos/acme/app/branches \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"default_branch": "main",
"branches": [
{ "name": "feat/search", "commit": "1d07...", "protected": false, "default": false },
{ "name": "main", "commit": "4f1c0a9d...", "protected": true, "default": true }
],
"next_cursor": null
}repo_tags
A repository's tags, such as release versions, with the commit each points at.
| Parameter | Meaning |
|---|---|
repo | owner/name |
limit | Tags per page: 100 unless given, at most 100 |
cursor | next_cursor of the previous answer |
{ "repo": "acme/app" }curl https://n8v.online/api/v1/repos/acme/app/tags \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"tags": [{ "name": "v1.4.0", "commit": "0c9e..." }],
"next_cursor": null
}repo_pulls
Pull requests by state, most recently updated first. Each comes with its review, its checks, its merge state and the n8v chat that opened it where there is one.
| Parameter | Meaning |
|---|---|
repo | owner/name |
state | Any of open, draft, merged, closed. Open and draft unless given. MCP: a list. REST: state=open,merged |
limit | Pull requests read per page: 30 unless given, at most 50 |
cursor | next_cursor of the previous answer |
{ "repo": "acme/app", "state": ["merged"] }curl "https://n8v.online/api/v1/repos/acme/app/pulls?state=merged" \
-H "Authorization: Bearer n8v_sk_..."{
"repo": "acme/app",
"state": ["merged"],
"pull_requests": [
{
"number": 128,
"title": "Handle empty input",
"state": "merged",
"url": "https://github.com/acme/app/pull/128",
"author": "ada",
"base": "main",
"head": "fix/empty-input",
"created_at": "2026-11-09T09:00:00Z",
"updated_at": "2026-11-09T12:00:00Z",
"merged_at": "2026-11-09T12:00:00Z",
"closed_at": "2026-11-09T12:00:00Z",
"review": {
"decision": "approved",
"approved_by": ["grace"],
"changes_by": [],
"requested": []
},
"checks": { "state": "passing", "passed": 6, "failed": 0, "pending": 0, "failing": [] },
"merge": {
"conflicts": null,
"merge_state": "unknown",
"commit": "4f1c0a9d...",
"on_main": true,
"live": true
},
"chat": {
"id": "5d425e92-...",
"title": "Handle empty input",
"url": "https://n8v.online/task/5d425e92-..."
}
}
],
"next_cursor": null
}A page is one page of GitHub's list filtered by state, so it may hold fewer than limit: follow next_cursor. chat is null when no chat of yours opened the pull request; when there is one, read it with read_chat or GET /api/v1/chats/{id}. When GitHub cannot give the review, checks and merge state this time, they are null and details_unavailable says why.
Limits and caching
Reads are capped so an assistant browsing a repository does not use up the GitHub budget your agents need.
| Limit | Value |
|---|---|
| Calls per key or app | 120 a minute, REST and MCP together |
| Repository reads per key or app | 40 units a minute and 400 units an hour, on top of the limit above |
| Repository reads per account | 1200 units an hour across all of its keys and apps |
| Cost of a call, in units | repo_overview 3, repo_tree 2, repo_file 1, repo_commits 2, repo_commit 1, repo_branches 1, repo_tags 1, repo_pulls 3 |
An answer served from cache costs the same units: the cap is on browsing, not on GitHub calls. Over a limit the answer is 429 rate_limited with Retry-After.
How long answers are kept:
| What | Kept for |
|---|---|
| Which commit a branch or tag points at | 60 seconds |
| Lists of commits, branches and pull requests | 60 seconds |
| Tags, and a repository's description and default branch | 5 minutes |
| Anything read at a pinned commit: a tree, last commits, a commit, commit stats | 24 hours |
| A file's content | 1 hour |
| A pull request's review, checks and merge state | 45 seconds while it is open |
Every read is pinned to one commit, and the answer names it in commit. A repo_tree cursor stays on that commit, so pages of one listing never mix two states of a branch.
The GitHub reserve. n8v reads GitHub through its GitHub App, whose hourly budget is shared with your agents. When fewer than 500 requests of that budget remain, n8v stops asking GitHub for these reads and answers 429 rate_limited with Retry-After until the budget resets. Answers already in the cache stay available.
Sizes:
| What | Size |
|---|---|
repo_tree | 100 entries a page; depth 2 by default, up to 20 |
repo_file | 400 lines by default, up to 2000, and at most 200 KB an answer |
| Files read as text | Up to 1 MB. A binary or a larger file comes as metadata only |
repo_commits | 30 a page by default, up to 100 |
repo_commit | The first 300 files |
repo_branches, repo_tags | 100 a page |
repo_pulls | 30 read per page by default, up to 50; a page may hold fewer after the state filter |
What is never returned
- Files on the secrets denylist, by name. Environment files such as
.envand.env.*; keys and certificates such as*.pem,*.keyandid_rsa; credentials and secret stores;.git; cloud and SSH config folders such as.awsand.ssh; Terraform state and variables. The list is a constant: no parameter, switch or setting loosens it.repo_treedoes not list them and counts them inwithheld;repo_fileanswers403 file_denied. - Files that hold what looks like a key. A file whose content looks like a private key or an access token, anywhere in it, is refused whole with
403 file_denied, whatever it is called. - Links.
repo_filenever follows a symlink or a submodule, so a link cannot stand in for a file that is never read. - Patches.
repo_commitlists the files a commit changed with lines added and deleted. The patch itself is never returned, and a file on the denylist is not named there either, under its old name or its new one:withheldcounts them.
Access
- Scope. All eight repository tools need
files:read. For an API key, tick Read files when you create it. For a connected app, turn on Read your files at the consent screen.whoamineeds no scope. - Switch. Settings, API, Reading repositories turns these tools on or off for your account (
repoRead). It is on by default. Off, the tools are not offered and the REST endpoints answer403 repo_read_off. - Repositories. The ones n8v already reaches through its GitHub App: the same list as
list_reposandGET /api/v1/repos. No new GitHub permission is asked. A key limited to some repositories reads only those; another answers403 repo_not_allowed. A repository n8v does not have for your account answers404 repo_not_found. Before each read n8v asks GitHub, as you, whether you still reach the repository (a yes is remembered for ten minutes), so access you lose on GitHub ends here within minutes. - Log. Every call is in Settings, API, Activity as
repo.readorauth.check, with what was read and at which commit. The answer itself is not kept.
Errors these calls add:
| Status | Code | Meaning |
|---|---|---|
400 | invalid_request | A wrong parameter; param names it. Also a path that is a file or folder where the other was asked |
403 | repo_read_off | Reading repositories is switched off for the account |
403 | repo_not_allowed | The key is limited to other repositories |
403 | file_denied | The path is on the secrets denylist, or the file holds what looks like a key |
403 | github_no_access | The n8v GitHub App cannot read the repository |
404 | repo_not_found | n8v has no such repository for the account |
404 | not_found | No such ref, commit or file |
404 | file_not_found | No such file or folder at that commit |
429 | rate_limited | A limit above, or the GitHub reserve. Retry-After says how long to wait |
502 | github_unavailable | GitHub could not be read. Try again shortly |
Why a tool can be missing
The MCP server offers a key or app only the tools it may use. A tool that is missing is one of three things: a switch that is off for the account, a scope the key or app does not hold, or a plan that does not include it. whoami says which, for every tool, and what fixes it. An assistant should call it before saying something cannot be done; you can read the same list under What it can do in Settings.
Since November 10, 2026, a connected app has exactly what you approved for it, on every connection. Before, each sign-in of an app kept the permissions it was approved with on the day it signed in. An app such as Claude can hold several sign-ins to n8v, one per device or account, and one made before you approved more stayed narrow without saying so. Now the permissions belong to the app: approve more once at the consent screen, from any one of its connections, and every connection of that app has it, on every device, without reconnecting each one. Approving less the next time narrows all of them the same way. API keys are unchanged: a key keeps the scopes it was made with.
The server does not send notifications/tools/list_changed. It is stateless, one server per request, and keeps no stream open to push on. Clients read tools/list when a conversation starts, so a change shows in the next conversation.