Deals

A deal is an opportunity your team is working, such as a sale, a renewal, or an engagement, with one counterparty: a contact or an organization. Every deal sits in a stage of a pipeline. Moving it to a won or lost stage closes it; moving it back to an open stage reopens it. Use this resource to track deals and report on them.

A deal is visible to your key exactly when its pipeline is, and anyone who can see a deal can edit or delete it. Carom records every stage move, whether a person made it or an agent did.

Carom’s deal agent, auto_deal, reads incoming email and proposes new deals and stage moves as proposals. It works the pipelines whose dealmaker_enabled is true, and runs only while its agent configuration is enabled.

#The deal object

Returned by every deal endpoint. Retrieving a single deal adds activity previews and the stage history; updating one returns the stage history too.

Attributes

  • idstring · uuid

    Unique identifier for the deal.

  • namestring
  • descriptionstring · nullable
  • statusstring

    The outcome of the deal’s current stage. You change it by moving the deal to another stage, not directly.

    openwonlost
  • amountnumber · nullable

    What the deal is worth, in currency. Zero or more, and less than 10,000,000,000,000.

  • currencystring

    ISO 4217 code for amount, such as USD, the default.

  • expected_close_datestring · date · nullable

    The day your team expects the deal to close.

  • closed_atstring · date-time · nullable

    When the deal entered a won or lost stage. Null while the deal is open.

  • lost_reasonstring · nullable

    Why the deal was lost. Cleared when the deal moves to an open or won stage.

  • pipeline_idstring · uuid

    The pipeline the deal belongs to. Set when the deal is created and never changes.

  • pipeline_stage_idstring · uuid

    The stage the deal is in, one of the pipeline’s stages.

  • contact_idstring · uuid · nullable

    The counterparty, when it is a person. Exactly one of contact_id and organization_id is set.

  • organization_idstring · uuid · nullable

    The counterparty, when it is an organization.

  • contactobject · nullable

    A summary of the counterparty contact. Null when the counterparty is an organization or when the contact isn’t visible to your key.

    Show 7 child attributesHide child attributes
    • idstring · uuid
    • namestring · nullable
    • contact_typestring

      As on the contact object.

    • globalboolean · nullable

      True for a global contact, visible to the whole account; false for a personal one.

    • user_idstring · uuid · nullable

      The user who owns the contact.

    • backgroundstring · nullable

      Free-form notes about the person.

    • avatarobject · nullable

      The contact’s picture, as on the contact object.

  • organizationobject · nullable

    A summary of the counterparty organization. Null when the counterparty is a contact.

    Show 5 child attributesHide child attributes
    • idstring · uuid
    • namestring · nullable
    • descriptionstring · nullable
    • domainstring · nullable
    • logoobject · nullable
  • owner_user_idstring · uuid · nullable

    The user responsible for the deal. Null when the deal has no owner.

  • created_by_user_idstring · uuid · nullable
  • pinnedboolean · nullable

    Whether your key’s user has pinned this deal.

  • created_atstring · date-time
  • updated_atstring · date-time
  • tagsarray of tags · nullable

    Tags applied to this deal.

  • transitionsarray of objects

    The deal’s stage history, oldest first. The first entry records the stage the deal was created in. Present only on Retrieve and Update responses.

    Show 8 child attributesHide child attributes
    • idstring · uuid
    • from_stageobject · nullable

      The stage the deal left, with its id, name, and position. Null on the first entry. position is null for a stage that has since been removed.

    • to_stageobject

      The stage the deal entered, with the same fields.

    • directionstring · nullable

      Whether the move went later or earlier in the pipeline, judged by the stages’ current positions. Null on the first entry and when either stage has since been removed.

      forwardbackward
    • occurred_atstring · date-time
    • changed_by_user_idstring · uuid · nullable

      The user who made the move, or on whose behalf the proposal that made it was applied.

    • proposal_idstring · uuid · nullable

      The proposal that made the move. Null for moves a person made directly. The id stays set after the proposal is deleted.

    • agentstring · nullable

      The agent that wrote that proposal, such as the deal agent. Null for moves a person made directly.

      assistantauto_dealauto_task
  • counts, tasks, events

    Activity previews, present only on Retrieve a deal.

