For the complete documentation index, see llms.txt. This page is also available as Markdown.

Cogram API

Integrate Cogram with your own systems: automate project management, sync user data, and build custom integrations against a stable REST API.

Overview

The REST API lives on its own subdomain (api.cogram.com), authenticates with API keys, and exposes stable, versioned endpoints built for third-party integrations.

Key features:

  • Stable versioned endpoints - The API is versioned (/v1/) to ensure backwards compatibility

  • Interactive documentation - Explore and test endpoints directly at api.cogram.com/v1/docs

  • External ID mapping - Link Cogram resources to your own system's identifiers

Authentication

The Cogram API uses API key authentication. API keys are organization-scoped and can be created by organization administrators.

Creating an API Key

  1. Click Create API Key.

  2. Give your key a descriptive name and optionally set an expiration date.

  3. Copy the key immediately. It will only be shown once.

Using Your API Key

Include the API key in the Authorization header as a Bearer token:

Optional Headers

X-Forwarded-User: Identifier of the user in your system (e.g., user ID or email) who triggered this action. When provided, Cogram's audit logs will attribute the action to this user rather than just the API key. Omit for automated system tasks with no associated user.

Base URL

All API requests should be made to:

Available Endpoints

Projects

Manage projects within your organization.

Method
Endpoint
Description

GET

/v1/projects

List all projects

POST

/v1/projects

Create a new project

GET

/v1/projects/{id}

Get a specific project

PATCH

/v1/projects/{id}

Update a project

DELETE

/v1/projects/{id}

Archive a project

Project create and update requests, and all project responses, include an is_public field. When true, the project is discoverable: it appears in the organization-wide project directory and non-members can request access. When false, the project is not listed in the directory. See the interactive API docs for full request/response schemas.

Record counts per project

Add ?include=counts to either GET /v1/projects or GET /v1/projects/{id} to populate a related_counts object on each project: how many emails, documents, drawings, drawing sets, submittals, RFIs, meetings, reports, observations, and transmittals are filed under it. Every field is present and defaults to 0, so a project with nothing filed returns zeros rather than omitting keys. To request more than one section at once, see Members per project below.

Without the parameter, related_counts is null and no counting work is done — existing integrations are unaffected. The counts for a whole page cost one additional query regardless of page_size, so requesting them on a 100-project page is no more expensive than on one project.

Counts are commonly used to notice that a project has changed since a previous poll: fetch a page, compare against the values you stored last time, and act on the projects that moved. Three kinds of change are invisible to them, because the number of records does not change:

  • An edit that leaves the record in place — a renamed document, a corrected email subject.

  • A new revision of an existing drawing, or action items generated for an existing meeting. Only the top-level record is counted, so drawings and meetings stay flat.

  • An addition and a deletion inside the same polling interval, which cancel out.

Soft-deleted meetings are excluded, matching the rest of the API. Cogram's built-in example records are counted — they are real records in the project.

Members per project

Add ?include=members to either GET /v1/projects or GET /v1/projects/{id} to populate a members array on each project. Every entry carries the member's user_id, email, and project role (OWNER, LEAD, MEMBER, or VIEWER), ordered by email so two polls can be compared without sorting first. LEAD is a management role — like OWNER but without the ability to add or remove members or delete the project — and appears only for organizations that have it enabled.

Match on user_id, not email — two accounts can share an email address, and user_id is what the membership endpoints below are keyed by. Display names are not included; fetch GET /v1/projects/{id}/members when you need them.

Without the parameter, members is null and no membership work is done — existing integrations are unaffected. A project that genuinely has no members returns [], which is how you tell "nobody is on it" apart from "I didn't ask".

include takes more than one section in a single request, spelled either way — repeat the parameter (?include=members&include=counts) or comma-separate the values (?include=members,counts). A name that matches no section is still rejected with a 422, so a typo does not fail silently.

Two things the array does not cover:

  • Group-granted access is not listed. Only direct per-user roles appear. Someone who can reach the project because their group was added to it is not a member here.

  • Users banned from your organization are omitted, matching GET /v1/projects/{id}/members.

The roster for a whole page costs one additional query regardless of page_size, but unlike counts the response itself grows with every member it carries — a 100-project page returns the roster of all 100. If you only need one project's members, ask on GET /v1/projects/{id}.

Polling for changed projects

