Back to home

Mite developer docs

SDK, REST API, authentication, triage, errors, rate limits, and webhooks

Mite is in-app feedback and release notes for React Native and Expo apps. Your
app sends bug reports, feature requests, and user identities to the Mite API.
Mite triages each report as it arrives, you act on it in the Mite dashboard, and
you publish release notes back to the same app.

This page is the developer index: SDK, REST API, authentication, triage, errors,
rate limits, webhooks, and the machine-readable files that describe all of it.

Quickstart

  1. Create an app in the Mite dashboard and generate
    an API key under Settings → Keys.
  2. Install the SDK in your Expo or React Native app.
  3. Submit your first bug report.
npm install @usemite/mite-sdk
import { Mite, MiteProvider, useBugReport } from '@usemite/mite-sdk'

const mite = new Mite({ apiKey: 'mite_ak_...' })
mite.init()

// Wrap your app once:
<MiteProvider miteInstance={mite}>{/* Your app */}</MiteProvider>

// Then submit bugs from anywhere:
const { submitBugReport } = useBugReport()
await submitBugReport({
  title: 'Something broke',
  description: 'What the user saw',
})

SDK

The React Native and Expo SDK is published as
@usemite/mite-sdk. It wraps every
endpoint below, collects device and app context, and retries safely. Use it
unless you have a reason to call the API directly.

Authentication

Every endpoint except /api/v1/health needs a Mite API key, sent as a bearer
token:

Authorization: Bearer mite_ak_...

Mite API keys are publishable, SDK-style tokens, like a Sentry DSN or a PostHog
project key. They ship inside your mobile binary and anyone can extract them
from a shipped APK or IPA, so they are not secrets. Mite handles abuse with
per-key rate limits and instant revocation instead of secrecy. Never use a Mite
key as a substitute for your own user authentication.

Each key carries scopes:

Scope Grants
read List releases, announcements, feature requests
write Submit bug reports, feature requests, votes, identities

A request with the wrong scope is refused with 403.

Base URL

https://usemite.com

API endpoints

Method Path Scope Purpose
GET /api/v1/health none Liveness probe
POST /api/v1/bug-reports write Submit a bug report
POST /api/v1/errors write Send a batch of JS error occurrences
POST /api/v1/upload-url write Create a single-use attachment upload URL
POST /api/v1/identify write Create or update an end-user profile
GET /api/v1/releases read List published releases
GET /api/v1/announcements read List active announcements
GET /api/v1/feature-requests read List feature requests
POST /api/v1/feature-requests write Submit a feature request
POST /api/v1/feature-requests/vote write Toggle a vote
GET /api/v1/feature-requests/votes read List one voter's votes
GET /getImage read Fetch a stored attachment

Every path also answers OPTIONS for CORS preflight.

The full request and response schema for each endpoint is published as OpenAPI
3.1 at usemite.com/openapi.json.

Submit a bug report

curl -X POST https://usemite.com/api/v1/bug-reports \
  -H "Authorization: Bearer mite_ak_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Crash when opening the Profile tab",
    "description": "The app closes immediately after tapping Profile.",
    "app_version": "1.4.2",
    "device_info": { "model": "iPhone 15", "os": "iOS 18.2" }
  }'
{ "id": "j57...", "status": "NEEDS_TRIAGE" }

The call returns as soon as the report is stored. Triage runs after the
response, so the status is NEEDS_TRIAGE here even for a report that is
triaged a moment later.

Send JS errors

The SDK batches the JS errors it catches and sends them here. Every event joins
the group of the same error: the same stack groups together even when the
message differs, and an error with too thin a stack groups by its message with
ids and numbers masked out. Errors do not count against the report quota.

curl -X POST https://usemite.com/api/v1/errors \
  -H "Authorization: Bearer mite_ak_..." \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "name": "TypeError",
      "message": "Cannot read property \'id\' of undefined",
      "stack": "TypeError: ...\n    at renderRow (index.bundle:1:48211)",
      "is_fatal": false,
      "app_version": "1.4.2"
    }]
  }'
{ "accepted": 1, "groups": ["erg_..."] }

