---
title: "List projects"
method: GET
path: "/projects"
tags: ["projects"]
---

# List projects

`GET /projects`

## Request

- Base URL: `https://api.cogram.com/v1`
- URL: `https://api.cogram.com/v1/projects`
- Auth: HTTP bearer

List projects in the organization.

Returns a paginated list of projects. By default, only active (non-archived)
projects are returned. Set include_archived=true to include archived projects.

Pass ``include=counts`` to populate ``related_counts`` on every project in
the page. The counts for the whole page cost one extra query regardless of
``page_size``.

Pass ``include=members`` to populate ``members`` on every project in the
page. Also one extra query for the whole page, but unlike counts the
response grows with the rosters it carries.

Pass ``updated_since`` to poll for change instead of reading the whole
organization. It answers "what changed", not "what changed to what": the
response carries each project's current state, so a caller tracking a field
— an archive flip, a rename, a Unanet custom field — compares it against
what it last saw. Two properties make that safe to build on:

- **It also reports creations.** A project created inside the window has no
  ``updated_at`` yet, and is matched on ``created_at`` instead. One cursor
  therefore covers both, and a caller needs no second poll for new projects.
- **The bound is inclusive and the order is oldest change first.** Store the
  newest ``updated_at`` (or ``created_at``) you were served and pass it back;
  you may be re-served that one row, and you will not miss one that shares
  its timestamp across a page boundary.

It does not report **deletions** — a deleted project is simply absent from
later responses. Archiving is not a deletion: an archived project is still
listed under ``include_archived=true``, with ``is_active: false``.

## Query parameters

- `include` ProjectIncludeV1[]: Optional response sections. `counts` populates `related_counts`; `members` populates `members`. Omitted sections come back as null. Repeat the param (`include=counts&include=members`) or comma-separate the values (`include=counts,members`) — both work.
- `page` integer: Page number (1-indexed).
- `page_size` integer
- `include_archived` boolean
- `updated_since` string, date-time, nullable: Only return projects created or changed at or after this instant, oldest change first. Must include a UTC offset (e.g. 2026-08-17T18:03:11Z). Compares against `updated_at`, falling back to `created_at` for a project that has never been updated.

## Response `200`

Successful Response

- ProjectListV1: Paginated project list response.
  - `data` ProjectV1[], required
    - `id` string, required
    - `name` string, required
    - `description` string, nullable, required
    - `external_id` string, nullable, required: Your system's ID for this project.
    - `client_name` string, nullable, required
    - `project_address` string, nullable, required
    - `is_active` boolean, required: False once the project is archived.
    - `email_filing_enabled` boolean, required
    - `meeting_filing_enabled` boolean, required: Whether Cogram suggests this project for meetings and, where the organization allows it, files meetings into it. Only takes effect while the organization has meeting filing turned on. Filing a meeting here by hand works either way.
    - `is_public` boolean, required: If true, project appears in org project directory; non-members can request access.
    - `unanet_project_id` string, nullable, required: Unanet ERP A/E project id, for projects synced from Unanet; null for every other project. Stable across customer re-codes, so join on this rather than on external_id.
    - `unanet_lead_stage` string, nullable, required: Unanet lead/pursuit stage name (e.g. "06-Submitted Proposal"). Set means an open opportunity; null means a normal project.
    - `unanet_custom_fields` object, nullable, required: The project's Unanet user-defined fields, keyed by Unanet fieldName. Replaced in full on each sync, so a cleared field disappears.
    - `unanet_market_sectors` ProjectMarketSectorV1[], nullable, required: The project's Unanet market sectors and their percentage split, in Unanet's order. Null for projects that aren't synced from Unanet or have no sectors.
      - `name` string, required: Sector name as configured in Unanet, e.g. "Healthcare".
      - `pct` number, nullable: Percent of the project attributed to this sector.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, nullable, required: Tracks the project record only — not the emails, RFIs, meetings or other records filed under it. Null if never updated.
    - `related_counts` ProjectRelatedCountsV1: How many records of each type the project holds. Every field defaults to 0, so a project with nothing filed under it returns zeros rather than omitting keys. Counts cover the record types an all-time export of the same project ships, with one exception: soft-deleted meetings are excluded, because they are absent from every other read path. An export narrowed to a time range ships a subset of what is counted here. Cogram's seeded example records are counted; they are real rows in the project. Usable as a change signal: poll, compare against the previous values, and re-export the projects that moved. Three classes of change are invisible to it, because cardinality does not move — an in-place edit (a renamed document, a corrected email subject); a new revision of an existing drawing, or action items generated for an existing meeting, since only the top-level record is counted; and an add plus a delete inside the same polling window, which nets to zero.
      - `emails` integer
      - `documents` integer
      - `drawings` integer
      - `drawing_sets` integer
      - `submittals` integer
      - `rfis` integer
      - `meetings` integer
      - `reports` integer
      - `observations` integer
      - `transmittals` integer
    - `members` ProjectMemberSummaryV1[], nullable: Users holding a role on this project, ordered by email. Present only when the request asked for it with ?include=members; null otherwise, so existing callers pay nothing for it. An empty list means the project genuinely has no members — distinguishable from null, which means they were not requested. Group-granted access is not listed; only direct per-user roles are.
      - `user_id` string, uuid, required
      - `email` string, required
      - `role` 'MEMBER' | 'OWNER' | 'LEAD' | 'VIEWER', required: Per-project role: OWNER manages membership, LEAD manages the project but not membership, MEMBER read/write, VIEWER read-only.
  - `total` integer, required: Total matches across all pages, not just this page.
  - `page` integer, required
  - `page_size` integer, required

## Other responses

- `401`: Unauthorized
- `422`: Validation error
- `429`: Rate limit exceeded
- `500`: Internal server error

---

[API](https://skmtc.dev/cogram/apis/cogram-api.md) · [All operations](https://skmtc.dev/cogram/apis/cogram-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cogram/apis/cogram-api/revisions/5a3c9c40fca5?raw)
