Privacy Policy

Last updated: 2026-10-04

Annotated is a website and a browser extension. The website is the Next.js app at https://annotated-web.vercel.app. The extension is the Annotated browser extension for Chrome and Microsoft Edge. This page describes what those programs read, what they save, and which other services receive it.

Sale of data

We do not sell your data.

Accounts

You sign in with Google or with X. Sign-in is handled by Supabase Auth. The project turns on those two providers and does not set custom OAuth scopes. A store-review account can also sign in with an email and password. That form is not offered on the public sign-in page. It creates the same session described below.

When an account is created, a database trigger makes a public profile from the sign-in record:

  • A handle, taken from the provider username when one is present, otherwise from the part of the email address before the @ sign.
  • A display name, taken from the provider name when one is present, otherwise the handle.
  • An avatar URL, taken from the provider photo when one is present. The browser loads that image from the host in the URL. The app treats googleusercontent.com, pbs.twimg.com, and abs.twimg.com as provider image hosts.

The profile also stores follower and following counts, an admin flag, and the time it was created. The email address stays on the Supabase auth user. It is not a column on the public profile. The profile table has a bio column of up to 280 characters. No screen in this repository saves a bio.

The website keeps the sign-in session in cookies. The extension keeps it in the browser's extension storage: access token, refresh token, expiry time, user id, email, and handle. The application code does not send posts to Google or X.

Light or dark theme is saved in the browser under the local storage key annotated-theme, on the website and in the extension. It is not sent to the server.

What is saved when you publish

Publishing an annotation requires you to be signed in. The website stores, on that annotation:

  • The source page address and the kind of source: YouTube, audio, video, or article.
  • The title, author, and publish time, when the extension read them from the page.
  • Text you highlighted on the page, up to 100 words.
  • Commentary you typed, up to 5,000 characters.
  • For a media clip, the start and end times. A clip cannot be longer than 90 seconds. For YouTube, the video id is stored as well.
  • A commentary audio file, if you recorded one.
  • A picture of the page, for a post that is not an audio or video clip.

Annotations with status active or disputed can be read by anyone, including the source address, highlighted text, commentary, and transcript. Files in the storage buckets named clips, commentary-audio, article-screenshots, and avatars can be read by anyone who has the file address.

Highlighted text

The extension reads the text currently highlighted on the page and shows it in the side panel. That text is saved on your annotation only when you publish, and only the first 100 words are kept.

Page screenshots

When you publish a post that is not an audio or video clip — an article, or a page where you removed the media — the extension captures the visible tab as a JPEG and sends it with the annotation. The website stores that image in the article-screenshots bucket. A YouTube, audio, or video clip does not take this capture.

If the capture does not return an image, and the page exposed a preview image, the extension sends that image's address and the website downloads it. If there is still no image, and the post is an article, the website fetches the source page looking for a preview image. If that fails, it sends the source address to Microlink at https://api.microlink.io/ and asks for a screenshot, then stores the image it gets back.

A still frame of a video, used as a preview inside the side panel, is not uploaded.

Browser extension and page access

The extension does not keep a list of pages you visit. Extension storage holds the sign-in session described above, not a browsing history.

Opening the side panel reads the active tab. The Clip tab is shown first. While that tab is open, the extension reads the active tab again when you switch tabs, when a tab's address changes or the tab finishes loading, and when the browser window gains focus. While the Clip tab is open it also asks the page for the media playback position about twice a second.

The "This page" tab, while it is open, reads the active tab the same way. It sends that page address to the website, plus the canonical address or YouTube video id when it has them, to list annotations for that source. It skips addresses that start with chrome://, edge://, or chrome-extension://.