On paid plans, triage runs once when a group is first seen and again when a
resolved group comes back. It labels the group:

Verdict Meaning What Mite does
duplicate The same fault as an open bug report or another error Links the error to that bug
recurring Keeps happening, or came back after it was resolved Opens a bug, or reopens the closed one
real_issue A fault in the app's code Opens a bug report for it
non_issue An expected condition, like a cancelled action or no network Nothing
noise Library warnings, development messages, platform errors Nothing

An error is only written off as non_issue or noise when the model is at
least 70% sure; anything less stays a real issue.

Triage

On paid plans, every accepted bug report is sent to one model call that answers
these questions about it:

Question Answer
actionable Probability that there is something to act on
spam Probability that the report is spam
reproducible Probability that the description is reproducible
category One of crash, ui, performance, data, auth, network, and six more
severity cosmetic, minor, major, or blocking
frustration calm, frustrated, or angry
area One of the feature areas you saved for the app, or other
duplicate_of The id of one of the 25 newest open reports, or none

actionable, spam, and reproducible are stored as probabilities on their
own. The rest carry a confidence, which the report page shows for category and
severity. The area question is asked only when the app has saved areas, and
duplicate_of only when the app has an open report to compare against.

Code reads the answers and decides what to do with them:

  • Priority is set from severity, reproducibility, and frustration, and only
    when severity carries at least 0.85 confidence and no one has set a
    priority on the report.
  • A duplicate is shown as a hint at 0.6 confidence or higher, with a Mark
    as duplicate
    button. Nothing is merged for you.
  • A report is treated as noise at 0.9 or higher and moves behind a toggle on
    the bugs list. Nothing is closed.
  • With an issue provider set for the app, a bug that is not noise and whose
    severity rounds to blocking at 0.85 confidence or higher opens a linked
    GitHub or Linear issue.

A feature request is triaged too, with a shorter question set: spam,
category over feature, improvement, bug, question, and other, plus area and
duplicate_of under the same conditions. It gets no severity, so no priority
rule applies to it.

Triage needs OPENROUTER_API_KEY. Without it, or when the call fails, the
report is stored untouched and the rest of intake continues.

Triage is not exposed over the API today. Re-run it on a single bug report from
the Triage card on the report page.

Errors

Every failure returns JSON, never HTML. Branch on code, not on error.

{
  "error": "API key is missing the required 'write' scope",
  "code": "forbidden",
  "hint": "Create a key with the write scope in Settings → Keys, or use an existing key that has it."
}
Status code Meaning
400 invalid_request Malformed JSON, or a field failed validation
401 unauthorized Missing, malformed, or unknown API key
402 REPORT_QUOTA_EXCEEDED The plan's monthly report allowance is used up
402 STORAGE_QUOTA_EXCEEDED The plan's attachment storage is used up
403 forbidden The key lacks the scope the endpoint needs
404 not_found No such resource for this application
413 payload_too_large The request body is larger than 256 KB
429 rate_limited Per-key rate limit exceeded
500 internal_error Unexpected server error

A quota refusal is 402, never 429. A 429 tells a client to retry; a
monthly quota cannot improve by retrying, only by changing plan. Quota
responses carry a quota object with limit, used, and resets_at.

Rate limits

Limits are per API key, as a token bucket refilled over one minute. They are
scoped to the key rather than to the application, so one misbehaving key cannot
exhaust the budget of a sibling key on the same app.

Endpoint Policy Requests per minute
/api/v1/bug-reports bug-reports 60
/api/v1/errors errors 60
/api/v1/upload-url upload-url 30
/api/v1/identify identify 120
/api/v1/releases releases 120
/api/v1/announcements announcements 120
/api/v1/feature-requests (GET) feature-requests-list 120
/api/v1/feature-requests (POST) feature-requests-create 30
/api/v1/feature-requests/vote feature-requests-vote 60
/api/v1/feature-requests/votes feature-requests-votes 120
/getImage images 300

