Docs › Agents and teams
Testers, servers and webhooks
Testers send a zip or post to a shared server, and webhooks tell Slack, Discord or Teams.
Test mode: testers send you a zip#
<Notato mode="test" project="checkout-web" appVersion={import.meta.env.VITE_APP_VERSION} />
Testers annotate, then press Package: a zip downloads with feedback.md (annotations in order, with inline screenshots), annotations.json, and shots/. Their work survives a reload. Give your agent the zip:
notato_import_bundle { path: "~/Downloads/notato-checkout-web-20261005-1042.zip" }
Or add server="https://notato.example.com" token="notato_…" and the zip is uploaded as well. Input fields are masked in screenshots by default in test and agent mode, and password fields always; data-notato-mask="false" opts a field out. Mark anything else private with data-notato-mask: it is covered in screenshots and none of its text, or the text of anything inside it, is recorded. What is typed into a field is never recorded as text. The mobile SDKs follow the same rules (Feedback.Mask in .NET MAUI, .notatoMask() in SwiftUI, Notato.mask(view) and Modifier.notatoMask() on Android).
Shared server#
For testers on other machines, run one server:
NOTATO_ADMIN_PASSWORD=… npx notato serve --host 0.0.0.0 --trust-proxy
Put a TLS-terminating reverse proxy in front. It serves the board UI at / (sign in as admin), the API, and MCP over HTTP at /mcp.
- Projects come first. Create each one on the board (New project) or with
npx notato project create <id>. Either way you get the project's first token (notato_…, shown once) and, on the board, what to paste into each kind of app. Apps can only send to projects that exist: anything else is answered404with how to create it. - More tokens are made in the board (the project's settings, or Tokens) or with
npx notato token create <project>. A token for one project reads and writes only that project; a token for*is for your agent. Deleting a project stops its tokens working. - Give the SDK
serverandtoken. Connect an agent tohttps://notato.example.com/mcpwith the headerAuthorization: Bearer notato_…(Settings › Agents on the board has the line for each agent). Turning agents off (--no-mcp, or Settings) closes/mcpand refuses agent tokens, while apps keep sending. Claude Code:claude mcp add --transport http notato https://notato.example.com/mcp --header "Authorization: Bearer notato_…". Codex:url = "https://notato.example.com/mcp"andbearer_token_env_var = "NOTATO_TOKEN"under[mcp_servers.notato]in~/.codex/config.toml. NOTATO_CORS_ORIGINSallows browser origins beyond loopback and private networks. CORS is not authentication: every request needs a token or a login.
Docker (data in a volume):
docker run -p 4747:4747 -v notato:/data -e NOTATO_ADMIN_PASSWORD=… ghcr.io/notatorg/notato
| Variable | Meaning |
|---|---|
NOTATO_HOST, NOTATO_PORT, NOTATO_DIR | Bind address (default 127.0.0.1), port (4747), data directory (./.notato) |
NOTATO_ADMIN_USER, NOTATO_ADMIN_PASSWORD | Admin account. The password is applied on every start; if unset, one is generated and printed once |
NOTATO_TRUST_PROXY=1 | Believe X-Forwarded-For and X-Forwarded-Proto from your proxy |
NOTATO_CORS_ORIGINS, NOTATO_ALLOWED_HOSTS | Comma-separated origins / Host names |
NOTATO_CONFIG, NOTATO_SCREENSHOTS | Settings file (default ./notato.config.json); on/off overrides the file. See Screenshots |
NOTATO_MCP | off refuses agents whatever the file says. See MCP over HTTP |
NOTATO_PROJECT | notato dev: the projects this agent works on, comma-separated. See Several repositories |
Webhooks#
The server can tell other systems when an annotation is created or changes, such as a Slack or Discord channel, a build hook, your own service:
npx notato config webhook add https://hooks.slack.com/services/T000/B000/XXXX --format slack --event resolved
npx notato config webhook add https://ci.example.com/notato --secret env:NOTATO_HOOK_SECRET
npx notato config webhook # list them
npx notato config webhook test ci.example.com # send a sample now, and see what the other end said
Or on the board, under Settings › Webhooks: pick Slack, Discord, Teams or JSON, paste the URL, choose the project and the events, add a signing secret for JSON, and save. Test (or Preview and test… while editing, before you save) lets you pick an event and a real note (or a made-up one), shows the message exactly as it will go out (the Teams card drawn as Teams shows it, the chat line, or the JSON and its headers) and says what is left out and why, then Send this test sends that very message and shows what the other end answered (the HTTP status, how long it took and what it said back). The board writes the same file, never shows a saved URL or a secret written in the file again (it shows https://hooks.slack.com/services/•••• and "a secret is set"; replace them to change them), and refuses an edit made to a copy that has since changed in another tab or with notato config.
They live in notato.config.json ("webhooks": [{ "url": …, "events": […], "format": "json", "secret": "env:NAME", "name": …, "project": … }]), so they can be committed, and a running server picks a change up on the next event. Events: annotation.created, acknowledged, variants_ready, variant_chosen, resolved, revert_requested, reverted, dismissed, reopened, updated, replied, deleted; leave events out for all of them.
- json (default) posts
{ id, event, at, project, annotation, markdown }: the whole annotation and ready-made standard Markdown. slack and discord post one line they accept as is. - teams posts an Adaptive Card in the message shape a Microsoft Teams workflow takes ("Post to a channel when a webhook request is received", or the chat version): what happened, the note, the latest reply, the page, status, severity and element, and a button to open the page when it is a web address. Use it for a Teams or Power Automate workflow URL; given
json, those templates try to post the whole body as a card and Teams answers BadRequest. - Screenshots. A teams card shows the note's screenshot (a tap opens it full size), and a json payload adds
screenshotUrls: { full, crop }. Teams fetches the image itself and can send no token, so each link is signed for that one image (/shared/<id>.png?sig=…, with a key inshare.keybeside the data) and opens nothing else without signing in. It needs an address the outside world can reach: the dev tunnel's withnotato dev --tunnel, orNOTATO_PUBLIC_URL(fornotato servebehind a proxy, say). Without one, messages go without the screenshot. The images load while the server is running."screenshots": falseon a webhook (or the switch in the board) leaves them out. - Signing. With a
secret, each body is signed:X-Notato-Signature: sha256=<HMAC-SHA256 of the raw body>, so the receiver can tell it came from your server. Writeenv:NAMEto read the secret from an environment variable in the server's environment instead of keeping it in a file you commit. If that variable is not set, nothing is sent to that webhook (an unsigned request when a signature was asked for would be worse), and the server log says so once.X-Notato-EventandX-Notato-Delivery(a unique id, for de-duplication) are always sent. - Delivery. Sent off to the side, so a slow or dead endpoint never holds up the API. Deliveries to one URL go in order. A network error, a 5xx, a 429 or a 408 is retried after 1s, 4s and 15s; any other refusal (a 401, a 404) is not, since it would be refused again. Redirects are not followed. It gives up after that and logs it: there is no queue that survives a restart.
- The test annotation
notato doctorfiles and deletes is not sent, so a channel never hears about a health check. - A broken config file sends nothing and screenshots stay off (see above);
doctorandnotato configsay so.
Settings, and working with other people#
The gear on the toolbar opens a panel of settings for you, kept in this browser:
| Setting | |
|---|---|
| Your name | Written on your notes, replies and picks, so others can tell who wrote what |
| Copy as Markdown | The level the toolbar's copy button writes at |
| Component names, Computed styles | Whether to record them with each note |
| Screenshots | Whether to take one with each note. The server's own setting wins: where it has them off this is switched off and says so |
| Only my notes | Hide what other people wrote on this page (needs your name) |
| Pin colour | The colour of a pin nobody has acted on yet. Seven choices, none of them a colour a status already means |
| Hide Notato until this page is reloaded | Gets it out of the way; the annotate shortcut brings it back |
Server and agent shows whether the page is connected, the server's version and how many pages are connected, and which coding agents are connected and whether one is watching (with how to set Notato up for one when none is). A page knows only where the server is: webhooks are set up on the server, in its config file or with npx notato config webhook, and a page never sees them.
Several people can annotate one project, each in their own browser with their own name. When more than one person has notes on a page, each pin carries its author's initials in a colour taken from their name, and a card shows who wrote what. The board has a Person filter, notato export --by "Sam" and ?by= on the API write only one person's notes, and a pick or a reply is attributed to whoever made it. This is attribution, not accounts: a shared server still has one admin login and project tokens, and anyone with a project's token can write as any name.