The deal object
{
  "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
  "name": "Wildgrove portfolio renewal",
  "description": "Three-year renewal covering all 14 Wildgrove properties.",
  "status": "open",
  "amount": 48000.0,
  "currency": "USD",
  "expected_close_date": "2026-10-15",
  "closed_at": null,
  "lost_reason": null,
  "pipeline_id": "5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86",
  "pipeline_stage_id": "c3e6a9b4-5d8f-4cab-9e3b-7f4dab2c6e93",
  "contact_id": null,
  "organization_id": "6b0e2d9a-4c31-4f8e-a7d2-90c4e1b7f5a3",
  "contact": null,
  "organization": {
    "id": "6b0e2d9a-4c31-4f8e-a7d2-90c4e1b7f5a3",
    "name": "Wildgrove Property Management",
    "description": "Manages residential and mixed-use buildings in the East Bay.",
    "domain": "wildgrove.example",
    "logo": null
  },
  "owner_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
  "created_by_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
  "pinned": false,
  "created_at": "2026-09-02T17:20:05.006Z",
  "updated_at": "2026-09-16T15:02:44.646Z",
  "tags": [
    {
      "id": "0f1e2d3c-4b5a-4968-8776-655443322110",
      "name": "Customer",
      "color": "#66b185",
      …
    }
  ]
}
get/deals Read key

#List deals

Returns the deals visible to your key, 50 per page by default, newest first. Filter by pipeline, stage, status, counterparty, owner, dates, or tags. All filters combine with AND.

This list uses page-number pagination.

Query parameters

  • slicestring

    A preset view. mine: deals your key’s user owns. open: open deals. closing-soon: open deals with an expected close date no more than 30 days away, including overdue ones, sorted by expected_close_date, earliest first. closed: won and lost deals, most recently closed first.

    mineopenclosing-soonclosed
  • querystring

    Match against deal names and descriptions. At most 500 characters.

  • pipeline_idstring

    Comma-separated pipeline ids.

  • pipeline_stage_idstring

    Comma-separated stage ids.

  • statusstring

    Comma-separated statuses, such as won,lost.

    openwonlost
  • contact_idstring · uuid

    Deals whose counterparty is this contact.

  • organization_idstring · uuid

    Deals whose counterparty is this organization.

  • contact_ids, organization_idsstring

    Comma-separated ids. A deal matches if its counterparty is any of the listed contacts or any of the listed organizations.

  • owner_user_idstring

    Comma-separated user ids.

  • expected_close_beforestring · date

    Deals expected to close on or before this day.

  • closed_afterstring · date

    Deals closed on or after this day.

  • started_after, started_beforestring · date

    Deals created on or after, or on or before, this day, as YYYY-MM-DD.

  • tagsstring

    Comma-separated tag ids, at most 50. A deal matches if it carries any of them.

  • pageintegerdefault 1
  • per_pageintegerdefault 50

    At most 100; larger values are reduced to 100.

Returns

  • collectionobject

    A page of deals and where it sits in the full list.

    Show 8 child attributesHide child attributes
    • recordsarray of deals

      Up to per_page deals, without transitions.

    • pageinteger
    • per_pageinteger
    • total_resultsinteger · nullable

      Deals matching the filters, across all pages.

    • pagesinteger · nullable
    • sortstring

      The field the list is sorted by: created_at, or expected_close_date and closed_at for the closing-soon and closed slices.

    • orderstring
    • slice_keystring · nullable

      The slice you sent, echoed back. Null when you sent none.

Errors

  • 400invalid_requestA parameter is malformed, for example an id that isn’t a UUID, an unknown slice or status, a date not in ISO 8601 form, or a query longer than 500 characters.