/api/v1/health is unauthenticated and unmetered.

Rate limit headers

Every metered response carries your remaining quota, so you can throttle before
you are refused rather than discovering the limit by hitting it. The fields
follow the IETF RateLimit header fields draft.

RateLimit-Policy: "releases";q=120;w=60
RateLimit: "releases";r=118;t=1
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 1
Field Meaning
RateLimit-Policy The quota in force: q requests per w seconds
RateLimit What is left for you: r requests, t seconds until the quota is whole
RateLimit-Limit Superseded plain-integer form of q
RateLimit-Remaining Superseded plain-integer form of r
RateLimit-Reset Superseded plain-integer form of t

The three plain-integer fields are from an earlier draft and are no longer part
of the specification. Mite still sends them because most deployed HTTP clients
read them; prefer RateLimit and RateLimit-Policy in new code.

All of them are named in Access-Control-Expose-Headers, so a browser can read
them cross-origin.

A refused request returns 429 with Retry-After in seconds, alongside
RateLimit reporting r=0. When both are present, Retry-After is
authoritative.

HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Policy: "bug-reports";q=60;w=60
RateLimit: "bug-reports";r=0;t=60

Rate limits on usemite.com

The machine-readable documents on usemite.com carry RateLimit-Policy and are
metered per client at 600 requests per minute. Those documents are
/openapi.json, /api/v1, /.well-known/api-catalog, /agents.md,
/llms-full.txt, and the JSON errors under /api/.

Those responses carry no live RateLimit field. They are publicly cacheable,
and a shared cache would otherwise hand one client's remaining quota to the
next. A refusal is no-store, so it carries the full set.

Webhooks

Mite posts outgoing notifications to Discord, Slack, and a signed endpoint of
your own (see Trigger an agent). Add a URL under
Settings → Webhooks in the dashboard and choose which events fire:

  • a new bug report arrives
  • a bug report changes status
  • a new feature request arrives

With Notify only on actionable reports on for an app, the arrival events
wait for triage and are skipped for a report triage calls noise. The same gate
holds the owner's email and in-app notification. If triage cannot run, all three
are sent.

Discord URLs must start with https://discord.com/api/webhooks/ and Slack URLs
with https://hooks.slack.com/services/. Mite does not accept inbound webhooks.

MCP server

Mite serves a Model Context Protocol server
at https://usemite.com/mcp. Connect Claude Code, Cursor, or a scheduled job to
it, and the agent can work through your bug queue, move the roadmap, and ship
release notes.

The MCP server is included on paid plans. An agent works only while the plan
of the app owner includes agent automations. When the plan loses it, every
request from the agent is refused with 402 and code FEATURE_LOCKED. The
agent works again when the plan is upgraded.

An agent connects in one of two ways. A connection made with OAuth acts on your
account. An agent key acts on one app.

Connect with OAuth

Claude, ChatGPT, and Cursor sign in with your Mite account. Add
https://usemite.com/mcp as a custom connector. The client opens a browser, you
sign in to Mite and approve the client, and no key is copied. Claude Code does
the same when you add the server without a header:

claude mcp add --transport http mite https://usemite.com/mcp

A connection acts on every app you own, with read and write access. The
list_apps tool lists those apps, and every other tool takes one of them as
app, by slug or id. An app you created but that now has another owner is
refused.

The connection is listed under Account connections in Settings → Agents
of any app. Revoke there blocks the client for your account. Mite refuses
every token of that client, new or old, with 403 and the code
connection_blocked, even after you sign in again. Click Allow again on
the connection to unblock it. Revoke does not change your other clients.

A request without a token gets 401 with a
WWW-Authenticate: Bearer resource_metadata="https://usemite.com/.well-known/oauth-protected-resource/mcp"
challenge. That document names the authorization server the client signs in
with. A token without the mcp scope gets 403 with insufficient_scope.

