# Problem reports

> How an automation lists open problem reports sent from signed-in TradeCatalog pages.

Signed-in supplier staff and buyers can send a bug or a piece of feedback from any page in the app. Each report is stored with status open and emailed to Happy Webs.

This page is for the automation that picks those reports up. It is not a workspace API key, and it is not in the OpenAPI document.

## Authenticate

The request uses a bearer token. The token is the Worker secret `FEEDBACK_REPORTS_TOKEN`.

Set it once, out of git:

```bash
wrangler secret put FEEDBACK_REPORTS_TOKEN
```

For local development, put the same name in `.dev.vars`. Do not commit the value.

Send it on every request:

```http
GET /api/feedback-reports?since=2026-10-15T00:00:00.000Z HTTP/1.1
Host: tradecatalog.app
Authorization: Bearer <FEEDBACK_REPORTS_TOKEN>
```

```bash
curl "https://tradecatalog.app/api/feedback-reports?since=2026-10-15T00:00:00.000Z" \
  -H "Authorization: Bearer $FEEDBACK_REPORTS_TOKEN"
```

If the secret is missing, or the header is missing or wrong, the response is `401` with `WWW-Authenticate: Bearer realm="feedback-reports"`. There is no cookie, no workspace API key, and no CORS.

**GET** `/api/feedback-reports`

## List open reports

`since` is required. It is an ISO-8601 time or a Unix time in milliseconds. The list is open reports created **after** that time, oldest first, up to 100.

| Name    | Type   | Required | Description                                                                                                                       |
| ------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `since` | string | yes      | Exclusive lower bound on `createdAt`.                                                                                             |
| `after` | string | no       | Report id. With `since`, also returns reports that share that exact time and sort after this id. Use both when `hasMore` is true. |

A report that has been closed is not in this list, even if it was created after `since`.

```json
{
  "reports": [
    {
      "id": "…",
      "kind": "bug",
      "status": "open",
      "message": "The price shows as zero.",
      "pageUrl": "https://tradecatalog.app/s/northgate/products",
      "tenantSlug": "northgate",
      "userId": "…",
      "role": "Owner",
      "email": "owner@example.com",
      "userAgent": "…",
      "viewport": "1440x900",
      "createdAt": "2026-10-15T12:00:00.000Z",
      "hasScreenshot": true,
      "screenshotUrl": "https://tradecatalog.app/api/feedback-reports/…/screenshot",
      "autoReplyText": null,
      "autoReplySentAt": null,
      "resolvedReplyText": null,
      "resolvedReplySentAt": null,
      "adminUrl": "https://tradecatalog.app/admin?section=reports&report=…"
    }
  ],
  "hasMore": false
}
```

Store the `createdAt` and `id` of the last report you kept. The next poll uses that time as `since`. If the page was full (`hasMore` is true), also pass that id as `after` and keep the same `since`, so a report saved in the same millisecond is not skipped.

## Screenshot

**GET** `/api/feedback-reports/{id}/screenshot`

The same bearer token. The response is the image, or `404` when that report has no screenshot. The id is the report id from the list. It is not an object key.

Platform admins can also open the screenshot while signed in at `/admin/feedback-reports/{id}`, and resolve a report under **Support → Reports**.

## Automatic reply

Saving a report emails the signed-in person one short note at their own address. Resolving it emails them once more, to say it is fixed and live. Each of those is sent at most once per report.

`autoReplyText` and `autoReplySentAt` are that first note. `resolvedReplyText` and `resolvedReplySentAt` are the resolved note. They stay null until the email has been sent.

Set the Worker variable `FEEDBACK_AUTO_REPLY` to `off` to stop both. `AUTO_REPLY_BCC` is a secret: the address copied on those emails. Leave it unset and nothing is copied. The wording is drafted with Workers AI. If that draft fails a check, a fixed note is sent instead.

Each report also becomes a numbered ticket. Resolving that ticket sends the same fixed-and-live email once. Mail to support@ or info@tradecatalog.app is forwarded to `SUPPORT_FORWARD_TO` before it is read, then filed the same way. If that address is missing, or the forward fails, the message is rejected so the sender can retry. It is attached to an existing account only when `Authentication-Results` shows a DMARC pass, or both DKIM and SPF when DMARC is absent. Otherwise the ticket stays unlinked and no acknowledgement is sent.