get/deals
curl "https://api.carom.io/deals?slice=closing-soon&pipeline_id=5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86" \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "collection": {
    "records": [
      {
        "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
        "name": "Wildgrove portfolio renewal",
        "status": "open",
        "amount": 48000.0,
        "expected_close_date": "2026-10-15",
        …
      },
      {
        "id": "3c9e1a7f-5b2d-4c8e-a6f0-2d4b8e1c7a53",
        "name": "Halvorsen Freight dispatch pilot",
        "status": "open",
        "amount": 12500.0,
        "expected_close_date": "2026-10-09",
        …
      }
    ],
    "slice_key": "closing-soon",
    "sort": "expected_close_date",
    "order": "asc",
    "per_page": 50,
    "page": 1,
    "total_results": 2,
    "pages": 1
  }
}
post/deals Write key

#Create a deal

Creates a deal in a pipeline your key can see. Give exactly one counterparty, a contact_id or an organization_id. Without a pipeline_stage_id, the deal starts in the pipeline’s first stage.

The deal’s status comes from the stage’s outcome, so a deal created in a won or lost stage is closed immediately.

Request body application/json

  • dealobjectrequired
    Show 10 child attributesHide child attributes
    • namestringrequired
    • pipeline_idstring · uuidrequired
    • pipeline_stage_idstring · uuid

      An active stage of that pipeline. Defaults to its first stage.

    • contact_idstring · uuid

      Required unless you give organization_id. Not both.

    • organization_idstring · uuid

      Required unless you give contact_id. Not both.

    • descriptionstring
    • amountnumber

      Zero or more, and less than 10,000,000,000,000.

    • currencystringdefault USD

      The ISO 4217 code of a currency in circulation, in uppercase, such as EUR. Null or omitted means USD.

    • expected_close_datestring · date
    • owner_user_idstring · uuid

      A user in your account. Without it, the deal has no owner.

Returns

The new deal object, wrapped in deal, with status 201.

Errors

  • 400invalid_requestA field failed validation, for example a missing name, a currency that isn’t an ISO 4217 code, or both or neither of contact_id and organization_id. error.fields says which.
  • 404record_not_foundThe pipeline isn’t visible to your key, the stage isn’t an active stage of it, the contact isn’t visible to your key, or the organization or owner doesn’t exist in your account.
post/deals
curl https://api.carom.io/deals \
  -H "Authorization: Bearer $CAROM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deal": {
      "name": "Wildgrove portfolio renewal",
      "pipeline_id": "5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86",
      "organization_id": "6b0e2d9a-4c31-4f8e-a7d2-90c4e1b7f5a3",
      "amount": 48000,
      "expected_close_date": "2026-10-15",
      "owner_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e"
    }
  }'
Response201
{
  "deal": {
    "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
    "name": "Wildgrove portfolio renewal",
    "status": "open",
    "amount": 48000.0,
    "currency": "USD",
    "expected_close_date": "2026-10-15",
    "pipeline_id": "5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86",
    "pipeline_stage_id": "a1c4e7f2-3b6d-4a8e-9c1f-5d2b8e0a4c71",
    "organization_id": "6b0e2d9a-4c31-4f8e-a7d2-90c4e1b7f5a3",
    "owner_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
    "created_at": "2026-09-02T17:20:05.006Z",
    …
  }
}
get/deals/{id} Read key

#Retrieve a deal

Returns a single deal with its stage history, up to five open tasks, and up to three upcoming events with the counterparty.

Path parameters

  • idstring · uuidrequired

    The deal’s id.

Returns

The deal object, wrapped in deal, with transitions and these additional attributes.

  • countsobject

    Full-set totals behind the tasks and events previews.

    Show 2 child attributesHide child attributes
    • open_tasksinteger

      Open tasks attached to the deal and visible to your key.

    • upcoming_eventsinteger

      Events with the counterparty that start today or later in your key’s user’s time zone.

  • tasksarray of tasks

    Up to five open tasks attached to the deal.

  • eventsarray of calendar events

    Up to three upcoming events with the counterparty, soonest first.