Connect with an agent key

A scheduled job or CI run has nobody to sign in, so it uses an agent key,
not an API key. Create one under Settings → Agents in the dashboard. An
agent key differs from an API key in three ways:

  • It is a secret. Mite shows it once and stores only its hash. Keep it out of
    your app binary and your repository.
  • It is bound to one app. Every tool acts on that app, so no tool takes an app
    id, and a request that names another app's bug is refused with FORBIDDEN.
  • It is read or write. A read key sees only the read tools.

Add the server to Claude Code:

claude mcp add --transport http mite https://usemite.com/mcp --header "Authorization: Bearer mite_agent_..."

Or commit a .mcp.json that reads the key from the environment:

{
  "mcpServers": {
    "mite": {
      "type": "http",
      "url": "https://usemite.com/mcp",
      "headers": { "Authorization": "Bearer ${MITE_AGENT_KEY}" }
    }
  }
}

Tools

Tool Access What it does
get_app read The app, and how many bug reports sit in each status
list_bugs read Bug reports, newest first, filtered by status, priority, and date
get_bug read One bug report with its comments, attachments, and linked requests
search_bugs read Bug reports like a text query or another bug, and what fixed them
list_changes read New reports, status changes, comments, and triage since a cursor
list_feature_requests read Feature requests, most voted first
list_crash_groups read Crash signatures shared by two or more bug reports
list_releases read Releases, highest version first
get_release read One release and the bugs it fixes
update_bug write Change a bug's status, priority, or title
claim_bug write Claim a bug for a while so other agents leave it alone
release_bug_claim write Drop your claim on a bug
assign_bug write Assign a bug to the app owner, or unassign it
comment_on_bug write Add a comment to a bug report
retriage_bug write Run triage on a bug report again
set_feature_request_status write Move a feature request on the roadmap
merge_feature_requests write Merge duplicate feature requests and their votes
link_bug_to_release write Mark a bug fixed in a release, and resolve it if it is still open
upsert_release write Create a release, or update the one with the same version
draft_release_notes write Draft notes from what shipped, resolving duplicates of fixed bugs
create_announcement write Draft an announcement, or return the unarchived one with its title
update_announcement write Edit, publish, or archive an announcement

list_bugs and list_changes page with a cursor. Pass the next_cursor of one page as the
cursor of the next, until next_cursor is null.

An OAuth connection also has list_apps, and each tool above takes an app
input.

A tool that fails returns an error result whose text starts with a code:
FORBIDDEN, NOT_FOUND, INVALID, or FEATURE_LOCKED when the plan does not
include the feature. A missing, unknown, revoked, or expired key or token is
refused with 401 before any tool runs. So is a key whose app has changed
owner since the key was created. The new owner creates their own keys.

The server is metered per agent key or OAuth connection under the mcp policy
at 120 requests per minute, with the same RateLimit headers as the REST API.

Prompts

The server also has one prompt, fix_bug, which needs read access. It takes
a bug_id (and app on an OAuth connection) and returns a brief for a coding
agent: what the user reported, the device, OS, and app version, the navigation
trail, crash frames, attachments, Mite triage, linked feature requests, and
related reports with the release that fixed them. The brief ends by asking the
agent to link its fix back with comment_on_bug and link_bug_to_release.
Issues that Mite opens on GitHub or Linear use the same brief as their body. A
fix_bug call that fails returns a JSON-RPC error whose message starts with
the same codes as a failed tool.

Trigger an agent

An agent can poll the queue on a schedule, or Mite can wake it when something
happens. Add an endpoint URL under Settings → Webhooks → Signed webhook.
Mite shows a signing secret (whsec_...) once. Rotate secret replaces it.

The URL must use https and point to a public host. Mite posts each event with
one attempt and a 10 second timeout, and the settings page shows the status
code of the last delivery. Send test event posts a webhook.test event.