Add ?updated_since=<timestamp> to GET /v1/projects to get only the projects that changed, instead of paging your whole organization to find out. Use it to keep an external system — a file server, a document store, a data warehouse — in step with Cogram.

The timestamp must carry a UTC offset (2026-08-17T18:03:11Z or 2026-08-17T20:03:11+02:00); one without an offset is rejected with 422. Remember to URL-encode it — a raw + in a query string means a space.

It answers what changed, not what changed to what. Each project comes back in its current state, so hold the values you care about and compare. That is what tells you a project was archived, renamed, or had a Unanet field rewritten.

Four properties make it safe to build a loop on:

  • It reports creations too. A project created inside the window has no updated_at yet, so it is matched on created_at instead. One cursor covers both, and you do not need a second poll for new projects.

  • Results are ordered oldest change first, so a project modified while you are paging is appended rather than shifted into a page you already read.

  • The bound is inclusive. Store the newest updated_at (or created_at, for a project that has never been updated) you were served and pass it back next time. You may be served that one project again; you will not miss another that shares its timestamp across a page boundary.

  • Archiving is a change, not a disappearance — but only if you ask for it. Pass include_archived=true, or an archived project drops out of the response and reads as if it were deleted.

Two limits to design around:

  • Deletions are not reported. A deleted project is simply absent from later responses. If that matters, reconcile against a full listing on a slower schedule.

  • updated_at tracks the project record only — its name, client, archive state, and Unanet fields. Filing an email or a drawing under a project does not change it. For that, use ?include=counts above.

Project Members

Manage project membership and roles.

Method
Endpoint
Description

GET

/v1/projects/{id}/members

List project members

PUT

/v1/projects/{id}/members/{user_id}

Add or update a member

DELETE

/v1/projects/{id}/members/{user_id}

Remove a member

Assigning the LEAD role (on create-with-members or PUT .../members/{user_id}) requires the role to be enabled for your organization; otherwise the request returns 422. OWNER, MEMBER, and VIEWER are always assignable.

Project Types

Manage the organization's project-type taxonomy (e.g. "Design-Build", "CM at Risk"). Project types are configured in the app at Organization Settings → Projects → Project Types; the API exposes the same CRUD.

Method
Endpoint
Description

GET

/v1/project-types

List all project types

POST

/v1/project-types

Create a new project type

GET

/v1/project-types/{id}

Get a specific project type

PATCH

/v1/project-types/{id}

Update a project type

DELETE

/v1/project-types/{id}

Delete a project type

Deleting a type clears the type on any project that was using it (the project is not deleted). See the interactive API docs for full request/response schemas.

Users

Manage organization members.

Method
Endpoint
Description

GET

/v1/users

List organization members

PATCH

/v1/users/{user_id}

Update a user's role

Data Exports

Request and download a multi-part zip export of your organization's data: projects, meetings, emails, documents, drawings, reports, observations, transmittals, and Procore-synced submittals and RFIs. Builds run asynchronously; once complete, each part is delivered via short-lived signed URLs (1-hour validity, regenerated on every status read).

For an end-to-end walkthrough of what's inside each zip, see Data Export Package Layout. For the UI equivalent of these endpoints, see Data Exports.

Data Exports are admin-equivalent: only admins or owners can issue API keys, and the endpoints can only be invoked by keys issued for the same organization. Manage keys at Organization Settings → Integrations → API Keys.

Method
Endpoint
Description

POST

/v1/data-exports

Create a new data export (returns immediately)

GET

/v1/data-exports

List data exports for the organization (paginated)

GET

/v1/data-exports/{id}

Get one data export's status and download URLs

POST

/v1/data-exports/{id}/cancel

Cancel a pending or running data export

GET /v1/data-exports returns the standard paginated envelope { "data": [...], "total": N, "page": N, "page_size": N }. Use ?page=N&page_size=N (defaults: page=1, page_size=50, max page_size=100) to walk through the audit trail.

Meeting audio recordings are never exported. Each meeting in the zip ships as meeting.json, meeting.md, the rendered meeting.docx (when your org has a default template; see Data Exports → templates), and transcript.json (when the meeting was transcribed). Photos and uploaded meeting attachments are included; audio is intentionally kept inside Cogram.

Request body: POST /v1/data-exports

Field
Type
Description

project_ids

array[string] | null

