> For the complete documentation index, see [llms.txt](https://docs.molecule.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.molecule.xyz/api-reference/labs-api/browse-and-search.md).

# Browse & Search

Cross-cutting read operations that aren't scoped to a single lab you administer: browsing and reading labs and their files, full-text search, and activity feeds. Reads tied to a specific resource live with that resource — e.g. members and DID-link status in [Lab Management](/api-reference/labs-api/lab-management.md).

## Listing Labs & Activity

Query operations for listing all labs and reading their activity feeds. To read a single lab and its data-room files, see [Get Single Project with Files](/api-reference/labs-api/lab-management.md#get-single-project-with-files); to read one file, see [Get File by Path](/api-reference/labs-api/files.md#get-file-by-path).

### List All Projects

Onchain labs, with optional membership filtering and page-numbered pagination. Superseded by `labsConnection` for new consumers, which adds cursor pagination, sorting and richer filters.

Returns [`LabsResult!`](/api-reference/types.md#labsresult).

| Name            | Type                                                     | Description                                                                                                                                                                              |
| --------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `walletAddress` | `String`                                                 | Wallet address to filter labs by membership. When provided, returns labs where this wallet holds any active role (owner/contributor/viewer) unless narrowed by `role`.                   |
| `role`          | [`LabMemberRole`](/api-reference/types.md#labmemberrole) | Role filter, applied only when `walletAddress` is set, restricting the result to labs where that wallet holds this role. Omit to include every lab the wallet belongs to under any role. |
| `page`          | `Int`                                                    | Page number (0-indexed).                                                                                                                                                                 |
| `perPage`       | `Int`                                                    | Number of items per page (max 100).                                                                                                                                                      |

> **🔓 Public Endpoint**: The `labs` query does not require authentication. You only need a consumer credential — `Authorization: mol_<consumerId>_<secret>`, with **no `Bearer` prefix** — and no Service Token.

**GraphQL Query:**

```graphql
query ListProjects($walletAddress: String, $page: Int, $perPage: Int) {
  labs(walletAddress: $walletAddress, page: $page, perPage: $perPage) {
    nodes {
      oclId
      shortname
      name
      description
      labAccountAddress
      labNftTokenId
      latestContributionAt
      trlValue
      trlRationale
      isVerified
    }
    totalCount
    pageInfo {
      hasNextPage
      hasPreviousPage
      currentPage
      totalPages
    }
  }
}
```

> The `labs` list returns lightweight `LabRef` objects. Data-room contents and account details are not part of `LabRef` — fetch them per lab via [`labWithDataRoomAndFiles`](/api-reference/labs-api/lab-management.md#get-single-project-with-files).

**CMS-enriched fields (optional):**

These fields are sourced from the Molecule CMS and hydrated only when requested in the selection set. They are `null` when the project has no corresponding CMS entry.

| Field               | Type     | Description                                                                                                                        |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| trlValue            | String   | Technology Readiness Level (TRL) assessment for the project                                                                        |
| trlRationale        | String   | Explanation supporting the assigned TRL value                                                                                      |
| trlLastUpdated      | DateTime | Timestamp of the last change to `trlValue`                                                                                         |
| weightedScore       | Float    | AI-generated weighted project score derived from the `trlValue`                                                                    |
| scoreInterpretation | String   | Human-readable summary of the overall assessment behind `weightedScore`                                                            |
| criterionScores     | \[JSON]  | Per-criterion breakdown behind `weightedScore`. Each entry is a JSON object shaped like `{ "criterion": String, "score": Number }` |
| scoredAt            | DateTime | Timestamp the project scoring behind `weightedScore` was last computed                                                             |
| todos               | \[JSON]  | AI-generated action items for the lab, each a JSON object shaped like `{ "todo": String, "completed": Boolean }`                   |
| isVerified          | Boolean  | Whether the project has been verified by Molecule                                                                                  |

**Example Request:**

```bash
curl -X POST https://production.graphql.api.molecule.xyz/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_CONSUMER_CREDENTIAL' \
  -d '{
    "query": "query ListProjects($page: Int, $perPage: Int) { labs(page: $page, perPage: $perPage) { nodes { oclId shortname name labAccountAddress trlValue } totalCount pageInfo { hasNextPage currentPage totalPages } } }",
    "variables": {
      "page": 0,
      "perPage": 20
    }
  }'
```

### Project Activity Feed

Get the activity timeline for a specific project. This is a **public endpoint** - no authentication required.

Unfiltered, `nodes` is a `LabActivityNode` union that also includes `LabEventAnnouncement` entries. [Announcements are deprecated](/api-reference/changelog.md#announcements-are-deprecated), but labs created before the deprecation still carry them, so **pass `filter: FILE` if you want a file-only feed** and handle `__typename` defensively if you do not. `LabActivityFilter` accepts `FILE` and `ANNOUNCEMENT`.

> **🔓 Public Endpoint**: The `labActivity` query does not require authentication. You only need a consumer credential — `Authorization: mol_<consumerId>_<secret>`, with **no `Bearer` prefix** — and no Service Token.

**GraphQL Query:**

```graphql
query GetProjectActivity(
  $id: String!
  $page: Int!
  $perPage: Int!
  $filter: LabActivityFilter
) {
  labActivity(oclId: $id, page: $page, perPage: $perPage, filter: $filter) {
    pageInfo {
      hasNextPage
      hasPreviousPage
      currentPage
      totalPages
    }
    nodes {
      __typename
      ... on LabEventFileAdded {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
      ... on LabEventFileUpdated {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
      ... on LabEventFileRemoved {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
    }
  }
}
```

**Example Request:**

```bash
curl -X POST https://production.graphql.api.molecule.xyz/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_CONSUMER_CREDENTIAL' \
  -d '{
    "query": "query GetActivity($oclId: String!, $page: Int) { labActivity(oclId: $oclId, page: $page, perPage: 20) { pageInfo { hasNextPage currentPage totalPages } nodes { __typename ... on LabEventFileAdded { entry { path contentType version accessLevel changeBy eventTime } } } } }",
    "variables": {
      "oclId": "0x0101000000000000000000000000000000000000000000000000000000000042",
      "page": 0
    }
  }'
```

**Use Cases:**

* Project timelines showing what changed in a data room and when
* Download links for data-room files
* Encrypted file access (Onchain-Verified Envelope Encryption for new files)
* Projects with many file events (efficient pagination)

### Global Activity Feed

Get all activity across all projects. This is a **public endpoint** - no authentication required.

> **🔓 Public Endpoint**: The `activities` query does not require authentication. You only need a consumer credential — `Authorization: mol_<consumerId>_<secret>`, with **no `Bearer` prefix** — and no Service Token.

**GraphQL Query:**

```graphql
query GetActivities($page: Int, $perPage: Int, $filter: LabActivityFilter) {
  activities(page: $page, perPage: $perPage, filter: $filter) {
    activities {
      __typename
      ... on LabEventFileAdded {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
      ... on LabEventFileUpdated {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
      ... on LabEventFileRemoved {
        entry {
          ref
          path
          tags
          description
          version
          accessLevel
          eventTime
          systemTime
          changeBy
          categories
          contentType
          contentHash
          contentText
        }
      }
    }
  }
}
```

> **Errors**: a failed `activities` query returns `data: null` with a top-level GraphQL `errors[]` entry whose `errorType` is the error code — see [Errors](/api-reference/errors.md#failed-query).

***

## Searching Labs

Semantic search across labs' files and announcements. Public. A blank `prompt` fails with `VALIDATION_FAILED`, and an upstream outage fails with `UPSTREAM_UNAVAILABLE` or `TIMEOUT` rather than returning an empty page.

Returns [`SearchLabsResult!`](/api-reference/types.md#searchlabsresult).

| Name      | Type                                                             | Description                                                                                 |
| --------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `prompt`  | `String!`                                                        | Natural-language search text. Truncated to 2000 characters.                                 |
| `filters` | [`SearchLabsFilters`](/api-reference/types.md#searchlabsfilters) | Filters narrowing which labs, tags, categories, access levels and entry kinds are searched. |
| `page`    | `Int`                                                            | Page number, 0-indexed; negative or non-integer values fall back to 0.                      |
| `perPage` | `Int`                                                            | Hits per page, to a maximum of 100; values outside 1-100 and non-integers fall back to 10.  |

**GraphQL Query:**

```graphql
query SearchLabs(
  $prompt: String!
  $filters: SearchLabsFilters
  $page: Int
  $perPage: Int
) {
  searchLabs(
    prompt: $prompt
    filters: $filters
    page: $page
    perPage: $perPage
  ) {
    nodes {
      __typename
      ... on SearchLabsFileHit {
        entry {
          lab {
            oclId
            shortname
          }
          path
          file {
            did
            contentType
            accessLevel
            description
            tags
            categories
            downloadUrl
          }
        }
      }
    }
    totalCount
    pageInfo {
      hasNextPage
      hasPreviousPage
      currentPage
      totalPages
    }
  }
}
```

`SearchLabsHit` is a union of `SearchLabsFileHit` **and** `SearchLabsAnnouncementHit`. The examples below match only the file arm; if you handle the union exhaustively, expect the announcement `__typename` too — [announcements are deprecated](/api-reference/changelog.md#announcements-are-deprecated) but pre-existing ones are still indexed and still returned.

**Example - Basic Search:**

```bash
curl -X POST https://production.graphql.api.molecule.xyz/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_CONSUMER_CREDENTIAL' \
  -d '{
    "query": "query SearchLabs($prompt: String!, $page: Int, $perPage: Int) { searchLabs(prompt: $prompt, page: $page, perPage: $perPage) { nodes { __typename ... on SearchLabsFileHit { entry { lab { oclId shortname } path file { contentType description tags } } } } totalCount pageInfo { hasNextPage currentPage totalPages } } }",
    "variables": {
      "prompt": "cancer research",
      "page": 0,
      "perPage": 10
    }
  }'
```

**Example - Filtered Search:**

```bash
curl -X POST https://production.graphql.api.molecule.xyz/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: YOUR_CONSUMER_CREDENTIAL' \
  -d '{
    "query": "query SearchLabs($prompt: String!, $filters: SearchLabsFilters) { searchLabs(prompt: $prompt, filters: $filters) { nodes { __typename ... on SearchLabsFileHit { entry { path file { tags accessLevel } } } } totalCount } }",
    "variables": {
      "prompt": "experimental data",
      "filters": {
        "byAccessLevels": ["PUBLIC"],
        "byTags": ["research", "validated"]
      }
    }
  }'
```

**Understanding Results:**

Search results are returned as a union type. Use the `__typename` field to determine result type:

* **SearchLabsFileHit**: File search result
  * Access via: `entry.file`
  * Contains: file metadata, tags, categories, download URL

**JavaScript Example:**

```javascript
const searchResults = await fetch(apiUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": process.env.CONSUMER_CREDENTIAL,
  },
  body: JSON.stringify({
    query: `query SearchLabs($prompt: String!) {
      searchLabs(prompt: $prompt) {
        nodes {
          __typename
          ... on SearchLabsFileHit {
            entry {
              path
              file { description tags }
            }
          }
        }
        totalCount
      }
    }`,
    variables: { prompt: "latest results" },
  }),
});

const { nodes, totalCount } = (await searchResults.json()).data.searchLabs;

// Handle different result types
nodes.forEach((node) => {
  if (node.__typename === "SearchLabsFileHit") {
    console.log("File:", node.entry.path, node.entry.file.description);
  }
});
```

***

## Onchain Activity

### Onchain Activity Feed

Per-transaction onchain activity for a lab or wallet, newest first; distinct from labActivity, the data room feed. Requires at least one of `oclId` or `wallet`, AND-ed when both are given, and fails with VALIDATION\_FAILED when both are missing or `oclId` is malformed.

Returns [`[OnChainEvent!]!`](/api-reference/types.md#onchainevent).

| Name     | Type     | Description                                                                                                                                                  |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `oclId`  | `String` | Restrict the feed to this lab. 32-byte OCL id of the lab, `0x` plus 64 hex, case-insensitive.                                                                |
| `wallet` | `String` | Restrict the feed to events involving this wallet address, case-insensitive. Covers role grants and revocations for the account and transfers to or from it. |
| `limit`  | `Int`    | Maximum transaction groups to return, 1 to 200. Values outside that range fall back to 50.                                                                   |
| `cursor` | `String` | `id` of the last entry of the previous page. A value missing either half, or with a non-integer block number, is ignored and the first page returned.        |

For the flat, one-row-per-event stream, use `rawOnChainActivity` (same filters and cursor semantics).

```graphql
query OnChainActivity(
  $oclId: String
  $wallet: String
  $limit: Int
  $cursor: String
) {
  onChainActivity(
    oclId: $oclId
    wallet: $wallet
    limit: $limit
    cursor: $cursor
  ) {
    id
    chainId
    txHash
    blockNumber
    blockTimestamp
    type
    title
    args
    events {
      id
      contractAddress
      contractName
      eventName
      logIndex
      args
    }
  }
}
```

\* Provide at least one of `oclId` or `wallet`.

On a raw event, `contractName` is one of `accessresolver`, `ocl`, `ipnft`, `ipt` or `bio-agent`, and `args` is a JSON object of the decoded event arguments (BigInts as decimal strings, addresses lowercased).

## Related

* [Lab Management](/api-reference/labs-api/lab-management.md) — reads scoped to one lab
* [Files](/api-reference/labs-api/files.md) — reading a single file

{% hint style="info" %}
**Need help?**

Ask the team on Discord. Reporting an API error? Include the `requestId` from the failing response so we can trace it.

<a href="https://t.co/L0VEiy4Bjk" class="button primary" data-icon="discord">Ask on Discord</a>

*Page wrong, unclear or missing something? Let us know with **Was this helpful?** on this page.*
{% endhint %}
