# Introduction

> Point at the bug. That's the whole product.

Source: https://docs.usejot.co/docs

Jot is click-to-pin feedback for live web apps. Someone clicks the broken thing
on a real page and types one sentence. The comment stays **pinned to that
element**, and whoever fixes it gets everything they need to reproduce it.

## Two people, one product

<Cards>
  <Card title="The reporter" href="/docs/reviewers/pinning">
    A PM, a founder, QA, a client. Clicks the button that does nothing and writes "this does
    nothing". Never opens a dashboard, never reads these docs.
  </Card>

  <Card title="The fixer" href="/docs/developers/inbox">
    A developer, or an AI agent. Gets the screenshot, the CSS selector, the URL, the browser and
    viewport, and — when the reporter recorded — a replay with console and network.
  </Card>
</Cards>

Every report carries:

* a **screenshot** of the page, with sensitive fields painted over in the browser
* the **selector** of the element that was clicked, and where on it
* the **URL**, browser, OS and viewport
* the **environment** it came from, if you tag one (`staging`, `preview-142`)
* an optional **session replay** with console errors and failed requests

## How it fits together

1. You add one script tag to your app. [Quickstart →](/docs/quickstart)
2. Reviewers open the widget on the live page and pin comments.
3. Reports land in your inbox in real time. You triage, fix, and resolve.
4. The reporter sees the fix arrive on their pin, and approves it.

Only the reporter approves. Resolved means "we think it's fixed"; approved means
"the person who found it agrees".

## Plans

Reviewers are free and unlimited on every plan — you never pay for the people
giving you feedback.

| Plan   | Price    | Projects  |
| ------ | -------- | --------- |
| Free   | $0       | 1         |
| Team   | $29 / mo | 5         |
| Agency | $79 / mo | Unlimited |

Issue tracker integrations and MCP agents start on Team.


---

# Quickstart

> From nothing to your first pinned comment in two minutes.

Source: https://docs.usejot.co/docs/quickstart

