Opportunity & Project Phase Tracking

Improved: Phase instance timestamps on GET /opportunities/{opportunity_id}

Each entry in phaseInstances now includes startedAt and completedAt, matching the projects endpoint. Use these to compute time-in-stage or close dates without a separate lookup.

  • Only populated on GET /opportunities/{opportunity_id}; the list and search endpoints do not return phaseInstances and are unaffected.
  • completedAt is null for the phase the opportunity currently occupies and any that have not been started.

New: Filter GET /opportunities/GET /projects (and their /search variants) by phase entry/exit date

Find records by when they entered or exited a phase — e.g. “opportunities that entered Closed - Won this month” — in a single query, without paging through the whole list.

ParamDescription
entered_phase_idPhase template ID to filter by entry date. Required when entered_after/entered_before is given; given alone, matches records that ever entered the phase.
entered_after / entered_beforeISO 8601 date or date-time bounds on when the record entered entered_phase_id (before is exclusive).
exited_phase_idPhase template ID to filter by exit date. Required when exited_after/exited_before is given; given alone, matches records that have exited the phase at any point.
exited_after / exited_beforeISO 8601 date or date-time bounds on when the record exited exited_phase_id (before is exclusive).
  • Look up a phase’s template ID via the phaseTemplateId on any phaseInstances entry from GET /opportunities/{opportunity_id}, GET /projects/{project_id}, or GET /workflows.
  • A *_phase_id is required whenever its matching date bound(s) are supplied; omitting it returns a 400 VALIDATION_ERROR. Invalid date formats also return 400.
  • When entered_phase_id/exited_phase_id is used, each row in the response also includes phaseTimeline — the record’s full phase-transition history in compact form (phaseTemplateId, startedAt, completedAt), so you can see when it entered/exited without a second call per record. Not present on unfiltered list/search calls, and not the same as phaseInstances (no names/positions/SLA — use the get-by-id endpoint for that).

Fixed: phaseInstances on GET /opportunities/{opportunity_id} was unordered and missing SLA fields

phaseInstances now returns in position order and includes phaseTemplate.redSla/yellowSla, matching the projects endpoint and the documented schema.

Fixed: primaryPhone list filter required an exact match

GET /opportunities/GET /projects (and their /search variants) primaryPhone filter did an exact match against the raw stored phone value, so any human-entered format (dashes, spaces, parens) returned nothing. Now normalizes to digits and substring-matches against both the record’s own phone and its linked contact’s phone.