> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BrowsingSignal

> BrowsingSignal is an append-only infrastructure entity that records hourly deltas of provider-domain visits flushed by the Chrome extension

BrowsingSignal is an append-only infrastructure entity that records hourly deltas of provider-domain visits flushed by the Chrome extension. Each row represents one (workspace, domain, hour-bucket) tuple: the count of visits accumulated since the last successful flush from that extension session. The provider-scoring pipeline aggregates rows within a 24-hour window to produce the `signal_boost` factor that ranks providers higher for workspaces that recently visited their domain. Rows have no soft-delete column and are pruned by the daily rescore cron after 90 days. The table is not exposed via Hasura and is internal-only.

| Naming                          | Value                             |
| ------------------------------- | --------------------------------- |
| Object                          | BrowsingSignal                    |
| Resource type (JSON:API `type`) | `browsing_signal`                 |
| Collection / records root       | — <sub>(not a records root)</sub> |
| REST base                       | `/v1/browsing-signals`            |
| Entity class                    | `BrowsingSignal`                  |

<Note>
  **Internal object.** Not currently exposed on the public REST API. The operations below describe the intended contract.
</Note>

## API operations

| Operation | Method & path                      | Status     |
| --------- | ---------------------------------- | ---------- |
| List      | `GET /v1/browsing-signals`         | 🟡 Planned |
| Retrieve  | `GET /v1/browsing-signals/{id}`    | 🟡 Planned |
| Create    | `POST /v1/browsing-signals`        | 🟡 Planned |
| Update    | `PATCH /v1/browsing-signals/{id}`  | 🟡 Planned |
| Delete    | `DELETE /v1/browsing-signals/{id}` | 🟡 Planned |

## Data model

### Attributes

| Field                | Type                      | Required         | Constraints                                                             | Allowed values    | Description                                                                                                                                                                           |
| -------------------- | ------------------------- | ---------------- | ----------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| browsing\_signal\_id | 🔒 system — UUID (string) | ✅ Yes            | UNIQUE; generated via gen\_random\_uuid() at INSERT                     | Any valid UUID v4 | Public-facing stable identifier for this browsing signal row. Generated by the database on insert; never supplied by callers.                                                         |
| domain               | string (text)             | ✅ Yes            | NOT NULL; no max-length constraint at the DB level                      | —                 | The provider domain that was visited (e.g. pennylane.com, stripe.com). Written by the Chrome extension flush handler; identifies which provider the workspace was browsing.           |
| visit\_count         | integer                   | ✅ Yes            | NOT NULL; column type int                                               | —                 | Delta of visits to this domain recorded since the last successful flush from the Chrome extension. Used by the provider-scoring pipeline as the raw signal weight before aggregation. |
| last\_visited\_at    | timestamptz               | ✅ Yes            | NOT NULL                                                                | —                 | Timestamp of the most recent visit to this domain within the flush batch. Used to bound recency windows in the 24-hour aggregation pass.                                              |
| created\_at          | 🔒 system — timestamptz   | ⚪ No (defaulted) | DEFAULT now(); set by DB on insert; also set via MikroORM onCreate hook | —                 | Wall-clock timestamp of row insertion. Used as the composite index key for workspace-scoped time-range queries and as the pruning anchor for the 90-day cron cleanup.                 |

### Relationships

| Name      | Type               | Required | Description                                                                                                                                                                                                                                  |
| --------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| workspace | to-one (ManyToOne) | ✅ Yes    | The workspace whose Chrome extension produced this signal. Foreign key stored as workspace\_pk with ON DELETE CASCADE — deleting a workspace removes all its browsing signals. This is the tenant scope anchor for every query on the table. |

### System-computed

* browsing\_signal\_id: generated via gen\_random\_uuid() at INSERT — never supplied by the caller
* created\_at: set to now() by the database DEFAULT and additionally via MikroORM onCreate hook — not user-supplied
* No soft-delete (deleted\_at column is absent): this is an append-only table; rows are pruned physically by the daily rescore cron after 90 days, not soft-deleted
* No updated\_at column: the entity is append-only by design; rows are never mutated after insertion
* pk: internal serial primary key — exposed only in the composite index definitions; never surfaced in the API response
* workspace\_pk: FK column written by MikroORM from the workspace relationship — never set directly by the caller
* Two composite indexes maintained automatically by Postgres: idx\_browsing\_signals\_workspace\_created (workspace\_pk, created\_at DESC) and idx\_browsing\_signals\_workspace\_last\_visited (workspace\_pk, last\_visited\_at DESC)

## Example

```json theme={null}
{
  "data": {
    "type": "browsing_signal",
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "attributes": {
      "browsing_signal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "domain": "pennylane.com",
      "visit_count": 4,
      "last_visited_at": "2026-06-02T08:45:00.000Z",
      "created_at": "2026-06-02T09:00:00.000Z"
    },
    "relationships": {
      "workspace": {
        "data": { "type": "workspace", "id": "ws_9f3a8b2c-1234-5678-abcd-000000000001" }
      }
    }
  }
}
```

<sub>Source: `apps/api/src/database/entities/BrowsingSignal.ts` · domain: ingestion · tier: Infrastructure</sub>