Reading a page is not limited to the moment you open the sidebar, and it is not limited to the sidebar being open if you grant access to all websites:

  • Required host permissions are the Annotated site (https://annotated-web.vercel.app/*) and https://*.supabase.co/*, so commentary audio can be uploaded. Development builds also include localhost.
  • The extension has the activeTab permission, which applies to the tab you are on when you click the Annotated toolbar icon. It does not have the tabs permission, so install and update do not warn that it can read your browsing history. The side panel reads the address from that active-tab grant or from the page detector after you allow a site. If the address is not available, the panel says to click the Annotated icon. The extension does not upload a browsing history.
  • Optional host permissions are http://*/* and https://*/*. They are not requested when you install. The side panel asks with a button labeled "Allow access to websites you visit." If you allow it, the extension registers a content script on all http and https pages and keeps that registration across browser sessions. The script runs when those pages load, including while the side panel is closed. It reads the page address, title, author, canonical link, publish-date metadata, preview-image address, highlighted text, and audio or video on the page, including the media address, current time, duration, and a JPEG still of a video frame when the page allows it. It passes that to the extension. If the side panel is not open, that information is not uploaded.
  • The same content script is installed by default on https://annotated-web.vercel.app, so it runs there without the optional permission. Development builds also install it on localhost.
  • Page highlights stay off until you choose Most liked or People I follow. That choice asks Chrome for the site you are on, such as https://example.com/*, and the prompt names that site. It does not ask for every website. Highlights then run only on sites you enable that way. An earlier grant for all websites, or a grant for a different site so a clip can be detected, does not turn highlights on there. If you allow the site, the extension registers its highlight script on that site and sends the page origin and path to list annotations. On YouTube it also sends the v parameter. It may send a canonical URL when that URL is public and on the same site. People I follow does not ask for access while you are signed out. The page script asks the background whether you are signed in. It does not read the session token from extension storage.

Page contents are sent to Annotated's servers when you publish an annotation, and the page address is sent when the "This page" tab looks up annotations for the tab you are viewing.

Microphone and commentary audio

Recording starts when you click "Record audio." The extension uses the microphone from a hidden offscreen page and keeps the recording in the side panel until you publish or discard it. After you stop, the extension closes that offscreen page. On publish, the website issues a signed upload address and the extension sends the audio file (audio/webm) directly to Supabase Storage, in the commentary-audio bucket. That recording is not sent to the speech-to-text services below.

Clips and transcripts

Audio and video annotations, other than YouTube playback, are processed by a media worker. The worker is the Fly.io app named annotated-worker, configured for region iad. It downloads the time range you chose with yt-dlp, trims it with ffmpeg, and uploads the clip to the public clips bucket. Video clips also get a JPEG thumbnail in that bucket. The worker deletes its temporary files when the job finishes. Its work directory is /tmp/annotated-worker.

A transcript is then saved on the annotation:

  • For YouTube, the worker first tries English captions from the video through yt-dlp. The website plays the YouTube clip with the YouTube player. The worker does not re-host the YouTube video file. If captions are not available, it downloads that clip's audio and sends the file to speech-to-text.
  • For other audio and video, it sends the stored clip file to speech-to-text.

Speech-to-text goes to xAI at https://api.x.ai/v1/stt when an xAI API key is configured and the provider setting is auto (the default) or xai. The request asks for English text. If the provider is openai, or only an OpenAI key is set, the same audio is sent to OpenAI Whisper at https://api.openai.com/v1/audio/transcriptions (model whisper-1 unless WHISPER_MODEL is set). If neither key is set, transcription is skipped and no transcript is stored.

The transcript text is saved on the annotation and shown on the annotation page.

Additional context

On an annotation page, a signed-in person can ask for additional context. The website then sends the annotation to xAI's chat API at https://api.x.ai/v1/chat/completions. The message includes the source type, title, author, address, and publish time, the highlighted text, the commentary text, and up to 8,000 characters of the transcript. The model is grok-4-1-fast-reasoning unless GROK_AI_CONTEXT_MODEL is set. The reply is stored on the annotation and shown to later visitors. This request runs only when someone asks and no context is stored yet. If the xAI key is not configured, the website returns an error and does not call xAI.

Comments, follows, claims, and other records

  • Comments you write, up to 1,000 characters, are stored and can be read with the annotation.
  • Likes and follows are stored and can be read publicly.
  • Bookmarks are stored and can be read only by the person who saved them.
  • Notifications for a comment, like, follow, claim, or counter-notice are stored for the recipient only.
  • A claim stores the claimant's name, email, claim type, an optional link to the original work, a written reason, and a signature. The claimant, the annotation owner, and admins can read it. You can file a claim without signing in. A signed-out claim is rate-limited by client IP address.
  • A counter-notice stores a statement and a signature from the annotation owner.
  • When an admin records a decision, optional admin notes can be stored on the claim. Those notes are included in the decision email to the claimant when they are present.

Claim and counter-notice email is sent with Resend when a Resend API key is configured. Mail goes to the claimant, and also to the ADMIN_EMAIL address when that setting is present. The From address is RESEND_FROM_EMAIL when set, and otherwise noreply@annotated.com. That is a sending address in the code, not a privacy contact. A claim decision is also emailed to the annotation owner at the email on their Supabase auth user, when that address is present. The owner email does not include the admin notes.

Rate limits for publishing, for additional context, and for claims are stored in a rate_limit_buckets table, keyed by user id or, for a signed-out claim, by client IP. The website reads that IP from the x-vercel-forwarded-for header, or from x-real-ip when the first header is missing.

Discover search sends the search text to the website so it can query annotations. This repository does not define a table that saves those searches.

Services that receive data

  • Supabase — accounts, the database, and file storage. The website uses it. The media worker uses it with a service-role key.
  • Vercel — the website is hosted at https://annotated-web.vercel.app. The app reads the client IP from headers Vercel sets, as described above.
  • Fly.io — hosts the media worker (app annotated-worker, region iad).
  • xAI — speech-to-text of clip audio, and the additional-context chat request, when those calls are configured.
  • OpenAI — speech-to-text of clip audio when that provider is the one configured.
  • YouTube — annotation pages load https://www.youtube.com/iframe_api to play a YouTube clip. The extension loads thumbnail images from i.ytimg.com while you preview a YouTube clip. The worker also requests the source address, including YouTube, when it downloads a clip or captions.
  • Microlink — may receive a source page address when the website needs a fallback screenshot.
  • The website itself may fetch the source page to look for a preview image.
  • Resend — sends the claim emails described above.
  • Google — when a discover card has no preview image, the browser requests a favicon from https://www.google.com/s2/favicons using the source site's hostname.

How long it is kept

The database schema and application code do not set an automatic deletion time for accounts, annotations, audio, screenshots, transcripts, comments, or claims. They remain until they are removed by the actions below.

When a claim is upheld, the annotation status is set to removed. That status is not included in the public read rule. The status change does not delete the row or the stored files.

The rate-limit function sometimes deletes other rate-limit rows that have not been updated for 7 days. That cleanup runs on about one in a hundred rate-limit checks and removes at most 50 rows each time. It does not delete annotations or accounts.

The media worker deletes the temporary files for a job when that job ends.

Deleting your information

You can delete an annotation you own. That removes the annotation row and the stored clip, thumbnail, commentary audio, and page screenshot for it. Comments, likes, bookmarks, claims, counter-notices, notifications, and processing jobs that point at that annotation are removed with it by database foreign keys.

You can delete your own comments. The annotation owner and admins can delete comments on that annotation as well. You can remove your own likes, bookmarks, and follows.

To delete your account, email support@annotated-web.vercel.app from the email address you use to sign in, asking for deletion. We will delete your account and the data saved with it within 30 days.

When that auth user is removed, the profile is removed with it, and annotations owned by that profile are removed with the profile. Removing the auth user does not by itself delete files in storage. Those files are removed when an annotation is deleted, as described above.

Chrome Web Store Limited Use

Data from the extension is used only to provide and improve the extension's single purpose of clipping and annotating web content. It is not sold or transferred to third parties except to the processors listed on this page as needed to provide the features (hosting, storage, speech-to-text, and preview images), or as required by law. It is not used or transferred for purposes unrelated to that single purpose. It is not used for creditworthiness or lending decisions. Humans do not read this user data except with your consent, for security, to comply with law, or when needed for support.

Contact

For privacy questions and requests, email support@annotated-web.vercel.app.