Optional. Scope the export to these projects — list candidate ids with GET /v1/projects (add ?include_archived=true to reach archived projects, which are still exportable). Org-wide exports (when omitted) must specify a time range under 6 calendar months.

time_filter

object

Discriminated by mode. {"mode": "range", "start": <iso>, "end": <iso>} or {"mode": "all_time"}. all_time is project-only.

Response fields

Field
Type
Description

id

string

Unique identifier (dex_...)

organization_id

string

Org the export belongs to

source

string

ui or api: how the export was created

requester_user_id

UUID | null

User who triggered it (UI source only)

requester_api_key_id

string | null

API key that triggered it (API source only)

requester_forwarded_user

string | null

Value of the X-Forwarded-User header at create time (audit trail)

requester_display

string

Human-readable attribution: user's name, API key '<name>', or <forwarded_user> (via API key '<name>') when both are present

project_ids

array[string]

Scope as requested; empty for org-wide exports

projects

array

Scope projects that still exist, with id, name, and customer_project_id

time_filter

object

Echo of the requested filter

status

string

pending, running, cancelling, cancelled, completed, failed, or expired

error

string | null

Failure reason when status == "failed"

requested_at

datetime

When the export was created

started_at

datetime | null

When the build worker began

completed_at

datetime | null

When the build finished (success, failure, or cancellation)

expires_at

datetime | null

When the parts will be deleted from storage (7 days after completed_at for completed)

parts

array

Empty until status == "completed"; one entry per zip part with a freshly-signed URL

Each entry in parts includes:

Field
Type
Description

id

string

Unique part identifier (dxp_...)

part_index

integer

Zero-indexed position within the multi-part set

size_bytes

integer

Compressed part size

signed_url

string

One-hour pre-signed download URL; re-fetch on every read

Polling pattern

The zip's internal structure, including folders, file types, and how to identify Cogram's example/demo data (the is_dummy field on JSON entities, and the X-Cogram-Is-Dummy header on .eml files), is documented in Data Export Package Layout.

Date ranges are inclusive of the end timestamp. The backend filter is <= on the end value you supply. If you want "everything modified through May 31", send end=2026-06-01T00:00:00Z (the next-day UTC midnight). Sending 2026-05-31T00:00:00Z would only capture exactly midnight on May 31, not the full day. The Cogram UI does this conversion for you when you pick a date in the date-picker; API callers must do it themselves.

Cap rule: Org-wide exports (no project_ids) are limited to a 6-month range and reject all_time. Project-scoped exports have no time-range limit. A violation returns 400 with "error": "range_too_large" (typed ErrorCode).

Project scope: A project id your organization doesn't own returns 404 with "error": "not_found" — the same response as an id that doesn't exist anywhere, so the API never reveals another organization's projects. One bad id rejects the whole request, so you never receive a zip that silently covers fewer projects than you asked for. The check runs before the export is accepted, so a rejected scope costs you nothing: no export row is created and the one-active-export slot stays free for an immediate retry.

Deprecated project_id: the original single-project field is still accepted and normalizes to a one-element project_ids. Sending both fields returns 422. New integrations should use project_ids.

Concurrency: At most one active export (pending, running, or cancelling) per organization, enforced both at the application layer and by a Postgres partial unique index. A second POST /v1/data-exports while an active one exists returns 409 with "error": "data_export_in_progress" and the existing export's id woven into the message. Poll that id and re-issue once it terminates.

Cancellation: pending exports cancel immediately. running exports transition to cancelling; the worker stops between zip parts and the next status read reflects the terminal state. Terminal statuses (completed, failed, cancelled, expired) return 409 conflict.

Stuck-row recovery: If a worker dies hard mid-build (rare), a periodic sweep transitions rows stuck in running/cancelling past the build-deadline cap (~6 hours) to failed so the per-org concurrency slot doesn't stay blocked. Retry by creating a new export.

Backup Runs

Report each backup run performed by the Cogram connector, so Cogram can tell you when backups stop.

Cogram cannot detect a backup that never starts — a connector that has stopped cannot report that it stopped. Instead, the connector tells Cogram when a run finishes and how often it is scheduled to run. If a scheduled run is missed — no successful run within that interval — Cogram emails the recipients configured at Organization Settings → Integrations → Backups. You get one email per outage, and it resets after the next successful run.

You do not need to call this endpoint yourself if you use the Cogram connector — it reports its own runs.

Method
Endpoint
Description

POST

/v1/backup-runs