Event Fires when data
bug.created a bug report arrives application_id, id
bug.status_changed a bug report changes status application_id, id, status, previous_status
feature_request.created a feature request arrives application_id, id
bug.triaged triage finishes on a new bug the triage verdict, see below
feature_request.triaged triage finishes on a new request the triage verdict, see below

The event toggles under Settings → Webhooks apply to the endpoint as well.
bug.triaged follows the new bug toggle and feature_request.triaged follows
the new feature request toggle. Discord and Slack do not get the triaged
events. A bulk status change sends one bug.status_changed per bug. The other
events hold ids only:

{
  "type": "bug.created",
  "timestamp": "2026-09-25T18:00:00.000Z",
  "data": { "application_id": "app_...", "id": "bug_..." }
}

Your agent reads the details with get_bug or list_feature_requests.

The triaged events carry the verdict, so an agent can act without a follow-up
call. They fire only when triage runs, and they fire even for a
report triaged as noise while Notify only on actionable reports is on.
Check is_noise to skip those.

{
  "type": "bug.triaged",
  "timestamp": "2026-09-25T18:00:05.000Z",
  "data": {
    "application_id": "app_...",
    "id": "bug_...",
    "title": "Crash on launch",
    "category": "crash",
    "severity": "blocking",
    "suggested_priority": "CRITICAL",
    "area": "Checkout",
    "duplicate_of": "bug_...",
    "is_noise": false,
    "confidence": { "category": 0.93, "severity": 0.88 },
    "url": "https://usemite.com/my-app/bugs/bug_..."
  }
}

severity is cosmetic, minor, major, or blocking. area and
duplicate_of are null when triage has no confident answer.
feature_request.triaged holds id, title, category, demand (niche,
few, many, or most), demand_score (0 to 100), area, duplicate_of,
bug_id (the bug the request was linked to, or null), is_noise,
confidence (category, demand), and url.

Mite signs with Standard Webhooks, so any
Standard Webhooks library verifies the webhook-id, webhook-timestamp, and
webhook-signature headers. Verify against the raw body, before you parse it.
This Cloudflare Worker verifies an event and starts a GitHub Actions workflow
through repository_dispatch:

import { Webhook } from 'svix'

type Env = { MITE_WEBHOOK_SECRET: string; GITHUB_TOKEN: string }

export default {
  async fetch(request: Request, env: Env) {
    const body = await request.text()
    try {
      new Webhook(env.MITE_WEBHOOK_SECRET).verify(
        body,
        Object.fromEntries(request.headers),
      )
    } catch {
      return new Response('Invalid signature', { status: 401 })
    }
    const event: { type: string; data: { id: string } } = JSON.parse(body)
    if (event.type !== 'bug.created') return new Response(null, { status: 204 })
    await fetch('https://api.github.com/repos/acme/app/dispatches', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${env.GITHUB_TOKEN}`,
        Accept: 'application/vnd.github+json',
        'User-Agent': 'mite-relay',
      },
      body: JSON.stringify({
        event_type: 'mite-bug',
        client_payload: { bug_id: event.data.id },
      }),
    })
    return new Response(null, { status: 202 })
  },
}

The workflow runs your agent with the MCP server connected, and the agent calls
get_bug with client_payload.bug_id. Delivery is at most once. Keep a
scheduled run too, so a missed event still gets picked up.

Machine-readable resources

Resource What it is
/openapi.json OpenAPI 3.1 description of the Mite API
/api/v1 Mite API discovery document
/.well-known/api-catalog Mite API catalog, as an RFC 9727 linkset
/agents.md When to use Mite, and how to make the first call
/llms.txt Index of Mite's pages for agents
/llms-full.txt Every Mite page as one Markdown document
/sitemap.xml Every public page
/docs.md This page as Markdown

Every public page also answers Accept: text/markdown with a Markdown
rendition of itself, and advertises it with a Link: rel="alternate" header.

Support

Open an issue on github.com/usemite/mite-sdk
for SDK problems, or use the in-app feedback widget in the Mite dashboard.