Errors

  • 404record_not_foundNo deal with that id is visible to your key.
get/deals/{id}
curl https://api.carom.io/deals/8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17 \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "deal": {
    "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
    "name": "Wildgrove portfolio renewal",
    "status": "open",
    "pipeline_stage_id": "c3e6a9b4-5d8f-4cab-9e3b-7f4dab2c6e93",
    …
    "counts": {
      "open_tasks": 1,
      "upcoming_events": 1
    },
    "tasks": [
      {
        "id": "e7f8a9b0-c1d2-4e3f-8a4b-5c6d7e8f9a01",
        "assignment": "Send the Q4 renewal proposal",
        "due": "2026-09-24",
        …
      }
    ],
    "events": [
      {
        "id": "b8c9d0e1-f2a3-4b4c-9d5e-6f7a8b9c0d12",
        "name": "Wildgrove renewal review",
        …
      }
    ],
    "transitions": [
      {
        "id": "f1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
        "changed_by_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
        "proposal_id": null,
        "agent": null,
        "direction": null,
        "occurred_at": "2026-09-02T17:20:05.006Z",
        "from_stage": null,
        "to_stage": {
          "id": "a1c4e7f2-3b6d-4a8e-9c1f-5d2b8e0a4c71",
          "name": "Lead",
          "position": 0
        }
      },
      {
        "id": "0b1c2d3e-4f5a-4b6c-9d7e-8f9a0b1c2d3e",
        "changed_by_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
        "proposal_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
        "agent": "auto_deal",
        "direction": "forward",
        "occurred_at": "2026-09-16T15:02:44.646Z",
        "from_stage": {
          "id": "a1c4e7f2-3b6d-4a8e-9c1f-5d2b8e0a4c71",
          "name": "Lead",
          "position": 0
        },
        "to_stage": {
          "id": "c3e6a9b4-5d8f-4cab-9e3b-7f4dab2c6e93",
          "name": "Proposal",
          "position": 2
        }
      }
    ]
  }
}
patch/deals/{id} Write key

#Update a deal

Changes the fields you send and leaves the rest alone. Send null to clear a nullable field; currency can’t be null. A deal always keeps exactly one counterparty, so to switch from an organization to a contact, send the new contact_id together with "organization_id": null.

To move the deal, send a pipeline_stage_id from the same pipeline. A deal can’t change pipelines, and a pipeline_id in the body is ignored. Carom sets status from the new stage’s outcome and records the move in transitions. Moving into a won or lost stage sets closed_at; moving back to an open stage clears it. Moving to an open or won stage clears lost_reason.

Path parameters

  • idstring · uuidrequired

Request body application/json

  • dealobjectrequired

    Any of the attributes accepted by Create a deal except pipeline_id, plus lost_reason.

    Show 1 child attributeHide child attributes
    • lost_reasonstring

      Why the deal was lost. Send it with a move to a lost stage, or while the deal is in one. A later move to an open or won stage clears it.

Returns

The updated deal object, wrapped in deal, with transitions.

Errors

  • 400invalid_requestA field failed validation, for example a null or unknown currency, or the change would leave the deal with both or neither counterparty. error.fields says which.
  • 404record_not_foundNo deal with that id is visible to your key, the stage isn’t an active stage of the deal’s pipeline, a new contact isn’t visible to your key, or the organization or owner doesn’t exist in your account.
patch/deals/{id}
curl -X PATCH https://api.carom.io/deals/8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17 \
  -H "Authorization: Bearer $CAROM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deal": {
      "pipeline_stage_id": "d4f7bac5-6e9a-4dbc-8f4c-8a5ebc3d7fa4",
      "amount": 51500
    }
  }'