Report that a backup started or finished

Request body: POST /v1/backup-runs

Field
Type
Description

event

string

started when a run begins, finished when it ends.

schedule_interval_hours

integer

How often the backup is scheduled to run, in hours (1–8760). Cogram measures health against this.

status

string | null

success or failed. Required on finished, and must be omitted on started.

projects_synced

integer | null

Optional. Projects filed by this run. Zero is valid — a run with no changed projects is healthy.

error

string | null

Optional failure detail, up to 2000 characters. Only meaningful when status is failed.

Example

Only successful runs count as healthy. A failed run tells Cogram the connector is alive, but not that your data is backed up, so it does not clear a stale-backup alert.

Alerts are off until you add recipients. Configure them under Organization Settings → Integrations → Backups. An empty recipient list turns alerting off.

Action Items

Retrieve action items extracted from meetings within your organization.

Method
Endpoint
Description

GET

/v1/action-items

List action items

The list endpoint supports the following query parameters:

Parameter
Type
Default
Description

page

integer

1

Page number (1-indexed)

page_size

integer

50

Items per page (max 100)

status

string

Filter by status: SUGGESTED, ACCEPTED, REJECTED, or COMPLETED

include_archived

boolean

false

Set to true to include archived items

project_id

string

Filter to items from meetings linked to this project

meeting_id

string

Filter to items from a specific meeting

assignee

string

Case-insensitive partial match on assignee name

q

string

Case-insensitive search in item description

sort_by

string

created_at

Sort field: created_at, due_date, status, assignee, or description

sort_dir

string

asc

Sort direction: asc or desc

When filtering by project_id, action items from meetings not linked to any project are excluded. Unknown project_id or meeting_id values return an empty list, not a 404 error.

Items with no value in nullable sort fields (due_date, assignee, status, description) are always placed at the bottom, regardless of sort direction.

Response fields

Each action item in the data array includes:

Field
Type
Description

id

string

Unique identifier

description

string | null

Action item text

assignee

string | null

Assigned person's name

status

string | null

SUGGESTED, ACCEPTED, REJECTED, or COMPLETED

origin

string | null

AUTO (AI-extracted) or MANUAL (user-created)

due_date

datetime | null

Due date in ISO 8601 format

completed_at

datetime | null

Timestamp when marked done

archived

boolean

Whether the item has been archived

meeting_id

string

ID of the meeting this item came from

meeting_name

string | null

Name of the meeting

project_id

string | null

ID of the project the meeting belongs to, if any

created_at

datetime

Creation timestamp in ISO 8601 format

updated_at

datetime | null

Last update timestamp

Board Items

Retrieve kanban board items: action items that have been promoted to a project's board, either manually by users or automatically via meeting reconciliation. Read-only in v1.

Method
Endpoint
Description

GET

/v1/board-items

List board items

GET

/v1/board-items/{id}

Get a single item with history

Filtering and search

The list endpoint supports the following query parameters:

Parameter
Type
Default
Description

page

integer

1

Page number (1-indexed)

page_size

integer

50

Items per page (max 100)

status

string

Filter by status: TODO, IN_PROGRESS, BLOCKED, DONE, or DISMISSED

include_archived

boolean

false

Set to true to include archived items

project_id

string

Filter to items in this project

meeting_id

string

Filter to items originated from this meeting (matches source_meeting_id)

meeting_series_uid

string

Filter to items tied to a recurring meeting series

assignee

string

Case-insensitive partial match on assignee name

q

string

Case-insensitive search across title and body

sort_by

string

created_at

Sort field: created_at, updated_at, last_status_changed_at, status, title, or assignee

sort_dir

string

asc

Sort direction: asc or desc

Unknown project_id or meeting_id values return an empty list, not a 404 error.

Items with no value in nullable sort fields (assignee, last_status_changed_at) are always placed at the bottom, regardless of sort direction.

The list endpoint excludes archived items by default. The get-by-id endpoint returns archived items.

Response fields

Each board item in the data array (and in the detail response) includes:

Field
Type
Description

id

string

Unique identifier (pbi_...)

project_id

string

ID of the project this item belongs to

project_name

string

Name of the project, for convenience

title

string

Short item title

body

string | null

Optional long-form markdown description

status

string

TODO, IN_PROGRESS, BLOCKED, DONE, or DISMISSED

priority

string | null