<Steps>
  <Step>
    ### Create a project

    Sign in at [app.usejot.co](https://app.usejot.co) and create a project for the
    site you want feedback on. A project is one app — staging and production can
    share it, told apart by [environment](/docs/widget#environments).
  </Step>

  <Step>
    ### Add the script

    Paste this before the closing `</body>` tag. **Project → Settings** shows it
    with your real key filled in:

    ```html
    <script
      src="https://cdn.jsdelivr.net/npm/@usejot/widget@1/dist/jot.js"
      data-jot-key="jot_pk_your_key"
    ></script>
    ```

    No build step and no framework requirement. Prefer a package?
    See [React](/docs/install/react), [Next.js](/docs/install/nextjs),
    [Vue](/docs/install/vue) and the rest under [Install](/docs/install/script).
  </Step>

  <Step>
    ### Check it is live

    Reload your page. A round Jot button appears on the right edge. The first
    time the page loads, the dashboard checklist ticks off **Install the widget**.
  </Step>

  <Step>
    ### Leave the first Jot

    Click the button, choose **Jot**, and click anything on the page. Type one
    sentence and send. It is in your inbox before you switch tabs.
  </Step>
</Steps>

## Who can comment

Each project picks one, in **Project → Settings → Who can leave Jots**:

* **Members only** (default) — reviewers sign in inside the widget with a
  6-digit code we email them. Anyone in your workspace can sign in.
* **Anyone on the page** — no account needed. Visitors only ever see their own
  pins.

## Next

<Cards>
  <Card title="Invite your reviewers" href="/docs/team" />

  <Card title="Tour the inbox" href="/docs/developers/inbox" />

  <Card title="Connect an issue tracker" href="/docs/integrations/linear" />

  <Card title="Hand reports to an agent" href="/docs/ai/mcp" />
</Cards>


---

# Script tag

> Any site, any stack — one tag before </body>.

Source: https://docs.usejot.co/docs/install/script

Works on anything that renders HTML: server-rendered apps, static sites, CMS
templates, the jQuery app nobody wants to touch.

```html
<!doctype html>
<html>
  <body>
    <!-- your page -->

    <script
      src="https://cdn.jsdelivr.net/npm/@usejot/widget@1/dist/jot.js"
      data-jot-key="jot_pk_your_key"
    ></script>
  </body>
</html>
```

Copy the exact tag from **Project → Settings** — it has your key in it.

## Versions

`@1` always serves the newest 1.x release, so fixes reach your site without
you changing anything. To approve every change yourself, pin an exact version
and bump it when you choose:

```html
<script
  src="https://cdn.jsdelivr.net/npm/@usejot/widget@1.0.0/dist/jot.js"
  data-jot-key="jot_pk_your_key"
></script>
```

The widget is also on npm as [`@usejot/widget`](https://www.npmjs.com/package/@usejot/widget)
if you would rather serve the files yourself.

## Options

Every option is a `data-jot-*` attribute on the same tag:

| Attribute            | Default | What it does                                                |
| -------------------- | ------- | ----------------------------------------------------------- |
| `data-jot-key`       | —       | **Required.** Your project key                              |
| `data-jot-trigger`   | `fab`   | The round Jot button (`fab`) or a vertical edge tab (`tab`) |
| `data-jot-edge`      | `right` | Which side the trigger sits on: `left` or `right`           |
| `data-jot-offset`    | `50`    | Vertical position, as a percent of the viewport height      |
| `data-jot-env`       | —       | Tags every report with an environment name                  |
| `data-jot-autostart` | `true`  | `false` waits for your code to call `Jot.init()`            |
| `data-jot-internal`  | —       | `true` hides the widget from ordinary visitors              |

The full list, with what each one does in detail, is in the
[widget reference](/docs/widget).

## What it costs your page

The tag loads a loader of about a kilobyte. The rest of the widget downloads
only when someone opens it, and the recorder only when someone presses
**Record**. A visitor who never opens Jot pays for the loader and nothing else.

The widget renders inside a shadow root, sits out of the page flow, and never
touches your styles — zero layout shift.

## Check it works

Reload the page. The Jot button appears on the right edge, and your dashboard's
setup checklist ticks off **Install the widget**.


---

# React

> One component, or one hook.

Source: https://docs.usejot.co/docs/install/react

<Callout title="Packages are on their way to npm">
  Until `@usejot/react` is published, the [script tag](/docs/install/script) in your `index.html`
  does the same job today.
</Callout>

```bash
npm install @usejot/react
```

```tsx title="src/main.tsx"
import { JotWidget } from "@usejot/react";

<App />
<JotWidget projectKey="jot_pk_your_key" />
```

`JotWidget` renders nothing. It loads the same script the plain tag does, and
removes the widget when it unmounts. StrictMode's double mount is safe — a
second mount is ignored.

Prefer a hook? `useJot(config)` does the same from inside any component.

## Options

Every [script option](/docs/install/script#options) is a prop: `trigger`,
`edge`, `offset`, `environment`, `autostart`.

```tsx
<JotWidget projectKey="jot_pk_your_key" environment="staging" edge="left" />
```

## Only on some deploys

Most teams want Jot on staging and preview deploys, not in front of every
customer. Render it behind whatever flag you already use:

```tsx
function Feedback({ show }: { show: boolean }) {
  return show ? <JotWidget projectKey="jot_pk_your_key" /> : null;
}
```

## Wait for consent

If your cookie banner must run first, hold the widget back and start it once
consent is granted:

```tsx
<JotWidget projectKey="jot_pk_your_key" autostart={false} />;

// later, when your consent tool says yes:
window.Jot?.init();
```


---

# Next.js

> App Router and Pages Router.

Source: https://docs.usejot.co/docs/install/nextjs

<Callout title="Packages are on their way to npm">
  Until `@usejot/next` is published, use `next/script` as shown [below](#without-the-package).
</Callout>

```bash
npm install @usejot/next
```

```tsx title="app/layout.tsx"
import { JotWidget } from "@usejot/next";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <JotWidget projectKey="jot_pk_your_key" />
      </body>
    </html>
  );
}
```

`JotWidget` is a client component, so it is safe in a server layout. For the
Pages Router, put the same component in `pages/_app.tsx`.

Every [script option](/docs/install/script#options) is a prop: `trigger`,
`edge`, `offset`, `environment`, `autostart`.

## Without the package

`next/script` with `lazyOnload` fetches the loader after everything of yours
has loaded — the right trade for a feedback widget.

```tsx title="app/layout.tsx"
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://cdn.jsdelivr.net/npm/@usejot/widget@1/dist/jot.js"
          data-jot-key="jot_pk_your_key"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}
```

## Only on some deploys

Most teams want Jot on staging and preview deploys, not production. Render it
behind whatever flag you already use, and tag reports with the environment so
they never mix:

```tsx
function Feedback({ show }: { show: boolean }) {
  return show ? <JotWidget projectKey="jot_pk_your_key" environment="preview" /> : null;
}
```


---

# Vue

> A plugin, installed once in main.ts.

Source: https://docs.usejot.co/docs/install/vue

<Callout title="Packages are on their way to npm">
  Until `@usejot/vue` is published, the [script tag](/docs/install/script) in your `index.html` does
  the same job today.
</Callout>

```bash
npm install @usejot/vue
```

```ts title="src/main.ts"
import { createJot } from "@usejot/vue";

app.use(createJot({ projectKey: "jot_pk_your_key" }));
```

Prefer a component? Mount the renderless `JotWidget` anywhere in your tree —
it loads the widget on mount and removes it on unmount.

```vue title="src/App.vue"
<script setup lang="ts">
import { JotWidget } from "@usejot/vue";
</script>

<template>
  <RouterView />
  <JotWidget :config="{ projectKey: 'jot_pk_your_key' }" />
</template>
```

Every [script option](/docs/install/script#options) is a config key: `trigger`,
`edge`, `offset`, `environment`, `autostart`.

Using Nuxt? Use [`@usejot/nuxt`](/docs/install/nuxt) instead.


---

# Nuxt

> A module, configured once in nuxt.config.

Source: https://docs.usejot.co/docs/install/nuxt

<Callout title="Packages are on their way to npm">
  Until `@usejot/nuxt` is published, add the [script tag](/docs/install/script) through
  `app.head.script` — it works today.
</Callout>

```bash
npm install @usejot/nuxt
```

```ts title="nuxt.config.ts"
export default defineNuxtConfig({
  modules: [["@usejot/nuxt", { projectKey: "jot_pk_your_key" }]],
});
```

The module registers a client-only plugin: nothing runs during server
rendering, and the widget loads after hydration.

Every [script option](/docs/install/script#options) is a module option:
`trigger`, `edge`, `offset`, `environment`, `autostart`.

## Wait for consent

Pass `autostart: false`, then call `window.Jot?.init()` once your cookie banner
grants consent.


---

# Svelte

> One component in your root layout.

Source: https://docs.usejot.co/docs/install/svelte

<Callout title="Packages are on their way to npm">
  Until `@usejot/svelte` is published, the [script tag](/docs/install/script) in `app.html` does the
  same job today.
</Callout>

```bash
npm install @usejot/svelte
```

```svelte title="src/routes/+layout.svelte"
<script>
  import { JotWidget } from "@usejot/svelte";
</script>

<slot />
<JotWidget config={{ projectKey: "jot_pk_your_key" }} />
```

The component mounts the widget in `onMount`, so it never runs during
SvelteKit's server render, and removes it when the layout unmounts.

Every [script option](/docs/install/script#options) is a key of `config`: `trigger`,
`edge`, `offset`, `environment`, `autostart`.


---

# Angular

> A provider at bootstrap, or a function in a component.

Source: https://docs.usejot.co/docs/install/angular

<Callout title="Packages are on their way to npm">
  Until `@usejot/angular` is published, the [script tag](/docs/install/script) in `index.html` does
  the same job today.
</Callout>

```bash
npm install @usejot/angular
```

```ts title="src/main.ts"
import { provideJot } from "@usejot/angular";

bootstrapApplication(AppComponent, {
  providers: [provideJot({ projectKey: "jot_pk_your_key" })],
});
```

## From a component

`jotInit(config)` mounts the widget and returns a cleanup function. Call it in
`ngOnInit`, clean up in `ngOnDestroy`:

```ts
import { jotInit } from "@usejot/angular";

export class AppComponent implements OnInit, OnDestroy {
  private cleanup = () => {};

  ngOnInit() {
    this.cleanup = jotInit({ projectKey: "jot_pk_your_key" });
  }

  ngOnDestroy() {
    this.cleanup();
  }
}
```

Every [script option](/docs/install/script#options) is a config key: `trigger`,
`edge`, `offset`, `environment`, `autostart`.


---

# Pinning a comment

> Click the broken thing. Say what's wrong. Send.

Source: https://docs.usejot.co/docs/reviewers/pinning

You don't need an account, a browser extension, or a bug template. You need the
page and one sentence.

<Steps>
  <Step>
    ### Open Jot

    Click the round Jot button on the edge of the page — or the **FEEDBACK** tab, if
    the site uses one. The first time, Jot asks whether you're a guest or a team
    member. Pick one; it remembers.
  </Step>

  <Step>
    ### Choose Jot

    The Jot tools appear. Press **Jot**. Your cursor becomes
    a crosshair and a hint reads *Jot this element*.
  </Step>

  <Step>
    ### Click the problem

    Click exactly what is wrong — a button, a price, a typo, an empty space where
    something should be. Disabled buttons work too. Clicking a link here never
    navigates away. Press <kbd>Esc</kbd> to cancel.
  </Step>

  <Step>
    ### Say what's wrong

    A small composer opens right there, asking *What's wrong here?* One sentence is
    enough. Optionally tag it — **bug**, **ui**, **copy**, **content**,
    **performance**, **accessibility** — and press **Send**.
  </Step>
</Steps>

Your pin stays on the element you clicked. Everyone who can see it sees it in
the same place, on any screen size.

## What gets sent

The composer tells you before you send: *Screenshot & selector attached.
Sensitive fields masked before anything leaves your browser.*

With your sentence, Jot sends a screenshot of the page, which element you
clicked, the URL, and your browser and screen size. Form fields you have typed
into and anything the site marks as private are painted over **in your browser**
before the screenshot is uploaded.

## Seeing other pins

Press **All** in the dock to list the Jots on this page — newest first,
searchable. Click one to jump to its pin.

* Team members see every pin on the page.
* Guests see only their own. Nobody else's name or report is ever shown to a guest.

If you want a pin off your screen for a moment, **More → Hide Jots** in the list
hides them all.

## In a mobile or desktop app

Apps built with Flutter or React Native work the same way, with taps instead of
clicks: tap the Jot button, tap **Jot**, tap the problem, and say what's wrong.
Your pin stays on the element you tapped, on any phone, tablet or window size.
Instead of the URL, Jot sends the screen you were on, the device and the app
version. Password fields and anything the app marks as private are blacked out
**on the device**, before the screenshot exists.

On a phone, Jot opens as a sheet from the bottom of the screen. On a tablet or
desktop, cards open beside the pin.


---

# Annotating the screenshot

> An arrow, a box, or a blur — when a sentence needs a finger.

Source: https://docs.usejot.co/docs/reviewers/annotating

Every Jot carries a screenshot. When the problem is "this, next to that", draw
on it.

1. In the composer, press **Annotate** on the screenshot thumbnail.
2. Pick a tool:
   * **Arrow** — point at something.
   * **Box** — outline an area.
   * **Blur** — hide anything you'd rather not share.
3. Press **Done**. The count on the button tells you how many marks you made.

**Undo** takes back the last mark; **Clear** removes them all; **Cancel** leaves
the screenshot as it was.

Areas Jot has already hidden for you — form fields and private elements — are
marked **auto-masked**. You can't unmask them; that is on purpose.


---

# Recording a session

> Show the bug instead of describing it.

Source: https://docs.usejot.co/docs/reviewers/recording

Some bugs only happen after three clicks in the right order. Record them. The
developer gets a replay of the page with every console error and failed request
lined up beside it.

<Steps>
  <Step>
    ### Press Record

    In the dock, press **Record**. Recording covers **this page only** — never
    other tabs, never your desktop, and it never asks to share your screen.
  </Step>

  <Step>
    ### Do the thing

    Use the page as you normally would until the bug appears. The recording bar
    counts errors and failed requests as they happen.
  </Step>

  <Step>
    ### Stop & describe

    Press **Stop & describe**. A composer opens with the recording already attached
    — *Recording attached · 42s*. Tell the team what happened and press
    **Send with recording**.
  </Step>
</Steps>

<Callout type="warn">
  A recording only reaches the team through a Jot. If you close the composer without sending, the
  recording is discarded.
</Callout>

## What is recorded

Not video — the page's structure and changes, replayed in the developer's
browser. Every input field is masked before anything leaves your browser, and
passwords are always masked. See [Privacy & masking](/docs/privacy) for the
details.

Recording is the one part of Jot that downloads extra code. Pages where nobody
presses **Record** never pay for it.

## In a mobile or desktop app

In apps built with Flutter or React Native, **Record** and **Stop & describe**
work the same way. The recording shows the app's own screen — never other apps
— with the console, network calls, screen changes and taps lined up beside it.
Taps are positions only; what you type is never recorded. Password fields and
anything the app marks as private are blacked out on the device, before the
recording exists. Jot keeps the last two minutes.

If the app crashes, Jot asks the next time it opens whether to send the crash to
the team. Say yes and it arrives as a **Bug** Jot, with whatever was recorded.


---

# Threads & replies

> Every pin is a conversation, on the page it is about.

Source: https://docs.usejot.co/docs/reviewers/threads

Click any pin to open its thread: the report, its status, and every reply.
Replies from the team, from the dashboard, or from an AI agent appear on the pin
as they are written — no reload.

Each reply shows who wrote it. Agent replies are marked **Agent**, so you always
know when a person answered.

## Replying

Type in **Reply…** at the bottom of the thread and send.

* Signed-in reviewers can **@-mention** a teammate. They get an email.
* Guests can reply to guest threads, but not to a team member's thread.

## Getting notified

When someone replies to your report, Jot emails you. When the team resolves it,
you get a **Fixed:** email with a button to check the fix.

To change what you hear about, open **All**, then **More → Notify me about**:
**Everything**, **Mentions and replies**, or **Nothing**.

Guests who left no email can't be notified — come back to the page to see the
thread.

## Sharing a Jot

**More → Copy link to this Jot** in any thread copies a link to the Jot in the
team's dashboard — handy for pasting into a chat with the developer.


---

# Approving a fix

> The team says it's fixed. You say whether it is.

Source: https://docs.usejot.co/docs/reviewers/approving

When the team ships a fix, they mark your Jot **Resolved** and you get an email.
Resolved means "we think it's fixed". It is not the end.

1. Open the page and click your pin.
2. Check the thing you reported.
3. If it's fixed, open **More** in the thread and choose **Approve**.

**Approved** is final. It tells the team the person who found the problem
agrees it is gone.

<Callout>
  Only the reporter approves. Not the developer who fixed it, not an AI agent — you.
</Callout>

## Not fixed?

Reply on the thread and say what you still see. The team can reopen it from
their inbox.


---

# Signing in

> A 6-digit code, typed straight into the widget.

Source: https://docs.usejot.co/docs/reviewers/sign-in

Some projects are for invited people only. On those, Jot asks you to sign in
before you can leave a Jot. There's no password and no link to click — you
never leave the page.

<Steps>
  <Step>
    ### Enter your work email

    Type the email you were invited with and press **Email me a code**.
  </Step>

  <Step>
    ### Type the code

    Jot emails you a 6-digit code. Type it into the widget and press **Sign in**.
  </Step>
</Steps>

Codes expire after 10 minutes and stop working after 5 wrong attempts. No
email? Press **Resend code**, or **Different email** if you typed the wrong one.

If the project has Google sign-in turned on, **Continue with Google** appears
as well.

## "You have not been invited to this project"

Jot only lets in people who are part of the team's workspace. Ask whoever sent
you the page to invite the exact address you typed, and to make sure you
accepted the invitation.

## Team members on public pages

On projects open to anyone, you can still sign in: choose
**I'm a team member — sign in**. You keep your name on your Jots, see every pin
on the page, and can @-mention teammates.

**Sign out** is in the dock.


---

# The inbox

> Every report, with its repro attached, the moment it is sent.

Source: https://docs.usejot.co/docs/developers/inbox

Each project has an inbox. A new Jot appears in it while the reviewer is still
looking at the page — no reload, no polling.

## A Jot, opened

Click a row to open its detail panel:

* the **screenshot**, with sensitive fields already masked
* **url**, **selector**, **browser**, **viewport**, **device**, and **env** if
  the page was tagged with an [environment](/docs/widget#environments)
* the **recording**, when there is one — **Watch replay** opens it with the
  console and network panels beside the page
* the whole thread, with replies from people, the team and agents

Reply from here and the reviewer sees it on their pin straight away. **File
issue** sends it to your [issue tracker](/docs/integrations/linear).

## Copy as Markdown

**Copy as Markdown** in the detail panel puts the whole report on your
clipboard — the sentence, the context, the thread — ready to paste into a pull
request, a chat, or an AI assistant.

## Triage

From the list, without opening anything:

* **Status** — change it on the row. See [statuses](/docs/developers/statuses).
* **Assign** — give a Jot to a teammate.
* **Snooze** — hide it for now. The **Snoozed** view keeps them.

Press <kbd>/</kbd> to search the text of every Jot.

## Filters and views

Filter by status — **All**, **Open**, **In progress**, **Resolved**,
**Approved** — or by label, assignee and environment.

Three views are always there: **Mine**, **Snoozed**, and **By page**, which
groups Jots by the URL they were left on. To keep a set of filters, choose
**Save this view** and name it. Saved views live in your browser.

## Layouts

Switch between **List** and **Board** at the top of the inbox. The board is
covered [here](/docs/developers/board).


---

# Statuses

> Four steps, and the last one belongs to the reporter.

Source: https://docs.usejot.co/docs/developers/statuses

| Status          | Means                                    | Who sets it               |
| --------------- | ---------------------------------------- | ------------------------- |
| **Open**        | Reported, nobody has picked it up        | New Jots start here       |
| **In progress** | Someone is on it                         | Your team, or an AI agent |
| **Resolved**    | A fix has shipped — "we think it's done" | Your team, or an AI agent |
| **Approved**    | The reporter checked and agrees          | **The reporter**          |

## Only the reporter approves

Resolved is your word. Approved is theirs. When you resolve a Jot, the reporter
gets an email asking them to check, and approves it from the widget. That is how
you know a fix fixed the thing the person actually saw.

AI agents can move a Jot to **In progress** or **Resolved**, never to
**Approved**.

## Moving between them

* **Open** → In progress, or straight to Resolved.
* **In progress** → back to Open, or on to Resolved.
* **Resolved** → Approved by the reporter, or reopened to Open or In progress
  if it isn't fixed after all.
* **Approved** is final.

On the reporter's page, resolved and approved pins shrink to small dots — still
clickable, no longer in the way.


---

# Labels

> What a report is about.

Source: https://docs.usejot.co/docs/developers/labels

A label says what kind of problem a Jot is. Reviewers pick one when they
send.

| Label             | For                                                 |
| ----------------- | --------------------------------------------------- |
| **Bug**           | Something is broken                                 |
| **UI**            | It works, but looks wrong                           |
| **Copy**          | A word, a typo, a tone                              |
| **Content**       | An image, a price, a fact                           |
| **Performance**   | Slow, janky, stuck                                  |
| **Accessibility** | Can't be reached by keyboard, screen reader, or eye |
| **Question**      | Not a problem yet — someone wants to know           |
| **Enhancement**   | A suggestion, not a defect                          |

The widget offers the first six. A recorded session is labelled **Bug** unless
the reviewer changes it.

Filter the inbox by label to work through one kind at a time.

<Callout>
  Labels describe the report. They are not the job titles people carry in your workspace — those are
  [member labels](/docs/team#job-label--who-you-are), and the two never mix.
</Callout>


---

# Board view

> The inbox as four columns. Drag to change status.

Source: https://docs.usejot.co/docs/developers/board

Choose **Board** at the top of the inbox. Every Jot becomes a card in one of
four columns: **Open**, **In progress**, **Resolved**, **Approved**.

Drag a card to another column to change its status. The reviewer's pin updates
as you drop it.

The **Approved** column is marked *reporter only*. Cards arrive there when the
person who reported them approves the fix.

Filters and saved views apply to the board the same way they apply to the list.


---

# Team & roles

> What someone may do, and who they are. Two different things.

Source: https://docs.usejot.co/docs/team

Jot keeps apart two things most tools squash into one field. A QA engineer can
be an admin. A founder can be a plain member.

## Role — what you may do

| Role       | Can                                                        |
| ---------- | ---------------------------------------------------------- |
| **Owner**  | Everything, including billing and deleting the workspace   |
| **Admin**  | Invite and remove people, create projects, change settings |
| **Member** | Comment, reply, triage, resolve                            |

A workspace always keeps at least one owner; the last one can't be demoted.

## Job label — who you are

**Founder** · **Product manager** · **Designer** · **Developer** ·
**QA engineer** · **Marketing** · **Support** · **Client** · **Stakeholder** ·
**Other**

The label sits next to someone's name on every Jot they file, so a developer can
tell a QA engineer's reproducible report from a stakeholder's opinion at a
glance. It changes nothing about what they are allowed to do.

## Inviting people

**Settings → Team**: enter an email, pick a role and a label, send. The
invitation link is tied to that address and expires after 14 days — it can't be
forwarded to someone else.

Reviewers are free and unlimited on every plan, including Free.

## Who can leave Jots

Each project picks one, in **Project → Settings → Who can leave Jots**:

* **Members only** (default) — reviewers sign in inside the widget with a
  6-digit code. The people who can sign in are exactly your workspace members;
  there is no second list to keep.
* **Anyone on the page** — no account needed. Visitors can leave their name and
  email if they want to hear back, and only ever see their own pins.

Turn on **Internal-only mode** to keep the widget for your team alone, even on a
public page. See [the widget reference](/docs/widget#internal-only).


---

# Linear

> File Jots as Linear issues, and hear back when they close.

Source: https://docs.usejot.co/docs/integrations/linear

<Callout>
  Issue trackers are available on the Team and Agency plans.
</Callout>

<Steps>
  <Step>
    ### Connect your workspace

    A workspace admin opens **Settings → Integrations** and presses
    **Connect Linear**. Approve access in Linear; the card shows **● connected**.
  </Step>

  <Step>
    ### Point a project at a team

    In the project, open **Integrations → Issue tracker**. Choose a team, and
    optionally the state new issues start in (**Default state…**). Press
    **Use Linear**.
  </Step>
</Steps>

A project files into one tracker at a time.

## What happens

* **File issue** on any Jot creates a Linear issue with the sentence, the page
  URL, the selector, and a link back to the Jot.
* Moving the Jot to **In progress** or **Resolved** in Jot moves the issue too.
* When the issue is closed in Linear, Jot shows it on the Jot and asks the
  reporter, on their thread, whether that fixes it. Approving is still theirs to
  do.


---

# Jira

> File Jots as Jira issues, and hear back when they close.

Source: https://docs.usejot.co/docs/integrations/jira

<Callout>
  Issue trackers are available on the Team and Agency plans.
</Callout>

<Steps>
  <Step>
    ### Connect your workspace

    A workspace admin opens **Settings → Integrations** and presses
    **Connect Jira**. Approve access in Atlassian; the card shows **● connected**.
  </Step>

  <Step>
    ### Point a project at a Jira project

    In the project, open **Integrations → Issue tracker**. Choose a project, and
    optionally a sprint (**Active sprint / backlog…**) — leave it empty to file into
    the backlog. Press **Use Jira**.
  </Step>
</Steps>

A project files into one tracker at a time.

## What happens

* **File issue** on any Jot creates a Jira issue with the sentence, the page
  URL, the selector, and a link back to the Jot.
* Moving the Jot to **In progress** or **Resolved** in Jot transitions the
  issue too.
* Jot checks Jira every few minutes. When the issue is done, Jot shows it on
  the Jot and asks the reporter, on their thread, whether that fixes it.


---

# Trello

> File Jots as Trello cards, with lists that mirror their status.

Source: https://docs.usejot.co/docs/integrations/trello

<Callout>
  Issue trackers are available on the Team and Agency plans.
</Callout>

<Steps>
  <Step>
    ### Connect your workspace

    A workspace admin opens **Settings → Integrations** and presses
    **Connect Trello**. Approve access in Trello; the card shows **● connected**.
  </Step>

  <Step>
    ### Map a board

    In the project, open **Integrations → Issue tracker**. Choose a board, then pick
    the list for each status: **Open**, **In progress**, **Resolved** and
    **Approved**. Press **Use Trello**.
  </Step>
</Steps>

A project files into one tracker at a time.

## What happens

* **File issue** on any Jot creates a card in your **Open** list with the
  sentence, the page URL, the selector, and a link back to the Jot.
* Moving the Jot to **In progress** or **Resolved** in Jot moves the card to the
  matching list.
* When a card reaches your done list in Trello, Jot shows it on the Jot and asks
  the reporter, on their thread, whether that fixes it.


---

# GitHub

> File Jots as GitHub issues, and see their state on the Jot.

Source: https://docs.usejot.co/docs/integrations/github

<Callout>
  Issue trackers are available on the Team and Agency plans.
</Callout>

<Steps>
  <Step>
    ### Connect your workspace

    A workspace admin opens **Settings → Integrations** and presses
    **Connect GitHub**. Choose which repositories Jot may use; the card shows
    **● connected**.
  </Step>

  <Step>
    ### Point a project at a repository

    In the project, open **Integrations → Issue tracker**, choose a repository, and
    press **Link**.
  </Step>
</Steps>

A project files into one tracker at a time.

## What happens

* **File issue** on any Jot opens a GitHub issue with the sentence, the page
  URL, the selector, and a link back to the Jot.
* The Jot shows the issue's live state. When the issue is closed, Jot asks the
  reporter, on their thread, whether that fixes it.
* Status changes you make in Jot stay in Jot — GitHub issues are not reopened or
  closed from here.


---

# Microsoft Teams

> Announce new Jots in a channel, with status changes threaded underneath.

Source: https://docs.usejot.co/docs/integrations/teams

<Callout>
  Notifications are available on the Team and Agency plans.
</Callout>

Teams is where people hear about Jots, not where they are tracked. A project can
have an issue tracker and a Teams channel at the same time.

<Steps>
  <Step>
    ### Connect your workspace

    A workspace admin opens **Settings → Integrations** and presses
    **Connect Microsoft Teams**. Approve access with your Microsoft account; the
    card shows **● connected**.
  </Step>

  <Step>
    ### Pick a channel

    In the project, open **Integrations → Notifications**. Choose a team, then a
    channel, and press **Save**.
  </Step>
</Steps>

## What happens

* Each new Jot posts a card in the channel: the sentence, who reported it, the
  environment, the screenshot, and a link straight to it.
* Status changes arrive as short replies under that card — "Resolved by …",
  "Approved by the reporter" — so the channel reads as a history, not a
  firehose.
* If the channel can't be reached, the Jot is still saved. Announcements never
  block a report.


---

# MCP for coding agents

> Let Claude Code or Cursor read reports, fix the code, and reply on the pin.

Source: https://docs.usejot.co/docs/ai/mcp

Jot speaks the Model Context Protocol, so a coding agent can work your feedback
like a teammate: read the report with its selector and recording timeline, fix
the code, reply on the thread, and move the status. The reply appears on the
reporter's pin as it is posted.

<Callout>
  MCP agents are available on the Team and Agency plans.
</Callout>

<Steps>
  <Step>
    ### Mint a token

    Open **Settings → Agents**. Choose the project, name the token after who will
    use it — "Claude Code" — and press **Mint token**.

    Copy it now; it is shown only once. The same page shows a ready-made config
    with the token filled in. An admin can revoke any token later.
  </Step>

  <Step>
    ### Add Jot to your agent

    <Tabs items={["Claude Code", "Cursor"]}>
      <Tab value="Claude Code">
        Save as `.mcp.json` in your project root:

        ```json title=".mcp.json"
        {
          "mcpServers": {
            "jot": {
              "command": "npx",
              "args": ["-y", "@usejot/mcp"],
              "env": {
                "JOT_API_TOKEN": "jot_agent_your_token"
              }
            }
          }
        }
        ```
      </Tab>

      <Tab value="Cursor">
        Save as `.cursor/mcp.json` in your project root:

        ```json title=".cursor/mcp.json"
        {
          "mcpServers": {
            "jot": {
              "command": "npx",
              "args": ["-y", "@usejot/mcp"],
              "env": {
                "JOT_API_TOKEN": "jot_agent_your_token"
              }
            }
          }
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Ask

    > Fetch the open feedback for this project. Fix what you can, reply on each Jot
    > with what you changed, and mark them resolved.
  </Step>
</Steps>

## Tools

Five, on purpose — every extra tool costs an agent accuracy.

| Tool                | Takes                                           | Does                                                                                      |
| ------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `fetch_feedback`    | `projectId`, `status?`, `page?`, `environment?` | Lists Jots: id, status, labels, sentence, URL, selector. `page` takes any URL on the page |
| `get_comment`       | `commentId`                                     | Everything about one Jot: context, thread, and the recording as a text timeline           |
| `post_reply`        | `commentId`, `message`                          | Replies on the thread, marked as an agent reply                                           |
| `update_status`     | `commentId`, `status`                           | Moves a Jot to `IN_PROGRESS` or `RESOLVED`                                                |
| `create_batch_note` | `comment_ids` (up to 50), `message`             | One note across several Jots — "all three were the same bug, fixed in #204"               |

An agent can't watch a video, so `get_comment` turns the recording into a
timeline: console errors and failed requests, each with its timestamp.

## What an agent can't do

* **Approve.** `update_status` refuses `APPROVED`. Closing the loop belongs to
  the person who reported the problem.
* **Leave its project.** A token sees one project and nothing else.


---

# Privacy & masking

> What leaves the browser, what never does, and how to control it.

Source: https://docs.usejot.co/docs/privacy

Read this before you turn on recording, and send it to whoever runs your
security review.

## The guarantee

**Masking happens in the browser, before anything is uploaded.** There is no
moment when unmasked content sits on a server waiting to be cleaned up. If a
field is masked, its plaintext never leaves the visitor's machine.

A test in Jot's own suite checks exactly this against the raw uploaded bytes.

## Masked by default

You don't configure any of this:

* **Every input value.** Recording runs with `maskAllInputs` on.
* **Passwords**, always — even if everything else were unmasked.
* In screenshots, **every form field holding a typed value**, and password
  fields even when empty.

## Marking your own content

Add a class to any element:

| Class        | In recordings                                      | In screenshots |
| ------------ | -------------------------------------------------- | -------------- |
| `jot-mask`   | Text replaced with asterisks, layout kept          | Painted over   |
| `jot-block`  | Not recorded at all — replays as a same-size blank | Painted over   |
| `jot-ignore` | Recorded, but typing in it is not                  | —              |

```html
<section class="jot-block">
  <!-- bank details: never recorded, never in a screenshot -->
</section>

<span class="jot-mask">Ada Lovelace</span>
```

For anything that appears on many pages, add CSS selectors once in
**Project → Settings → Recording privacy** instead. They are masked everywhere,
in recordings and screenshots.

## Screenshots

Every Jot carries a screenshot of the page. Before the image exists as bytes,
the widget paints over everything above, on a canvas in the reporter's browser.
Only the painted-over pixels are uploaded. There is no unmasked copy, in transit
or at rest.

The reporter is told before they send: *Screenshot & selector attached.
Sensitive fields masked before anything leaves your browser.*

## Network requests

A recording keeps the page's `fetch` and `XMLHttpRequest` calls the way the
browser's Network tab shows them: URL, method, status, timing, headers, and the
request and response bodies. Jot's own calls are never recorded.

Before any of it is uploaded, in the visitor's browser:

* **Credential headers are redacted** — `Authorization`, `Cookie`,
  `Set-Cookie`, API-key and CSRF headers, and any header whose name contains
  *token*, *secret*, *session*, *auth*, *key* or *password*. The name is kept;
  the value becomes `[redacted]`.
* **Secret fields in bodies are redacted** — in JSON (at any depth) and form
  bodies, and in the URL's query string: passwords, tokens, secrets, API keys,
  credentials, session ids, card numbers, CVV/CVC, IBAN, SSN, PINs, one-time
  and verification codes.
* **Masked content stays masked** — anything typed into a masked field, or
  shown inside `jot-mask`, `jot-block` or your own selectors, is replaced with
  `[masked]` wherever it appears in a URL, header or body.
* **Only text is kept** — JSON, text, XML and form bodies, up to 16 KB each.
  Files, images, binary data and streams are noted by type, never read.

To keep headers and timing but no bodies at all, turn off **Capture request
and response bodies** in **Project → Settings → Recording privacy**.

## What recording can't capture

Recording copies the page's structure, not the screen. It cannot see:

* other browser tabs
* anything outside the page
* the desktop, notifications, or other apps

It never asks for screen-sharing permission, because it never needs it.

## Keeping a key on your own sites

In **Project → Settings → Allowed domains**, list the hostnames the widget may
run on — `app.acme.com`, or `*.acme.com` for every subdomain. Jot refuses
requests from anywhere else, so a copied project key is useless off your
domains. An empty list allows every site.

## Retention

Recordings are kept for 30 days by default. Change it in
**Settings → General → Recording retention**, anywhere from 1 to 365.

## Your obligations

You are recording your own users. [Using Jot lawfully](/docs/legal/using-jot-lawfully)
covers consent, notices and erasure requests.


---

# Widget reference

> Every option, as a config key and as a data attribute, plus the window.Jot API.

Source: https://docs.usejot.co/docs/widget

## Options

Every option exists twice: as a `data-jot-*` attribute on the
[script tag](/docs/install/script), and as a config key in the
[packages](/docs/install/react). They mean exactly the same thing.

| Config key    | Attribute            | Default   | What it does                                                            |
| ------------- | -------------------- | --------- | ----------------------------------------------------------------------- |
| `projectKey`  | `data-jot-key`       | —         | **Required.** Your project key, from **Project → Settings**             |
| `trigger`     | `data-jot-trigger`   | `"fab"`   | `"fab"` is the round Jot button; `"tab"` is a vertical **FEEDBACK** tab |
| `edge`        | `data-jot-edge`      | `"right"` | Which side the trigger sits on: `"left"` or `"right"`                   |
| `offset`      | `data-jot-offset`    | `50`      | Vertical position, as a percent of the viewport height                  |
| `environment` | `data-jot-env`       | —         | Tags every report with an environment. Trimmed and lowercased           |
| `autostart`   | `data-jot-autostart` | `true`    | `false` keeps the widget unmounted until you call `Jot.init()`          |
| —             | `data-jot-internal`  | —         | `"true"` hides the widget from ordinary visitors — see below            |

```html
<script
  src="https://cdn.jsdelivr.net/npm/@usejot/widget@1/dist/jot.js"
  data-jot-key="jot_pk_your_key"
  data-jot-trigger="tab"
  data-jot-edge="left"
  data-jot-env="staging"
></script>
```

## Environments

`environment` stamps every Jot from that page with a name you choose —
`staging`, `preview-142`, `production`. Filter the inbox by it so production
reports and preview noise never mix. Agents can filter by it too.

## Internal only

`data-jot-internal="true"` keeps the widget invisible to your customers on a
public page:

1. The widget mounts only when the page is opened with `?jot=true` in the URL.
2. The browser remembers that, so later visits stay in. `?jot=false` forgets it.

Pair it with **Internal-only mode** in **Project → Settings → Who can leave Jots**
so Jot also refuses anyone who isn't a signed-in team member.

Calling `Jot.init()` yourself skips the `?jot=true` check — your page's own code
deciding to show the widget is consent enough.

## `window.Jot`

The script defines one global.

| Call            | Does                                                                      |
| --------------- | ------------------------------------------------------------------------- |
| `Jot.init()`    | Mounts the widget. Use with `autostart: false`, e.g. after cookie consent |
| `Jot.open()`    | Opens the widget, as if the trigger was clicked                           |
| `Jot.destroy()` | Removes the widget from the page. `Jot.init()` brings it back             |
| `Jot.version`   | The API version, currently `"1"`                                          |

```js
// after your consent tool says yes:
window.Jot?.init();
```

Mounting twice is harmless — the second call does nothing. The packages call
`Jot.destroy()` for you when their component unmounts.

## What it does to your page

As little as possible:

* Renders inside a **shadow root** — no styles cross in either direction.
* Sits out of the page flow — **zero layout shift**, no new scrollbar.
* Never changes your `<body>` styles and never traps focus while closed.
* Loads in stages: a loader of about a kilobyte up front, the rest when someone
  opens it, the recorder only when someone presses **Record**.

## Privacy classes

| Class        | Effect                                                 |
| ------------ | ------------------------------------------------------ |
| `jot-mask`   | Text masked in recordings, painted over in screenshots |
| `jot-block`  | Left out of recordings, painted over in screenshots    |
| `jot-ignore` | Typing in it is not recorded                           |

More in [Privacy & masking](/docs/privacy).


---

# Troubleshooting

> The usual suspects, each with its actual cause.

Source: https://docs.usejot.co/docs/troubleshooting

## The Jot button doesn't appear

Check, in order:

1. **Is the tag on the page?** View source and look for `jot.js`. Framework
   users: is the component rendered on this route?
2. **Is the key right?** Copy the tag again from **Project → Settings**.
3. **Is this domain allowed?** If the project lists
   [allowed domains](/docs/privacy#keeping-a-key-on-your-own-sites), this
   hostname must be on it. `*.acme.com` covers subdomains.
4. **Is it internal only?** With `data-jot-internal="true"`, the widget only
   appears after visiting the page with `?jot=true`.
5. **Is it waiting for consent?** With `autostart` off, nothing shows until your
   code calls `Jot.init()`.

## "You have not been invited to this project" — but they were

The people who can sign in are your workspace members. On **Settings → Team**,
check the invitation was **accepted** — pending invitations don't count — and
that the reviewer typed that exact address.

## The 6-digit code never arrived

Check spam, then press **Resend code**. Codes expire after 10 minutes. After 5
wrong attempts, request a new one.

## "I recorded, but there's nothing in the inbox"

A recording only reaches the team through a Jot. Pressing
**Stop & describe** opens a composer with the recording attached — if it was
closed without **Send with recording**, the recording was discarded. Record
again.

## The inbox didn't update when someone commented

The inbox updates live. If a new Jot only shows after a reload:

* Check you are looking at the **right project** — each inbox shows one.
* A browser extension or company proxy may be blocking live connections. The
  Jot is saved either way; reloading shows it.

## A guest can't see someone else's pin

That is the privacy model, not a bug. On projects open to anyone, guests only
ever see their own pins. Team members see every pin.

## A pin disappeared

Pins are anchored to the element they were left on. If a deploy removed that
element, the pin hides rather than floating loose. The Jot itself — screenshot,
selector, thread — is still in the inbox.

## "This workspace hit its monthly feedback cap"

The Free plan includes 100 Jots a month. The reviewer's note wasn't saved, and
your team has been told. Upgrade under **Settings → Billing**.


---

# Privacy policy

> How Jot handles personal data, as controller and as processor.

Source: https://docs.usejot.co/docs/legal/privacy-policy

This policy covers two very different relationships, and it is honest about the
difference:

1. **Jot as controller.** When you visit our website, sign up, or use the
   dashboard, we decide why and how your data is processed.
2. **Jot as processor.** When a Jot customer embeds the widget in their app,
   the data the widget captures about *their* end users is processed on that
   customer's instructions. The customer is the controller; we are their
   processor under the [Data Processing Addendum](/docs/legal/dpa).

## Who we are

Where Jot is the controller (1 above), that is **Jot Co**. Privacy questions
and requests: **[privacy@usejot.co](mailto:privacy@usejot.co)**.

If you encountered Jot inside someone else's product — the feedback tab on a
site you were reviewing — **direct any privacy request to the operator of that
site**. They control the data; we act on their instructions. If you contact us
directly we will forward your request to them where we can identify them.

## What we collect, and why

### As controller (our site and dashboard)

| Category      | Examples                                                                       | Purpose                               | Legal basis                        |
| ------------- | ------------------------------------------------------------------------------ | ------------------------------------- | ---------------------------------- |
| Account data  | name, email, password hash, company                                            | providing the service, authentication | contract (Art. 6(1)(b))            |
| Billing data  | plan, invoices — card details go to Creem, our merchant of record, never to us | charging for the service              | contract                           |
| Usage data    | pages visited in the dashboard, feature usage                                  | improving the product                 | legitimate interest (Art. 6(1)(f)) |
| Support data  | emails you send us                                                             | answering you                         | legitimate interest                |
| Security logs | IP address, user agent, sign-in events                                         | abuse and fraud prevention            | legitimate interest                |

### As processor (the embedded widget)

On our customers' instructions, the widget may capture: screen content
**subject to masking** (all inputs masked by default; passwords always),
DOM selectors, page URLs, IP address, user agent, and interaction events
(clicks, scrolls) when session recording is on. Purposes, notices, and legal
bases for this data are the customer's responsibility;
[Using Jot lawfully](/docs/legal/using-jot-lawfully) explains what we require
of them.

## International transfers

Our infrastructure providers are listed on the
[sub-processors page](/docs/legal/subprocessors). Where personal data leaves
the EEA or UK, transfers rely on the European Commission's Standard
Contractual Clauses (SCCs) and, where applicable, the UK Addendum.

## Retention

| Data                             | Kept for                                      |
| -------------------------------- | --------------------------------------------- |
| Session recordings               | 30 days by default (configurable per project) |
| Account and workspace data       | life of the account                           |
| Deleted organisation             | 7-day grace period, then purge begins         |
| Hard purge after deletion        | complete within 30 days                       |
| Backups / point-in-time recovery | beyond use immediately, purged within 35 days |

## Your rights

Where GDPR or similar law applies, you may request access, rectification,
erasure, restriction, portability, and object to processing based on
legitimate interest. Write to **[privacy@usejot.co](mailto:privacy@usejot.co)** and we will respond
within one month. Where we act as processor, we will pass the request to the
controller (our customer) instead of answering it ourselves.

You also have the right to complain to your supervisory authority. We would
prefer you talk to us first, but that is your call, not a condition.

## Cookies and local storage

Jot sets only cookies that are strictly necessary to keep you signed in and to
protect sign-in from forgery, and stores a few interface preferences in your
browser. No advertising or analytics cookies. The full list, with purposes and
durations, is on our [cookie policy](https://usejot.co/cookies).


---

# Terms of service

> The agreement between Jot and its customers, including acceptable use.

Source: https://docs.usejot.co/docs/legal/terms

These terms govern your use of Jot at [usejot.co](https://usejot.co). By
creating an account you agree to them on behalf of yourself and your
organisation.

## Who we are

Jot is operated by **Jot Co** ("**Jot**", "**we**"). Questions about these
terms: **[legal@usejot.co](mailto:legal@usejot.co)**.

## The service

Jot provides a feedback widget, dashboard, API, and MCP server for collecting
and resolving feedback on live web applications, including optional session
recording. We may change or improve the service; we will not materially reduce
its core functionality during a paid term without notice.

## Plans and payment

| Plan   | Price         |
| ------ | ------------- |
| Free   | $0            |
| Team   | $29 per month |
| Agency | $79 per month |

Prices are in US dollars. Paid plans are sold through **Creem**, our merchant
of record: Creem processes the payment, issues the invoice, and calculates and
collects any applicable sales tax or VAT. Card details go to Creem and never
reach Jot's systems. Plan limits are listed on the pricing page at
[usejot.co](https://usejot.co/#pricing).

You can cancel a paid plan at any time; it stays active until the end of the
period you paid for, and does not renew. Refunds follow the
[refund policy](/docs/legal/refund-policy): a full refund within 14 days of
the first payment for a plan.

## Acceptable use

You may **not** deploy the Jot widget — and in particular session recording —
on pages that capture:

* **Payment card data** (PANs, CVVs, cardholder data in scope for PCI DSS)
* **Credentials** beyond what the recorder masks by design
* **Health data.** Jot is not a HIPAA business associate and we do not sign
  BAAs — to be plain: do not use Jot anywhere PHI appears on screen
* **Children's data**, or on services directed at children
* **Special-category data** (Art. 9 GDPR: racial or ethnic origin, political
  opinions, religious beliefs, trade-union membership, genetic or biometric
  data, sex life or orientation)

If a page unavoidably shows such data, you must exclude it with `.jot-block`
or keep the widget off that page entirely.

## Your end users' data

**We never use or train on your end users' data, never sell it, and never
share it** except with the sub-processors needed to run the service. This is a
contractual commitment, not a marketing line — it is incorporated into these
terms and the [DPA](/docs/legal/dpa), and a breach of it is a breach of
contract.

## Your responsibilities

You warrant that you have provided any notices and obtained any consents
required by applicable law before enabling the widget or session recording for
your end users — [Using Jot lawfully](/docs/legal/using-jot-lawfully) shows
how. You will indemnify us against claims arising from your failure to do so,
or from deploying Jot in breach of the acceptable-use rules above.

## Liability

Neither party is liable for indirect or consequential loss. Each party's total
liability is capped at the fees you paid Jot in the 12 months before the event
that gave rise to the claim. Nothing excludes liability
that cannot lawfully be excluded.

## Suspension and termination

We may suspend a workspace that breaches the acceptable-use rules, after
notice where practicable. You may cancel at any time; deletion then follows
the retention schedule in the [privacy policy](/docs/legal/privacy-policy).

## Governing law

These terms are governed by the laws of the Federal Republic of Nigeria, and
the courts of Lagos State have jurisdiction over any dispute about them.

## Changes

We will give notice before material changes to these terms take effect. The
version you agreed to is recorded on your account.


---

# Refund policy

> When you can get your money back, and how.

Source: https://docs.usejot.co/docs/legal/refund-policy

Jot plans are monthly subscriptions. We want you to pay only for something
that works for you.

## 14-day refund on your first payment

If Jot isn't right for you, email **[support@usejot.co](mailto:support@usejot.co)** within **14 days of
your first payment for a plan** and we will refund that payment in full. No
questions, no forms.

## Renewals

Renewals are not refunded. Cancel any time before your next renewal and you
won't be charged again: your plan stays active until the end of the period
you already paid for.

## Upgrades and downgrades

Moving between plans is handled by our payment provider, which adjusts the
amount you are charged for the rest of the current period.

## How refunds are paid

Paid plans are sold through **Creem**, our merchant of record. Refunds go back
to the card or payment method you used, through Creem. Depending on your bank
they can take 5–10 business days to appear.

## Where the law gives you more

Nothing here limits a right you have under the consumer law that applies to
you.

Questions: **[support@usejot.co](mailto:support@usejot.co)**.


---

# Data processing addendum

> Processor terms under Art. 28 GDPR, with annexes.

Source: https://docs.usejot.co/docs/legal/dpa

This Data Processing Addendum (DPA) forms part of the agreement between the
customer (**controller**) and Jot (**processor**), as identified in the
[Terms of service](/docs/legal/terms), and applies wherever Jot processes
personal data of the customer's end users. Questions: **[privacy@usejot.co](mailto:privacy@usejot.co)**.

## Processor obligations (Art. 28(3)(a)–(h))

Jot shall:

* **(a)** process personal data only on the customer's documented
  instructions, including regarding international transfers, unless required
  by law — in which case Jot informs the customer before processing unless the
  law forbids it;
* **(b)** ensure persons authorised to process the data are bound by
  confidentiality;
* **(c)** implement the technical and organisational measures in Annex II;
* **(d)** engage sub-processors only under the conditions in Annex III;
* **(e)** assist the customer, insofar as possible, in responding to data
  subject requests;
* **(f)** assist the customer with security, breach notification, DPIAs, and
  prior consultation obligations (Arts. 32–36);
* **(g)** at the customer's choice, delete or return all personal data at the
  end of the services, and delete existing copies unless law requires storage
  — per the deletion SLAs below;
* **(h)** make available all information necessary to demonstrate compliance
  and allow for and contribute to audits conducted by the customer or its
  mandated auditor.

## Breach notification

Jot notifies the customer of a personal data breach affecting their end users'
data **without undue delay and in any case within 48 hours** of becoming aware
of it, with the information the customer needs for its own Art. 33 obligations.

## Deletion SLAs

* Deleted organisation: 7-day grace period, then purge begins.
* Hard purge complete within **30 days** of deletion.
* Backups and point-in-time recovery snapshots: beyond use immediately, purged
  within **35 days**.

## International transfers

Where processing involves a transfer out of the EEA or UK, the parties
incorporate the EU Standard Contractual Clauses by reference — **Module 2**
(controller → processor) or **Module 3** (processor → processor) as
applicable — together with the UK Addendum where UK GDPR applies.

## Annex I — Description of processing

| Item               | Description                                                                                                |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Subject matter     | Collection and resolution of feedback on the customer's web application, including optional session replay |
| Duration           | The term of the agreement plus the deletion periods above                                                  |
| Nature and purpose | Capturing feedback reports and associated technical context so the customer can reproduce and fix issues   |
| Data subjects      | The customer's end users (reviewers, visitors to pages where the widget is deployed)                       |
| Data types         | Screen content subject to masking, DOM selectors, page URLs, IP address, user agent, interaction events    |

## Annex II — Technical and organisational measures

* TLS for all data in transit; encryption at rest for database and object
  storage.
* Masking by default: all inputs masked in the end user's browser before
  upload; passwords always masked; `.jot-block` / `.jot-mask` honoured;
  masking is never performed server-side.
* Access control: tenant isolation enforced and tested; role-based membership;
  the internal admin console on a separate origin with TOTP.
* Logging: admin actions and impersonation written to an audit log and to the
  customer's own activity feed; impersonation is read-only and time-boxed.
* Deletion procedures per the SLAs above.

## Annex III — Sub-processors

The current list is maintained at
[Sub-processors](/docs/legal/subprocessors). Jot notifies customers of
intended additions or replacements, and the customer may **object within 30
days** of notice on reasonable data-protection grounds; if the objection
cannot be resolved, the customer may terminate the affected services.


---

# Sub-processors

> The vendors that process data on Jot's behalf, and what each one sees.

Source: https://docs.usejot.co/docs/legal/subprocessors

Jot uses a deliberately short list of vendors. DPA customers receive notice of
any addition or replacement and have a 30-day objection right — see
[Annex III of the DPA](/docs/legal/dpa#annex-iii--sub-processors).

| Name                    | Purpose                                       | Data categories                                                                              | Location                      |
| ----------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------- |
| Cloudflare R2           | Object storage                                | Screenshots, session recordings                                                              | Automatic (set by Cloudflare) |
| Resend                  | Transactional email                           | Names, email addresses, notification content                                                 | US                            |
| Sentry                  | Error tracking                                | Error reports: stack traces, request URLs, browser and server metadata                       | EU (Germany)                  |
| LLM provider (optional) | AI features, only if a workspace enables them | Comment text and technical context sent for the enabled feature; sent with training disabled | US                            |

**Creem** handles payments as merchant of record. It is an **independent
controller**, not a sub-processor: it receives billing name, email and payment
details directly from the customer at checkout, and that data never touches
Jot's systems.

Last updated: 2026-10-11.


---

# Using Jot lawfully

> A deployment guide for customers — consent, masking, notices, and erasure.

Source: https://docs.usejot.co/docs/legal/using-jot-lawfully

You are the controller for your end users' data; Jot is your processor. That
means the notices, consents, and legal bases are yours to get right. This page
makes that as mechanical as possible.

## Gate the widget behind consent

If your consent management platform (CMP) requires consent before analytics or
recording tools run, do not let Jot start on its own. Set
`data-jot-autostart="false"` on the script tag and start the widget only after
the CMP grants consent:

```html
<script
  src="https://cdn.jsdelivr.net/npm/@usejot/widget@1/dist/jot.js"
  data-jot-key="jot_pk_your_key"
  data-jot-autostart="false"
></script>
```

```js
// after your CMP reports consent for the relevant category:
window.Jot.init();
```

Until `init()` is called, the loader stays inert — nothing is captured, no
network requests are made on your users' behalf.

## Mask what recording must not see

* Every input is masked by default (`maskAllInputs`); passwords are always
  masked regardless of configuration.
* Add `.jot-block` to any element that must not be recorded at all.
* Add `.jot-mask` to any element whose text must be masked.
* Masking happens in the end user's browser before upload — never on a server.

## What is captured, what never is

**Captured** (when the reporter acts): a screenshot of the **full page** — with
`.jot-block` and `.jot-mask` elements, your project's configured mask
selectors, every filled form field, and passwords painted over in the browser
before upload — the DOM selector, page URL, browser and OS, and — only if the
reporter presses Record — a session replay with console and network activity,
subject to masking.

**Never captured**: password plaintext, masked input values, anything inside
`.jot-block`, or anything at all before the reporter interacts with the
widget.

## The reporter is told before sending

The composer shows a notice line above the Send button: sending attaches a
screenshot of this page, and sensitive fields are masked before anything
leaves the browser. The transparency line is built in — nobody submits a
screenshot unknowingly.

## Limit where the widget runs

* **Domain allowlist** — project settings take a list of hostnames
  (`app.acme.com`, or `*.acme.com` wildcards). The API refuses widget traffic
  from any other site; an empty list allows all sites.
* **Internal-only mode** — set the project's visibility to **Internal** (the
  API then refuses anyone who is not a signed-in team member) and add
  `data-jot-internal="true"` to the embed: the tab mounts only when the page
  is visited with `?jot=true`, so end users never see it.
* **Environment tagging** — `data-jot-env="staging"` stamps every report with
  the deployment it came from, keeping production feedback separate.

## A paragraph for your privacy policy

Copy, adapt, and have your counsel confirm:

> We use Jot, a feedback tool, to let reviewers report issues on this site.
> When a reviewer submits feedback, Jot captures a screenshot of the page —
> with sensitive fields masked before it leaves the browser — the page
> address, and browser information. If the reviewer starts a
> session recording, their interactions on the page are recorded with all form
> input masked before it leaves the browser. This data is processed by Jot on
> our behalf and retained for up to 30 days (recordings) or for as long as the
> report is needed. See Jot's privacy documentation for details.

## Handling an erasure request

When one of your end users asks you to erase their data, delete the reports
and recordings that concern them from **Settings → Privacy** in your
dashboard, which removes the comment, its screenshot, and any recording from
storage. Deletion propagates to backups within 35 days. If you cannot locate
the report, contact support with the approximate date and page URL.

## DPIA assistance

If your deployment needs a Data Protection Impact Assessment (for example,
recording on pages used at scale), the [DPA](/docs/legal/dpa) commits us to
assist. Email **[privacy@usejot.co](mailto:privacy@usejot.co)** and we will provide the technical
descriptions (Annexes I and II) in whatever format your assessment needs.


---

# Fix with AI

> Select reports, get a pull request.

Source: https://docs.usejot.co/docs/ai/fix-with-ai

<Callout>
  Fix with AI is part of the Agency plan, for workspace admins and owners, on projects linked to a
  [GitHub repository](/docs/integrations/github).
</Callout>

Select up to ten Jots in the inbox and press **Fix with AI**. Jot reads the
reports, works on a copy of your repository, checks its change, and opens a pull request.

While it works, the toolbar shows **Fixing…**. When it's done, **Fix PR ↗**
takes you to the pull request. If no working fix is found, it says
**Fix failed** and nothing is pushed.

You can also start it from a single Jot's detail panel.

## What it will and won't do

* It opens a pull request. It never merges, and never pushes to your main
  branch.
* It doesn't change any Jot's status. Review the PR, ship it, then resolve the
  Jots — and the reporters approve.
* One attempt runs per project at a time.


---

# Flutter

> One widget around your app — iOS, Android, macOS, Windows, Linux and the web.

Source: https://docs.usejot.co/docs/install/flutter

<Callout title="The package is on its way to pub.dev">
  Until `usejot` is published, add it from the `usejot/jot-flutter` repository as a git dependency.
  The API on this page is the one it ships with.
</Callout>

## Install

```yaml title="pubspec.yaml"
dependencies:
  usejot:
    git: https://github.com/usejot/jot-flutter
```

Once it is on pub.dev, this becomes `flutter pub add usejot`. Jot needs Flutter 3.41 or newer.

Wrap your app once:

```dart title="lib/main.dart"
import 'package:usejot/usejot.dart';

void main() => runApp(
  Jot(projectKey: 'jot_pk_your_key', child: const MyApp()),
);
```

Then give your app's navigator the observer, so every pin knows which screen it was left on:

```dart
MaterialApp(
  navigatorObservers: [Jot.navigatorObserver],
  // …
)
```

That is the whole install. A small Jot button appears on the edge of the screen. Reviewers tap
it, tap the thing that is wrong, and type one sentence.

## Platforms

| Platform            | Recording                                         | Crashes                                   |
| ------------------- | ------------------------------------------------- | ----------------------------------------- |
| iOS, Android, macOS | video, encoded natively                           | native and Dart                           |
| Windows, Linux      | video when the machine can encode it, else frames | native and Dart, or Dart only with frames |
| Web                 | frames                                            | Dart                                      |

On iOS, Android and macOS, recording runs in Jot's native core: frames are masked and encoded
to video on a background thread. Windows and Linux use the same native core when the machine
has a video encoder (on Linux, GStreamer with an H.264 encoder). Without one, and on Flutter
web, Jot falls back to a lighter recording made of individual frames, at a lower frame rate.
Everything else — pins, screenshots, annotations, threads and live updates — works the same
everywhere.

Jot needs iOS 13, macOS 11 and Android 7 (API 24) or newer.

## Screens

`Jot.navigatorObserver` follows your app's page routes. The route on top is the screen: its
name if it has one (`/checkout`), otherwise the page widget's type. Every Jot, pin and recording
is filed under that screen.

Name your routes and the dashboard reads like your app:

```dart
MaterialPageRoute(
  settings: const RouteSettings(name: '/details'),
  builder: (_) => const ProductDetails(),
)
```

## Options

```dart
Jot(
  projectKey: 'jot_pk_your_key',
  environment: 'staging',
  authScheme: 'shop.acme',
  edge: JotEdge.left,
  child: const MyApp(),
)
```

| Option        | What it does                                                                    |
| ------------- | ------------------------------------------------------------------------------- |
| `projectKey`  | Your project's public key. Required.                                            |
| `environment` | Tags every Jot (`staging`, `preview`…) so you can filter by it.                 |
| `authScheme`  | The scheme Google sign-in returns to. Defaults to `jot.` plus your app id       |
| `trigger`     | `JotTrigger.fab` (the Jot button, default) or `JotTrigger.tab`, a slim edge tab |
| `edge`        | `JotEdge.right` (default) or `JotEdge.left`                                     |
| `offset`      | The tab's position down the edge, 0–100 (percent). Default 50                   |

Open Jot from your own UI with `Jot.of(context).open()`, or `Jot.open()` from anywhere.
`Jot.of(context)` also has `close()` and `isOpen`.

## Pins land on the same widget

A pin remembers the widget under the reviewer's finger — its text, its semantics label, its
type and its place in the widget tree — and the spot inside it. Later, on another phone, a
tablet, a resized window or in landscape, Jot finds the same widget and puts the pin back at
the same spot. You don't have to set anything up. If the widget is gone, the pin hides; if
nothing matches well enough, the pin shows where it was dropped, marked approximate.

For the few widgets that matter most, give them a name and the match is exact, on every device:

```dart
JotAnchor(
  id: 'checkout.pay',
  child: ElevatedButton(onPressed: pay, child: const Text('Pay now')),
)
```

`JotAnchor` sets the id as the child's semantics identifier, so accessibility tools see it too.
Layout does not change.

## Privacy

Masking happens on the device, before an image or a recording exists. Nothing unmasked ever
leaves it.

* Password fields (`obscureText: true`) are always blacked out.
* Wrap anything else in `JotMask`:

```dart
JotMask(child: CardNumberField())
```

* To mask without shipping a build, add ids under **Project → Settings → Masked element ids
  (native apps)**. Any `JotAnchor` with that id, and any widget with that
  `ValueKey<String>`, is blacked out from the next session on.

Recorded network calls never include credentials: auth headers, cookies, tokens and anything
that looks like a password are replaced on the device before they are stored. Whatever was
typed into a masked field is blanked out of recorded requests too. Request and response bodies
are kept only when **Capture request and response bodies** is on in **Project → Settings →
Recording privacy**.

## Recording and crashes

Reviewers can press **Record**, reproduce the problem, then press **Stop & describe**. Jot keeps
the last two minutes. The recording carries the screen, console output, network calls, screen
changes, app lifecycle, taps (positions only — never keystrokes) and crashes. Nothing is
uploaded unless the reviewer sends the Jot. On iOS the upload carries on in the background if
the reviewer leaves the app; Jot hooks into your app delegate itself, so there is no code to add.

What is captured on its own:

* **Network calls made with `dart:io`** — `package:http`, Dio and plain `HttpClient`. Jot chains
  to any `HttpOverrides` your app installed first.
* **`debugPrint` output**, screen changes and errors.

What needs one line from you:

* **Network calls on the web.** `dart:io` doesn't exist there; wrap your client:

  ```dart
  final client = JotHttpClient(http.Client());
  ```

  `JotHttpClient` works on every platform, so you can use it everywhere.

* **`print` output.** Start your app inside `Jot.runZoned`:

  ```dart
  void main() => Jot.runZoned(
    () => runApp(Jot(projectKey: 'jot_pk_your_key', child: const MyApp())),
  );
  ```

If the app crashes — with or without a recording running — Jot saves the error before the app
closes. On the next launch it asks the reviewer whether to send it to the team. If they agree,
the crash becomes a **Bug** Jot on the screen it happened on, with the stack trace and whatever
was recorded.

## Sign-in

Invited reviewers sign in with a 6-digit code sent to their email. That works everywhere with
no setup.

To offer **Continue with Google** too:

* **iOS, Android and macOS** — add your scheme under **Project → Settings → App redirect
  schemes**. By default it is `jot.` plus your bundle id (iOS, macOS) or package name (Android),
  lowercased, with any `_` turned into `-`: `com.acme.my_shop` gives `jot.com.acme.my-shop`. If
  your bundle id and package name differ, add both. Nothing else to set up.

* **A scheme of your own** — pass `authScheme` to `Jot`, and on Android set the same value in
  `android/app/build.gradle.kts`, since Android declares the scheme at build time:

  ```kotlin
  android {
      defaultConfig {
          manifestPlaceholders["jotAuthScheme"] = "shop.acme"
      }
  }
  ```

* **macOS, Windows and Linux** — turn on **Allow desktop sign-in (loopback)** in the same
  section. Sign-in then opens in the system browser and returns to the app on its own. On
  Windows and Linux this is how Google sign-in works, so the setting is required there. On
  macOS, without it, Jot uses the app redirect scheme instead.

* **macOS release builds** — a sandboxed app needs the incoming network entitlement for desktop
  sign-in, and the outgoing one to reach Jot at all. Flutter's template leaves both out of
  release builds; add them to `macos/Runner/Release.entitlements`, and the outgoing one to
  `DebugProfile.entitlements` if it is missing there:

  ```xml title="macos/Runner/Release.entitlements"
  <key>com.apple.security.network.client</key>
  <true/>
  <key>com.apple.security.network.server</key>
  <true/>
  ```

* **Web** — nothing more; sign-in opens in a popup.

## Allow your app

Under **Project → Settings → Allowed apps**, add your iOS and macOS bundle id and your Android
package name, for example `com.acme.shop`. A project with no allowed apps accepts every app.
Flutter web runs in a browser, so it follows **Allowed domains** instead.

Like an allowed domain, an app id can be spoofed. It is a convenience gate, not a password —
the reviewer's sign-in is what protects your feedback.

The app settings appear on Flutter and React Native projects.

## Phones and desktops

On screens narrower than 640 points, Jot opens as a bottom sheet. On tablets and desktops,
cards open beside the pin. <kbd>Esc</kbd> closes whatever is open.


---

# React Native

> Pin feedback on the real screen in iOS and Android apps.

Source: https://docs.usejot.co/docs/install/react-native

<Callout title="Packages are on their way to npm">
  `@usejot/react-native` is published with the next release. The API on this page is the one it
  ships with.
</Callout>

Jot for React Native is the same product as the web widget: a reviewer taps the
broken thing on a real screen and types one sentence. Your team gets the screen,
the element, a masked screenshot, the device and the build. They can also get a
recording with video, console, network and crashes.

It runs on iOS and Android, needs React Native 0.80 or newer with the New
Architecture, and Android 7 (API 24) or newer. Expo works with a development
build. Expo Go does not, because Jot has native code.

## Install

One package. Nothing else to add: Jot brings its own storage, safe-area and
icon drawing.

<Tabs items={["Expo", "Bare React Native"]}>
  <Tab value="Expo">
    ```bash
    npx expo install @usejot/react-native
    ```

    This also adds `"@usejot/react-native"` to the `plugins` in your `app.json`.
    The plugin lets iOS finish uploading a recording after the reviewer leaves the
    app. If your config is `app.config.js`, add it there yourself:

    ```js title="app.config.js"
    export default {
      plugins: ["@usejot/react-native"],
    };
    ```

    Then rebuild your development build (`npx expo prebuild`, or your EAS build).
  </Tab>

  <Tab value="Bare React Native">
    ```bash
    npm install @usejot/react-native
    npx pod-install
    ```

    Android needs nothing more. On iOS, so a recording finishes uploading after
    the reviewer leaves the app, forward background sessions from your AppDelegate:

    ```swift title="ios/YourApp/AppDelegate.swift"
    import JotReactNative

    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String,
                     completionHandler: @escaping () -> Void) {
      _ = RNJotKitBridge.handleBackgroundUploadEvents(identifier, completion: completionHandler)
    }
    ```
  </Tab>
</Tabs>

## Wrap your app

```tsx title="App.tsx"
import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
import { JotProvider } from "@usejot/react-native";

export default function App() {
  const navigationRef = useNavigationContainerRef();
  return (
    <JotProvider projectKey="jot_pk_your_key" navigationRef={navigationRef}>
      <NavigationContainer ref={navigationRef}>{/* your screens */}</NavigationContainer>
    </JotProvider>
  );
}
```

The provider adds a small floating button. Tap it and Jot opens. Nothing else
in your app changes.

## Screens

`navigationRef` takes React Navigation's container ref. Jot reads the current
route's name and listens for changes, so every Jot is filed under the screen it
was made on, such as "Checkout". Pins, presence and recordings follow the
screen. Without it, the whole app counts as one screen.

## Props

| Prop            | What it does                                                       |
| --------------- | ------------------------------------------------------------------ |
| `projectKey`    | Required. Your project's public widget key                         |
| `environment`   | `"staging"`, `"preview-142"`. Filters in the dashboard             |
| `navigationRef` | Screen names for pins, presence and recordings                     |
| `trigger`       | `"fab"` (default) or `"tab"`                                       |
| `edge`          | `"right"` (default) or `"left"`                                    |
| `offset`        | Tab position, 0–100 (percent of the height). Default 50            |
| `autostart`     | `false` waits for `useJot().init()`, for example after consent     |
| `authScheme`    | The Google sign-in redirect scheme. Default `jot.<your bundle id>` |

## Open Jot from your own UI

```tsx
import { useJot } from "@usejot/react-native";

function HelpMenu() {
  const jot = useJot();
  return <Button title="Report a problem" onPress={jot.open} />;
}
```

`useJot()` returns `open`, `close`, `startRecording`, `init` and `isOpen`.
Outside a `JotProvider` it does nothing.

## Pins land on the same element

When a reviewer taps, Jot finds the view under their finger. It records what
that view is: its accessibility label, its text, its place in the view tree and
its `testID`. Later, on another phone, a tablet or in landscape, Jot finds the
same view and puts the pin at the same spot inside it. You don't have to set
anything up.

For the elements that matter most, add an exact ID:

```tsx
import { JotAnchor } from "@usejot/react-native";

<JotAnchor id="checkout.pay">
  <Pressable onPress={pay}>
    <Text>Pay now</Text>
  </Pressable>
</JotAnchor>;
```

`JotAnchor` puts the ID on its child as `testID` and `nativeID`, so your layout
stays the same. A custom component must pass both on to a native view.
If no view on the screen matches a pin, the pin is shown at the spot it was
dropped, marked approximate. If its element has left the screen, the pin hides.

## Privacy

Masking happens on the device, before an image or a recording exists.

* Password fields (`secureTextEntry`) are always blacked out.
* Wrap anything else in `JotMask`:

```tsx
import { JotMask } from "@usejot/react-native";

<JotMask>
  <Text>{customer.cardNumber}</Text>
</JotMask>;
```

* To mask without shipping a build, add ids under **Project → Settings →
  Masked element ids (native apps)**. Any `JotAnchor` with that id, and any
  `<JotMask id="…">` with it, is blacked out.

Recorded network calls keep their method, URL, status and timing. Credential
headers, secret query parameters and secret body fields such as `password`,
`token` and `cardNumber` are replaced on the device. Bodies are kept only when
**Capture request and response bodies** is on in **Project → Settings →
Recording privacy**. Keystrokes are never recorded. Only tap positions are.

## Recording

A reviewer taps **Record**, shows the problem, then taps **Stop & describe**.
The last two minutes of the app's own window are encoded to video natively, on
a background thread. That recording goes with the Jot, along with the console,
network calls, screen changes, taps and app lifecycle. Nothing is uploaded until
the Jot is sent.

Console output is captured on its own. So are calls made with `fetch`, axios or
anything else built on `XMLHttpRequest`. For a client that bypasses it, such as
`expo/fetch`, wrap the client:

```ts
import { fetch as expoFetch } from "expo/fetch";
import { JotHttp } from "@usejot/react-native";

export const fetch = JotHttp.wrap(expoFetch);
```

For a native HTTP module you can't wrap, report each call yourself with
`JotHttp.record({ method, url, status, durationMs })`. It is redacted like the
rest.

## Crashes

If the app crashes, Jot saves the error before the app closes. A JavaScript
error and a native crash are handled the same way. On the next launch, Jot asks
the reviewer whether to send it to the team. If they agree, the crash becomes a
**Bug** Jot on the screen it happened on, with the stack trace and whatever was
recorded.

Jot runs its own crash handler first, then hands the crash on to whatever
handler your app already had, such as Crashlytics or Sentry.

## Sign-in

Invited reviewers sign in with a 6-digit code sent to their email. That needs
no setup.

To offer **Continue with Google** too, add your redirect scheme under
**Project → Settings → App redirect schemes**. By default it is `jot.` plus your
bundle ID (iOS) or package name (Android), lowercased, with any `_` turned into
`-`: `jot.com.acme.shop`. If the two ids differ, add both schemes. The default
needs no setup on either platform.

To choose another scheme, pass `authScheme` to `JotProvider`, and register it
for the build:

* **Expo** — pass it to the plugin too:

  ```json title="app.json"
  {
    "expo": {
      "plugins": [["@usejot/react-native", { "authScheme": "shop.acme" }]]
    }
  }
  ```

  The plugin adds the scheme to iOS's `Info.plist` and to the Android build.

* **Bare** — set it on Android, which declares the scheme at build time. iOS
  needs nothing.

  ```groovy title="android/app/build.gradle"
  android {
    defaultConfig {
      manifestPlaceholders["jotAuthScheme"] = "shop.acme"
    }
  }
  ```

## Allow your app

In **Project → Settings → Allowed apps**, add your bundle ID (iOS) and your
package name (Android), for example `com.acme.shop`. A project with no allowed
apps accepts every app. The app settings appear on React Native and Flutter
projects.

Like an allowed domain, an app ID can be spoofed. It is a convenience gate, not
a password. The reviewer's sign-in is what protects your feedback.

## Phones and tablets

On screens narrower than 640 points, Jot opens as a bottom sheet that you can
drag between two heights. On wider screens, it opens as a card beside the pin.
Both work in portrait and landscape, and the sheet moves up when the keyboard
opens.
