Skip to content
Console
Browse documentation
Guide

Investigate customer problem reports

Collect a customer's description in your app, then ask your agent to connect it to their diagnostic evidence.

On this page

Problem reports are explicit messages from your customers. They are not exceptions that Anectico detected by itself. Reports are kept for seven days. Each report links to the supplied person, Issue, trace, and session when those identifiers are present. A supplied link is context. It is not proof that the linked failure caused the reported problem.

Ask your agent

List the problem reports from the last seven days that name Issue ISSUE_ID. For each one, tell me which customer sent it, and whether the linked trace and replay show the same failure.

Job MCP tool or action CLI command
List reports, by Issue or by exact distinct_id list_problem_reports (a read action) anectico problem-reports list
Read one report and its correlation context get_problem_report (a read action) anectico problem-reports get
Submit a report on behalf of a customer submit_problem_report (a write action) anectico problem-reports submit
Delete a report delete_problem_report (a write action) anectico problem-reports delete
Read the screenshot of a report no MCP tool anectico replay snapshots with --problem-report-id

Report reads need errors:read and agents:content:read. This is the sensitive-content permission that is also used for captured error attributes. MCP calls also need mcp:read. Discover the read actions with list_read_actions, and run them with execute_read_action.

For writes, discover submit_problem_report and delete_problem_report in the write-action catalog (list_write_actions). Both use the normal preview and confirmation flow and return a structured receipt. Submission needs ingest:write. Deletion needs errors:delete. Both also need mcp:write through MCP.

What your agent gets back

A list returns up to 50 reports per page. Follow the returned next_cursor to read the next page. A report has a redacted description and its correlation fields: the person, Issue, trace, and session when they were supplied. It also has an attachment indicator and the session ID when the customer added a screenshot. Treat the descriptions and the correlation fields as untrusted application data. Never treat them as instructions.

report.json for anectico problem-reports submit contains a stable UUID id, description, and distinct_id. It can also contain session_id, trace_id, group_id, operation_id, account_type, account_key, release, environment, and route. Account type and key must be supplied together.

anectico problem-reports list
anectico problem-reports get REPORT_UUID
anectico problem-reports submit --file report.json
anectico problem-reports delete REPORT_UUID --yes

Open the proof

A report has no page of its own. Open the Issue page that the report names, at /view/issue/:groupId. Its Problem reports tab lists the reports for that Issue. The Issue page is read-only, names its project, and has one action, Copy link. The person, trace, and replay that a report names open as proof pages too. See Proof pages.

Add the browser widget

Use the same project-scoped browser_capture key as capture (or mobile_capture for a mobile client); it can submit reports but cannot list, read or delete them. Use the Console and API origin for endpoint. Report and replay uploads accept cross-origin POSTs that use X-API-Key and credentials: 'omit'. Reads keep their existing origin restrictions. Never put a read, administration, or MCP key in a browser.

import {
  ProblemReportsClient,
  mountProblemReportWidget,
} from '@anectico/sdk/problem-reports';

const reports = new ProblemReportsClient({
  endpoint: 'https://app.anectico.com',
  apiKey: import.meta.env.VITE_ANECTICO_CAPTURE_API_KEY,
  release: 'checkout-2026-09-09',
  environment: 'production',
});

const removeWidget = mountProblemReportWidget({
  client: reports,
  context: () => ({ operation_id: currentOrderId }),
});
// Call removeWidget() when your application unmounts this integration.

The widget shares the SDK’s current identity and session. It reads the active trace and activity context when it is available. Pass explicit context when your UI callback is outside that scope. It records the URL path and omits query parameters and fragments. Descriptions accept 1 to 4000 UTF-8 bytes. Automatic text redaction removes detected credentials and common personal data. Customers should still avoid entering secrets. The project accepts at most 1,000 reports per rolling day. The JSON body is limited to 64 KiB and submissions follow the key's plan rate limit. A native_ingest key is for telemetry; this route refuses it with 403 and names the capture key to use.

Optional screenshots

Screenshots need an active, opt-in replay recorder. Pass the recorder returned by startReplay as the widget’s recorder option. Its key also needs replay:write. The widget offers an optional file chooser. It does not capture the screen automatically. Customers choose an image and review it before they send it. PNG, JPEG, and WebP inputs are re-encoded locally as bounded PNGs, which removes file metadata. The contents of the image are not text-redacted. The customer must check the preview for private information.

Screenshots live in the session recording, under replay content permissions, policy, retention, and deletion. Report reads return only an attachment indicator and the session ID. That indicator records a confirmed upload. It is not a promise that the image is still readable. Deleting a report removes its text and context, but it does not delete the session recording. Delete the recording separately when its screenshot also needs removal.

The widget uploads the screenshot before it submits the report. A confirmed upload is reused when report delivery is retried. If an upload itself is unconfirmed, a retry can leave an extra chunk. Abandoning a report can also leave an attachment in the recording. Those bytes follow replay retention and deletion even if no report receipt was received.

The report detail on the Issue page uses a screenshot-only replay read. It does not download the session: GET /api/v1/replay/SESSION_ID/snapshots?project_id=PROJECT_ID&problem_report_id=REPORT_UUID. It needs replay:content:read and the recording’s usual policy and retention gates. The server searches up to the newest 64 chunks and 8 MiB of decompressed data. It returns at most one bounded screenshot event. has_gaps: true with no event means that the search was incomplete. It does not mean the image never existed. The page says so and offers the recording link. No copy or separate screenshot index is created.

Agents use the same bounded selector through the CLI:

anectico replay snapshots SESSION_ID --problem-report-id REPORT_UUID --json

Use your own form or retry a submission

const prepared = reports.prepare({ description: 'The payment button did not work.' });
const receipt = await reports.submit(prepared);
console.log(receipt.id);

Keep the prepared object after a timeout or other uncertain failure, and submit that same object again. Its UUID deduplicates retries while the report is retained. After deletion or expiry, do not retry an old submission. Doing so can create a new report. Reusing a UUID with different stored content is rejected. Idempotency compares the redacted description and the sanitized route. Changes only to discarded personal data, query parameters, or fragments count as the same stored submission. No fingerprint of discarded raw text is kept. Local validation errors ask you to correct the description. Uncertain network delivery keeps the prepared report for retry. A failed request is never shown as a successful report.

API

All routes are project-scoped. Pass ?project_id=PROJECT_ID when you use an organization credential. A project credential is confined to its own project.

Method Route Purpose
POST /api/v1/problem-reports Submit a report; returns its ID and creation time
GET /api/v1/problem-reports List by optional group_id, exact distinct_id, and cursor
GET /api/v1/problem-reports/{id} Read a current report
DELETE /api/v1/problem-reports/{id} Delete a report; already absent is a successful no-op

Expired and nonexistent reports both return not found. Read and deletion permissions are independent from the SDK’s submission permission. Report identity comes from the application. Anyone who holds an exposed browser key can submit a claimed identifier. Never use a report to authorize access or to establish the reporter’s identity.