Response200
{
  "deal": {
    "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
    "name": "Wildgrove portfolio renewal",
    "status": "won",
    "amount": 51500.0,
    "pipeline_stage_id": "d4f7bac5-6e9a-4dbc-8f4c-8a5ebc3d7fa4",
    "closed_at": "2026-09-18T20:41:37.911Z",
    …
    "transitions": [
      …
      {
        "id": "6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f",
        "changed_by_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
        "proposal_id": null,
        "agent": null,
        "direction": "forward",
        "occurred_at": "2026-09-18T20:41:37.911Z",
        "from_stage": {
          "id": "c3e6a9b4-5d8f-4cab-9e3b-7f4dab2c6e93",
          "name": "Proposal",
          "position": 2
        },
        "to_stage": {
          "id": "d4f7bac5-6e9a-4dbc-8f4c-8a5ebc3d7fa4",
          "name": "Won",
          "position": 3
        }
      }
    ]
  }
}
delete/deals/{id} Write key

#Delete a deal

Permanently deletes a deal. Anyone who can see the deal can delete it.

The deal’s stage history, the comments on it, and its pins are deleted with it, and its tags are removed. Tasks attached to the deal are kept and no longer point to it, including tasks your key can’t see. Pending proposals about the deal expire.

Path parameters

  • idstring · uuidrequired

Returns

An empty response with status 204.

Errors

  • 404record_not_foundNo deal with that id is visible to your key.
delete/deals/{id}
curl -X DELETE https://api.carom.io/deals/8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17 \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response204
No content
get/deals/summary Read key

#Summarize deals

Totals the deals that List deals would return for the same filters: how many are open and what they’re worth, and how many were won in the last 30 days and what those were worth.

A deal counts as won in the window when its closed_at falls within it. Filters that exclude open or won deals, such as slice=closed or slice=open, make those counts and values 0.

Values add up amount as stored, without converting currencies, and count only deals in pipelines that track amounts. Deal counts include every matching deal.

Query parameters

  • slice, query, pipeline_id, pipeline_stage_id, contact_id, organization_id, contact_ids, organization_ids, owner_user_id, expected_close_before, closed_after, started_after, started_before, tags

    As on List deals. There is no status filter, because the summary reports open and won deals side by side.

Returns

  • summaryobject
    Show 5 child attributesHide child attributes
    • open_countinteger

      Open deals.

    • open_valuenumber

      Total amount of open deals.

    • won_countinteger

      Deals won within the last won_window_days.

    • won_valuenumber

      Total amount of those won deals.

    • won_window_daysinteger

      Currently 30.

Errors

  • 400invalid_requestA parameter is malformed, as on List deals.
get/deals/summary
curl "https://api.carom.io/deals/summary?pipeline_id=5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86" \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "summary": {
    "open_count": 12,
    "open_value": 214500.0,
    "won_count": 3,
    "won_value": 61000.0,
    "won_window_days": 30
  }
}
get/deals/report Read key

#Retrieve the deal report

Returns one flat row per deal across every pipeline visible to your key, for building your own reports. The report holds every open deal and every deal won or lost in the last 24 months, up to 5,000 deals. It takes no parameters; filter and aggregate the rows yourself.

Open deals are always included. If the total would exceed 5,000, Carom leaves out the oldest closed deals first and says so with truncated, omitted_count, and window_start. For a single pipeline with stage history, use Retrieve a pipeline report.

Returns

  • reportobject
    Show 4 child attributesHide child attributes
    • truncatedboolean

      True when closed deals were left out to stay within 5,000.

    • omitted_countinteger

      How many closed deals were left out.

    • window_startstring · date

      The report includes every deal closed on or after this day: 24 months ago, or later when closed deals were left out.

    • dealsarray of objects

      Open deals newest first, then closed deals most recently closed first.

      Show 13 child attributesHide child attributes
      • idstring · uuid
      • namestring
      • statusstring
        openwonlost
      • pipeline_idstring · uuid
      • pipeline_stage_idstring · uuid
      • amountnumber · nullable
      • expected_valuenumber · nullable

        For an open deal, amount times its stage’s likelihood; the full amount for a won deal; 0 for a lost one. Null when the deal has no amount or its pipeline doesn’t track amounts.

      • owner_user_idstring · uuid · nullable
      • created_atstring · date-time
      • closed_atstring · date-time · nullable
      • expected_close_datestring · date · nullable
      • current_stage_entered_atstring · date-time · nullable

        When an open deal last entered its current stage. Null for closed deals.

      • lost_reasonstring · nullable
