Skip to main content

API

The BugPort API is the programmatic surface behind everything the product does — the dashboard, the browser extension, the NPM widget, and the MCP bridge all talk to it. This page is an overview: it explains the base URL, the three ways callers authenticate, the main resource groups you'll work with, the media upload flow, and how to decide between the API, the widget, and MCP. The full per-endpoint reference (request and response schemas for every path) is being published separately; treat the examples here as illustrative shapes, not a contract.

v0.2.0 The OpenAPI contract is versioned and stable enough to build against, but field-level schemas may still gain optional properties. Pin to the /v1 prefix and code defensively against unknown fields.

Base URL and versioning

https://api.bugport.dev/v1

Every endpoint lives under the /v1 path prefix. Versioning is done through that path segment — a future breaking revision would ship as /v2 rather than silently changing /v1 responses. When you point the widget or a self-hosted client at a non-default host, the base URL must still end in /v1; a missing version segment is the most common cause of 404 responses.

💡
If you are self-hosting or testing locally, override the base URL (for example in the widget's apiBaseUrl prop) but keep the trailing /v1. The hosted production API is always https://api.bugport.dev/v1.

Authentication

BugPort has three distinct callers, and each authenticates differently. The right credential depends on who is calling and what scope of data they should reach.

Supabase session JWT
Used by the dashboard and app. After a user signs in through Supabase Auth (Google, GitHub, or email/password), the frontend sends the session JWT as a Bearer token. The backend verifies the JWT and resolves the user's workspace memberships. This is the highest-trust path and reaches everything the signed-in user is permitted to see.
Public widget key
Used by widget submissions from inside your own app. A bp_pub_… key identifies a project and is safe to ship in client-side code because it is origin-restricted — submissions are only accepted from the allowed origins configured on the key. It can create bug reports for its project but cannot read or manage data.
Scoped MCP token
Used by AI tools via MCP. A workspace-scoped (optionally project-restricted) Bearer token with explicit scopes such as bugs:read and bugs:write. Shown once at creation, revocable, and every call it makes is audit-logged. See MCP.

The browser extension authenticates as a signed-in user as well, and uses its own /v1/plugin/… endpoints tuned for capture upload.

Auth model

Every credential resolves to a workspace scope before any data is returned. Access is always scoped — there is no path that crosses workspace boundaries.

Resource groups

The API is organized into a handful of resource groups. These are the real groups in the v0.2.0 contract; the per-endpoint reference will document each path's parameters and response body.

System
Liveness and build info: GET /v1/health, GET /v1/version. Useful for monitoring and for confirming your client reaches the right deployment.
Auth
GET /v1/auth/me returns the current user; POST /v1/auth/sync-profile reconciles the Supabase identity with BugPort's profile during onboarding.
Bugs
The core dashboard resource (JWT). GET/POST /v1/bugs, GET/PATCH /v1/bugs/{bug_id}, plus sub-resources for /logs, /network, /comments, /activity, /media, /export, and /share-links. This is where triage, status changes, and comments happen.
Widget submissions
Public endpoints authenticated by a widget key: POST /v1/widget/media/upload-url, POST /v1/widget/media/complete, POST /v1/widget/bugs. These mirror the media flow below, scoped to a single project.
Media upload
POST /v1/media/upload-url, POST /v1/media/complete, GET /v1/media/{media_id}. Binaries go to Cloudflare R2, never through Postgres.
Comments and status
Sub-resources of a bug: /v1/bugs/{bug_id}/comments for discussion and PATCH /v1/bugs/{bug_id} for status and severity changes. These also drive the activity timeline.
Workspaces and Projects
/v1/workspaces, /v1/workspaces/{id}/members, /v1/workspaces/{id}/invites, /v1/workspaces/{id}/storage, /v1/projects, and /v1/projects/{project_id}/widget-keys. Workspaces are the isolation and billing boundary; projects group bugs and own widget keys.
Plans and billing
GET /v1/plans, and under a workspace .../billing, .../billing/checkout, .../billing/portal, .../billing/storage-addons. Billing is workspace storage-based — every plan includes unlimited team members.
Connectors
GET /v1/connectors, POST /v1/connectors/{provider}/oauth/start|callback, POST /v1/connectors/{id}/test. The foundation for pushing bugs into issue trackers — see Integrations.
MCP bridge
GET /v1/mcp/tools, POST /v1/mcp/tools/invoke, GET /v1/mcp/config, and bug helpers /v1/mcp/bugs/{bug_id}/context and /v1/mcp/bugs/{bug_id}/prompt. The token-authed REST bridge behind MCP.
Share links
GET /v1/public/share/bug/{share_id} — public, read-only access to a single bug for external sharing, governed by the same display rules as the dashboard.

Example requests

These examples use real paths to show the shape of typical calls. Field names in bodies are illustrative — consult the published reference for exact schemas.

List bugs for the authenticated user (dashboard, JWT):

GET /v1/bugs HTTP/1.1
Host: api.bugport.dev
Authorization: Bearer <supabase_session_jwt>

Fetch a single bug's detail:

GET /v1/bugs/{bug_id} HTTP/1.1
Host: api.bugport.dev
Authorization: Bearer <supabase_session_jwt>

Update a bug's status (drives the activity timeline):

curl -X PATCH https://api.bugport.dev/v1/bugs/{bug_id} \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{ "status": "in_progress" }'

Media upload flow

Screenshots, videos, attachments, HAR files, and heavy debug payloads are stored in Cloudflare R2, not in Postgres. Clients never stream binaries through the API — instead they request a presigned URL, upload directly to R2, and then tell BugPort the upload finished. This keeps the API fast and keeps large media off the database.

  1. Request a presigned upload URL

    Call POST /v1/media/upload-url (or /v1/widget/media/upload-url for widget submissions) describing the file you intend to upload. The response includes a short-lived URL pointing directly at R2.

    curl -X POST https://api.bugport.dev/v1/media/upload-url \
    -H "Authorization: Bearer $JWT" \
    -H "Content-Type: application/json" \
    -d '{ "filename": "screenshot.png", "content_type": "image/png" }'
  2. Upload the binary directly to R2

    Send the file bytes to the presigned URL using the HTTP method it specifies (typically PUT). The bytes go straight to Cloudflare R2 and never pass through BugPort's API or Postgres.

    curl -X PUT "<presigned_r2_url>" \
    -H "Content-Type: image/png" \
    --data-binary @screenshot.png
  3. Complete the upload

    Call POST /v1/media/complete (or /v1/widget/media/complete) to register the finished object so BugPort can attach it to a bug and generate thumbnails. After this, the media is retrievable via GET /v1/media/{media_id}.

💡
The browser extension uses the same three-step pattern through its /v1/plugin/media/upload-url and attachment upload-urls/complete/fail endpoints, with a fail step so partial uploads can be cleaned up.

Error model

The API uses standard HTTP status codes, and validation failures return a structured body rather than a bare string. Treat the status code as the primary signal and the body as detail.

StatusMeaningTypical cause
400Bad requestMalformed payload or invalid parameters.
401UnauthorizedMissing, expired, or invalid credential.
403ForbiddenValid credential, but out of scope — e.g. a widget key submitting from a disallowed origin, or a token reaching another workspace.
404Not foundUnknown resource, or a base URL missing the /v1 prefix.
422Validation errorRequest shape is understood but fails field validation; the body describes which fields.
429Too many requestsRate limited.
5xxServer errorRetry with backoff; check GET /v1/health.

A 403 almost always means a scoping problem, not a typo: the credential is valid but is not allowed to reach that data.

When to use API vs widget vs MCP

There are three ways to get data in and out of BugPort. Pick by who is calling and what they need to do.

🧩Widget

Use the public widget key when you want end users of a web app to file reports in-product. Submit-only, origin-restricted, safe in client-side code. See the NPM widget.

🔌API (JWT)

Use the Supabase JWT for full dashboard-grade access — building internal tools, scripting triage, exporting data, or managing workspaces and projects on behalf of a signed-in user.

🤖MCP

Use a scoped MCP token when an AI tool should read and act on bugs from your editor — triage, fix-prompt generation, status updates. Scoped, revocable, audited. See MCP.

A quick rule of thumb: widget is for collecting reports from users, the JWT API is for operating BugPort programmatically, and MCP is for letting AI assistants work with your bugs under tight, auditable scope.

Next steps