URGENT, HIGH, NORMAL, or LOW; null if unset

due_date

date | null

Calendar due date in ISO 8601 (YYYY-MM-DD); null if unset

assignee

string | null

Free-text assignee name (may differ from linked user's display name)

assignee_user_id

UUID | null

Cogram user the item is assigned to, if matched

source_meeting_id

string | null

Meeting this item was originated from, if any

source_meeting_name

string | null

Name of the source meeting

meeting_series_uid

string | null

Identifier of the recurring meeting series, if applicable

archived

boolean

Whether the item has been archived

created_at

datetime

Creation timestamp in ISO 8601 format

updated_at

datetime | null

Last update timestamp

last_status_changed_at

datetime | null

Timestamp of the most recent history entry, or null if none

History (detail endpoint only)

GET /v1/board-items/{id} additionally returns a history array of timeline entries for the item, ordered oldest-first. Each entry represents a BoardItemHistoryEntry row, which may record a status transition, an assignee change, a priority shift, a due-date move, a comment, or any combination.

Field
Type
Description

id

string

Unique identifier (bsc_...)

changed_at

datetime

When the change was applied

changed_by_user_id

UUID | null

Cogram user who made the change; null for automated (LLM) changes

changed_by_user_name

string | null

Display name of the user, for convenience

triggered_by_meeting_id

string | null

Meeting that triggered this change, if any

triggered_by_meeting_name

string | null

Name of the triggering meeting

previous_status

string | null

Status before the change (null on the initial creation entry, or on a non-status row)

new_status

string | null

Status after the change. null on rows that aren't status transitions (assignee, priority, due-date, or pure-comment rows)

previous_assignee

string | null

Free-text assignee before the change

new_assignee

string | null

Free-text assignee after the change

previous_assignee_user_id

UUID | null

Linked user before the change

new_assignee_user_id

UUID | null

Linked user after the change

previous_priority

string | null

Priority before the change (URGENT, HIGH, NORMAL, LOW, or null)

new_priority

string | null

Priority after the change

previous_due_date

date | null

Due date (ISO YYYY-MM-DD) before the change

new_due_date

date | null

Due date after the change

reason

string | null

LLM-authored justification (only set on reconciliation-triggered rows)

comment

string | null

User-authored comment, if any

To distinguish entry types from a single row:

  • Status transition: previous_status != new_status

  • Assignee change: new_assignee != previous_assignee (or the *_user_id equivalents)

  • Priority change: new_priority != previous_priority

  • Due-date change: new_due_date != previous_due_date

  • Comment: comment is set and there is no other delta

  • Automated change: changed_by_user_id is null and triggered_by_meeting_id is set

Rate Limiting

API requests are rate limited on a per-key basis. The default limit is 1000 requests per minute.

When you exceed the rate limit, the API returns a 429 Too Many Requests response with a Retry-After header indicating when you can retry.

Error Handling

The API uses standard HTTP status codes and returns errors in a consistent JSON format:

Common Error Codes

HTTP Status
Error Code
Description

400

range_too_large

Org-wide Data Export exceeded the 6-month cap (or used all_time)

401

api_key_missing

No API key provided

401

api_key_invalid

API key not found or incorrect

401

api_key_expired

API key has expired

404

not_found

Resource does not exist

409

conflict

Resource already exists

409

data_export_in_progress

Org already has an active Data Export; poll its id and retry

422

validation_error

Invalid request body

429

rate_limit_exceeded

Too many requests

This is not an exhaustive list. For all possible error responses per each endpoint and all error codes, see the interactive documentation at api.cogram.com/v1/docs.

Pagination

List endpoints support pagination using query parameters:

Parameter
Default
Max
Description

page

1

-

Page number (1-indexed)

page_size

50

100

Number of items per page

Example:

Paginated response format:

API Reference

For detailed endpoint documentation, request/response schemas, error codes and an interactive API explorer, visit:

api.cogram.com/v1/docs

Security Best Practices

  • Keep your API keys secret - Never expose them in client-side code or public repositories

  • Use environment variables - Store API keys in environment variables, not in code

  • Rotate keys regularly - Create new keys and revoke old ones periodically

  • Use descriptive names - Name your keys by their purpose to track usage

  • Revoke unused keys - Delete keys that are no longer needed

Support

Questions or issues with the API? Email support@cogram.com or use the in-app Help menu.

Last updated

Was this helpful?