get/deals/report
curl https://api.carom.io/deals/report \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "report": {
    "truncated": false,
    "omitted_count": 0,
    "window_start": "2024-09-19",
    "deals": [
      {
        "id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
        "name": "Wildgrove portfolio renewal",
        "status": "open",
        "pipeline_id": "5e8a1c3f-7b2d-4f6e-a9c0-3d1b7e4f2a86",
        "pipeline_stage_id": "c3e6a9b4-5d8f-4cab-9e3b-7f4dab2c6e93",
        "amount": 48000.0,
        "expected_value": 33600.0,
        "owner_user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
        "created_at": "2026-09-02T17:20:05.006Z",
        "closed_at": null,
        "expected_close_date": "2026-10-15",
        "current_stage_entered_at": "2026-09-16T15:02:44.646Z",
        "lost_reason": null
      },
      …
    ]
  }
}
get/deals/{id}/discussion Read key

#Retrieve a deal’s discussion

Returns the comments your team has left on this deal and on its counterparty, grouped into scopes. Comments are internal; the counterparty never sees them.

The first scope is the deal itself and is always present. When the counterparty is an organization, the next scopes are the organization and the people who work there; when it’s a contact, the next scope is that contact. Those scopes appear only when they hold at least one comment visible to your key. Every visible comment is included; the discussion isn’t paginated.

Path parameters

  • idstring · uuidrequired

Returns

  • discussionobject
    Show 1 child attributeHide child attributes
    • scopesarray of objects

      One entry per group of records: the deal first, then the others.

      Show 6 child attributesHide child attributes
      • keystring
        dealorganizationpeoplecontact
      • labelstring

        A heading for the scope: This deal, the counterparty’s name, or People at followed by the organization’s name. Display text; use key to identify a scope.

      • unitstring

        A plural noun for the scope’s records, for display: deals, organizations, or people.

      • primaryboolean

        True for the deal’s own scope.

      • total_commentsinteger
      • recordsarray of objects

        The commented records in the scope, most recently discussed first.

        Show 6 child attributesHide child attributes
        • keystring

          The record type and id, joined by a hyphen.

        • record_typestring
          dealorganizationcontact
        • record_idstring · uuid
        • namestring · nullable
        • total_commentsinteger
        • commentsarray of comments

          Oldest first.

Errors

  • 404record_not_foundNo deal with that id is visible to your key.
get/deals/{id}/discussion
curl https://api.carom.io/deals/8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17/discussion \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "discussion": {
    "scopes": [
      {
        "key": "deal",
        "label": "This deal",
        "unit": "deals",
        "primary": true,
        "total_comments": 1,
        "records": [
          {
            "key": "deal-8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
            "record_type": "deal",
            "record_id": "8f2b6d4a-1c9e-4a7b-b3d5-0e6f8a2c4d17",
            "name": "Wildgrove portfolio renewal",
            "total_comments": 1,
            "comments": [
              {
                "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
                "html_content": "<p>Legal signed off on the three-year term. Sending paper Monday.</p>",
                "text": null,
                "user_id": "9c41d2e8-3f6a-4b7c-8d1e-5a2f7b9c0d3e",
                "created_at": "2026-09-17T16:05:31.821Z",
                …
              }
            ]
          }
        ]
      },
      {
        "key": "people",
        "label": "People at Wildgrove Property Management",
        "unit": "people",
        "primary": false,
        "total_comments": 1,
        "records": [
          …
        ]
      }
    ]
  }
}