What if I lose the key?
Key
In secure mode, the notes go with it. There is no recovery and
no rotation — the key is what decrypts them, and the server has
never had it. In open mode the key is written in the tag, on a page
anybody can read, so it cannot be lost.
Can I let someone read the notes without writing any?
KeyWho can do what
No. The key reads and writes; there is no reader-only role.
What exactly is encrypted, and by what?
KeyPrivacy
The remark, its author, the element it points at
and the page it is on — everything a reader wrote. Your
browser encrypts it with AES-256-GCM, using a key derived
from yours by HKDF-SHA-256, and sends the result. The key stays
in the browser: it travels in the tag or in a paste, never in a
request. What the server stores is a block of bytes it cannot
open, a project id, and a blind index saying which page —
which is how it can group notes without knowing a single path.
The exact scheme is in
FORMAT.md.
Where does the key come from?
KeySetting it up
Your browser draws it, on the
install page, and sends it
nowhere. The project id in the tag is derived from it and does not
lead back to it — the server recognises that id, it never
issues one.
What does the notes server see?
PrivacyYour own server
Counts, times, sizes, IP addresses and your domain. Never your
paths, never your text — in secure mode it cannot read a line
it stores.
What statistics does annotepage collect?
PrivacyYour own server
Usage totals, and nothing else. Once a day, each
annotepage server tells the project three numbers: how many projects,
notes and pages it has carried, along with its version. That is how we
know how many servers run annotepage, and how much they are used.
Nothing identifies you. No domain name, no
project, no page, not a word of a note. Servers are told apart by an
identifier each one draws at random, tied to nothing. Addresses are
never stored: only a keyed fingerprint shared by hundreds of networks,
kept for the day — the rigour asked of far more sensitive
data.
It is on by default, and one line turns it off:
'report_statistics' => false in
internal/config-local.php, or the matching option in the
installer.
Is there an account, a moderator, a way to delete a note?
Who can do whatPrivacy
None of them, and no notification either. A project is a key and
the notes under it. Nobody can point at a remark and make it go
— age can, and only age: see the next answer.
How long are the notes kept?
Who can do whatPrivacyYour own server
For as long as the server that holds them says. The installer
writes ninety days into every configuration it creates,
counted from a thread’s last message: the whole thread goes at
once, so a reply is never cut off its remark, and a discussion that
is still live is never cut short. Nobody chooses which — age,
and nothing else, which is what keeps “no moderation”
true. The panel on your pages says the number the server really
uses, and it says how many notes have already gone that way. On
your own server, one line in the file keeps everything for
ever.
Is there a limit on what can be written?
Your own serverWho can do whatSetting it up
Yes, four of them, and every one is a line in your
configuration with a default that suits a review team. They are
counted per five-minute window: 120 writes per IP address,
300 writes per project — everybody writing to it
together — and 90 exports per IP. Past one of those the
answer is a 429 saying when to come back. A request body over
64 KB gets a 413. And a project may be capped at a
number of rows, which a public relay sets to 6000 — about
2000 remarks, since a discussed thread is three rows — and a
server carrying one team’s own notes leaves off.
You do not have to find them afterwards: the installer
has a field for every one, folded under “change anything
else”, with the number that applies if you leave it empty
shown in the field — and the same on the command line, where
--help lists each one with its default.
Can I switch from open to secure later?
KeyPrivacy
For new notes, yes: change the tag. The notes already written
keep the mode they were written in, and nothing converts them.
Can the page sit behind a login?
PrivacyYour site
Yes. Neither the notes server nor the assistant ever opens it:
the MCP talks to the notes API and to nothing else, and the page is
opened by the reviewers’ own browsers, which are already
signed in.
Can I try it on my own machine?
Setting it upYour site
Yes: localhost is a secure context, so the browser
has the crypto it needs. But never declare
localhost:3000 in a server’s list of domains
— that name belongs to every machine on earth, so declaring it
lets any of them write to your project.
Is it free?
Cost and licence
Yes, and it is MIT. The shared notes server at
api.annotepage.com is free to use with nothing to
install; running your own is free too, and that is the whole
list.
Do I have to use your server?
Your own serverCost and licence
No. One PHP file installs your own, and the
install page takes you
through it. Whichever one you run, its address is what goes in the tag.
What does the daily line change on my server?
UpdatesYour own server
Three things. The update: it asks
which version is published, and when it is newer, downloads only the
files that differ, checks each one, and swaps them in. The files it
replaced are kept in .update/previous-<version>-<date>,
beside the install. The maintenance: it removes the threads
older than your configuration keeps them —
ninety days after the last reply, unless you
changed it — and adjusts the tables when a release needs
it. The statistics: it tells the project how much this
server carries — three totals, under
an identifier drawn at random — unless you turned that
off.
It never touches
internal/config-local.php, never puts
install.php back once it is deleted, and never replaces
a store file you have edited yourself.
Where does an update come from, and what if it fails halfway?
UpdatesYour own serverPrivacy
From the
project’s repository on GitHub, over HTTPS with the
certificate checked. Every file is hashed once it is on your disk and
compared with the SHA-256 in the published manifest: one mismatch
and nothing is changed. The new files wait beside the old ones
and are swapped in together; if the swap fails, the old ones go
back. A run that overruns its time swaps nothing and tries again the
next day.
What an update takes from your server is a request for
the version number, once a day, and the files when there is a release.
Nothing about your notes, your sites or your visitors — the one
other thing a server sends is its daily
totals. The line the install
page gives you runs at a minute and an hour drawn for your install,
so servers do not all ask at the same second.
Is the address for the scheduler safe if somebody finds it?
UpdatesYour own server
It carries a long secret the installer writes and
shows you once. Without it, the server answers as if the action did
not exist. With it, somebody can make your server check for a
release and run its maintenance — at most one real check a
day, or every time with &force=1 — and
nothing else: what gets installed is still only a release whose
every file matches the published manifest.
If it leaks, into a host’s logs for instance,
change update_token in
internal/config-local.php and paste the new address
into the scheduler.
Can I update by hand instead, or not at all?
UpdatesYour own server
Yes. Run php internal/update.php when
you choose; --only-maintenance does the housekeeping
and fetches no update, and --help lists both.
With nothing scheduled, the server keeps working on
the version it has. Old threads still go, but only when somebody
writes a note, and then one time in fifty — so a quiet project
keeps them longer than its configuration says.
Can I install the assistant's tool for good?
Setting it upUpdates
Yes. By default nothing is installed: you say
npx when you ask, and your assistant fetches the latest
version each time. Installed and plugged in, its tools are there in
every conversation instead:
npm install -g annotepage-mcp
claude mcp add annotepage -- annotepage-mcp
With npx, nothing stays on the machine: the latest
version is fetched each time, so there is never anything to update.
Installed, it stays, and it does not update
itself. When it is behind it says so to your assistant, which tells you:
updating is npm install -g annotepage-mcp@latest, often with
administrator rights, and then restarting the assistant — two
things it cannot always do on its own. Once installed, npx
annotepage-mcp runs that copy too.
Can I run a version before everybody else?
Setting it upThe tag on your page
Yes, and it is one word in each place.
The tag on your pages — ask for
@next instead of @2:
https://cdn.jsdelivr.net/npm/annotepage-client@next/dist/annotepage.js
Your own notes server — one line in
internal/config-local.php, pointing it at the
candidate branch:
'update_source' => 'https://raw.githubusercontent.com/tuxo83/annotepage/next/server/webroot/',
It stays hashed against the manifest either way:
a candidate is checked exactly as a release is. Put
main back and the next daily check returns it to
the stable one.
The assistant — nothing. The MCP
talks to your server whichever code it is running.
Why @2 and not @latest?
The tag on your pageNothing appears
Because it is not the ceiling it looks like.
A page carrying @2 still ends up running version 3
— your notes server announces the client it works with, and
the client fetches that exact version and replaces itself, major
number included. What @2 decides is only which copy
loads first.
And that order is the point. A major version is
the one release allowed to change the protocol; the client is on
a CDN and the server is on somebody's machine, so
@latest would deliver the new client to every page
the day it is published, in front of servers that have not moved
yet. Announced instead, the server is already running it before
it says so.
If you would rather have the newest whatever your
server is running, ask for it — it is the same one word:
https://cdn.jsdelivr.net/npm/annotepage-client@latest/dist/annotepage.js
A release candidate never arrives through any of
the three: ranges exclude pre-releases, latest is
never moved to one, and the client refuses to install one on its
own.
Where is the source?
Cost and licence
All of it, MIT:
github.com/tuxo83/annotepage
— the client, the notes server, the MCP and this site. The two
published packages are
annotepage-client
(the tag) and
annotepage-mcp
(what plugs an assistant onto it). The server is not a package:
it is one PHP file that fetches the rest.
Can I load it as a module?
The tag on your pageNothing appears
No. The client reads
document.currentScript to find its own settings, and a
type="module" script does not have one: nothing appears
on the page. It is no longer silent about it — one line goes to
the console — and the settings can be
declared on the page instead.
My build combines the scripts, and nothing appears
The tag on your pageNothing appears
The client reads its settings off its own tag, through
document.currentScript — which is null for a
file concatenated with others by a “combine JS” option or an asset
pipeline, inlined, or loaded as a module. There is then no tag to read, so the
client stands down having drawn nothing and sent nothing, and writes one line
in the console saying why.
The same settings can be declared on the page instead, in
one object, before the client is loaded:
<script>
window.annotepageConfig = {
server: 'https://example.com/annotepage/api.php',
key: '<43 characters>'
};
</script>
The names are the attributes without data-, and
server is required there: a tag served by your own site falls
back on ../api.php beside its own file, and with no tag there is no
file to count from — an address guessed wrong would send the remarks
nowhere at all, so it is refused instead. A page may carry both a tag and the
object, and they must then say the same thing: anything else is refused, naming
the setting they disagree about. The tag stays the standard way, and the
install page writes it for you.
What if I pin the script with a wrong integrity digest?
The tag on your pageNothing appears
The browser refuses the file and the tool simply does not
appear. Nothing on the page says why. The
install page writes the
digest for you when you ask for a pinned tag.
My site sets a Content-Security-Policy. What do I need?
The tag on your pageNothing appears
Two directives. script-src must allow wherever the
client is served from, or it never loads;
connect-src must allow your notes server, or
fetch fails and the tool stands down in silence, as if
nothing had been configured. style-src usually needs
nothing: the stylesheet goes in as a constructed sheet, which is not
an inline sheet in the policy’s sense.
Can one page carry two projects?
The tag on your page
No: one tool runs on a page at a time. The first tag
loaded wins; another one on the same page is ignored, and says so in the
console.
Several projects on one site are fine — one per
section, one per client — as long as each page loads only its own
tag. Deciding which one a page gets belongs to your site’s own
code.
A switch between projects is possible, and it is
the site’s to build: a small control that reloads the page with the
other project’s tag. The two never run at the same time — the
reload is what hands the page from one to the other.
Can reviewers annotate only part of a page?
The tag on your page
Yes. Add data-zone to the tag with a CSS
selector, or several separated by commas — the article, not the
header, the menu or the footer:
data-zone="article"
data-zone=".post-content, .comments"
In annotation mode those areas are outlined, and a click
anywhere else writes nothing and says why. Notes already written outside
stay visible. If the selector matches nothing, nothing can be annotated
— the tool never falls back to the whole page.
It guides reviewers; it is not a security boundary. The
restriction lives in the browser, where anyone determined can lift it.
I send the HTML file instead of a link. Will everyone see the same notes?
The tag on your pageNothing appears
Not on its own. A page is identified by the path in its
address, and a file opened from a disk has the path of the folder it was
saved in — a different one for every person, and for you as soon as
you move or rename it. Same file, same key, and each reader gets an empty
page of their own.
Tell the file which page it is, with
data-page on the tag. Every copy then shares one set of notes,
wherever it is opened from:
data-page="/reviews/budget-2026.html"
data-page="/3f2b8c1e-9a4d-4c7b-8e21-5d6f0a1b2c3d"
The value is yours to choose, written like a path: it
starts with one /, has no space and no ...
Anything else is refused on screen rather than corrected. Give it the path
of a page that is online and the copy shares that page’s notes.
Three things to know. Put it on a single document,
never in a template shared by a site — every page would become the
same page. Notes written before you added it stay under the old path. And a
file opened from a disk has no site address, so a project restricted to a
list of sites refuses it; it needs a project without that list.
What language does it speak?
The tag on your pageYour site
Notes: any language. The tool encrypts them and
never reads them. Its own buttons and messages ship in English and
French.
The panel follows the page. A page declaring
<html lang="fr"> (or fr-CA) gets it in
French with nothing to add, and a French WordPress already declares it.
Any other language stays English. The browser’s language is
ignored on purpose: two people looking at one page see the same
panel.
To force a language, change a word or add your own, point
the tag at a label file. It always wins, and a copy of the client you
host yourself needs it; a label it lacks falls back on English:
data-labels="https://cdn.jsdelivr.net/npm/annotepage-client@2/labels/fr.js"