> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# GetLeadTimelineAPI mirrors the erdo_get_lead_timeline MCP tool.




## OpenAPI

````yaml /api/openapi.json get /v1/datasets/{datasetSlug}/leads/{leadRef}/timeline
openapi: 3.0.0
info:
  title: Erdo API
  description: >-
    Erdo's REST API: query and write datasets, run agents, manage threads,
    integrations, pages, evals, workstreams, experiments, and bounded outreach.
    Authenticate with a Bearer API key (erdo_api_...) or scoped token
    (erdo_token_...).
  version: '2026-09-22'
servers:
  - url: https://api.erdo.ai
    description: Production
security:
  - bearerAuth: []
paths:
  /v1/datasets/{datasetSlug}/leads/{leadRef}/timeline:
    get:
      summary: |
        GetLeadTimelineAPI mirrors the erdo_get_lead_timeline MCP tool.
      operationId: GET:mcp.GetLeadTimelineAPI
      parameters:
        - allowEmptyValue: true
          explode: false
          in: path
          name: datasetSlug
          required: true
          schema:
            type: string
          style: simple
        - allowEmptyValue: true
          explode: false
          in: path
          name: leadRef
          required: true
          schema:
            type: string
          style: simple
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  canonical_lead_id:
                    type: string
                  dataset_id:
                    type: string
                  dataset_slug:
                    type: string
                  emails:
                    items:
                      type: string
                    type: array
                  entries:
                    items:
                      $ref: '#/components/schemas/leadaction.TimelineEntry'
                    type: array
                  lead_reference:
                    type: string
                  phones:
                    items:
                      type: string
                    type: array
                  row:
                    additionalProperties:
                      type: string
                    type: object
                  timing:
                    $ref: '#/components/schemas/leadaction.TimingFacts'
                  unavailable:
                    additionalProperties:
                      type: string
                    description: >
                      listed here is unknown, never empty — the evaluation is
                      told so.
                    title: >
                      Unavailable names each source that could not be read and
                      why. A source
                    type: object
                type: object
          description: Success response
        default:
          $ref: '#/components/responses/APIError'
components:
  schemas:
    leadaction.TimelineEntry:
      properties:
        at:
          format: date-time
          type: string
        detail:
          type: string
        direction:
          title: |
            Direction is inbound (the lead acted), outbound (we acted) or empty.
          type: string
        from_playbook:
          description: >
            text it sent, a call it placed. The fingerprint leaves those out
            while they are fine, because a contact the engine made is not the
            lead moving underneath the decision — the revisit time is what
            decides when to follow one up. Counting them made the engine
            supersede its own approved cards: a call placed at 15:52 reached the
            timeline a minute later, the next tick read the new record as "the
            lead changed", re-decided, and closed the card somebody had said yes
            to in between. A delivery that FAILED is still in the hash, because
            a bounced email or a rejected text changes what to do.


            How a contact is recognized as ours differs by channel and both
            tests are exact. An email carries the context the mailer stamped on
            it; a text and a call are matched against the record this lead's own
            decision rows stored as their execution reference — the message id,
            the call id. A salesperson ringing or texting the lead from their
            own phone matches neither, and that is the point: it is news the
            engine has to react to. See markEngineContacts.
          title: >
            FromPlaybook marks a contact this engine made ITSELF: an email it
            sent, a
          type: boolean
        kind:
          title: >
            Kind is capture, email, email_reply, sms, call, chat, booking or
            suggestion.
          type: string
        ref:
          title: >
            Ref identifies the underlying record (an email id, a call id, a
            session id).
          type: string
        status:
          type: string
        summary:
          type: string
      title: |
        TimelineEntry is one thing that happened with a lead, in any channel.
      type: object
    leadaction.TimingFacts:
      description: >
        the platform so "no response four days after the second email" is
        matched on numbers rather than on date arithmetic the model does.
      properties:
        awaiting_our_reply:
          description: >
            It is narrower than LastResponseAt by one kind: a booking is a
            response and owes no reply.
          title: >
            AwaitingOurReply is the lead having SAID something we have not
            answered.
          type: boolean
        captured_at:
          format: date-time
          type: string
        days_since_capture:
          format: int64
          type: integer
        days_since_last_outbound:
          format: int64
          type: integer
        days_since_last_response:
          format: int64
          type: integer
        last_meeting_at:
          format: date-time
          type: string
        last_outbound_at:
          format: date-time
          type: string
        last_outbound_by_channel:
          additionalProperties:
            format: date-time
            type: string
          description: >
            counting only what went out: the per-channel gaps are measured from
            here.
          title: >
            LastOutboundByChannel is when we last reached out on each contact
            channel,
          type: object
        last_response_at:
          description: >
            call, a chat, a text or a booking. A capture is excluded: it is the
            lead arriving, and a second one is the same person filling the same
            form in again. It used to be called last\_inbound\_at and counted
            captures, which told contactWindow the lead had responded since our
            last email and told the prompt "never answered on any channel" one
            line above "the lead responded after our last email".
          format: date-time
          title: >
            LastResponseAt is when the lead last ANSWERED us — a reply, an
            inbound
          type: string
        lead_zone:
          allOf:
            - $ref: '#/components/schemas/leadaction.LeadZone'
          description: |
            Erdo worked it out. See lead\_timezone.go.
          title: >
            LeadZone is the clock a text or a call to this lead is timed in, and
            how
        local_time:
          type: string
        next_meeting_at:
          format: date-time
          type: string
        next_working_window_start:
          format: date-time
          type: string
        now:
          format: date-time
          type: string
        outbound_messages:
          format: int64
          type: integer
        phone_hours:
          allOf:
            - $ref: '#/components/schemas/leadaction.ContactWindow'
          description: >
            development's days and hours in the lead's own timezone. It equals
            the development's reading whenever the lead's zone could not be
            determined.
          title: |
            PhoneHours is the same working-hours window read in LeadZone: the
        reachability:
          description: >
            "confirmed" from then on, permanently. It is derived here, once, so
            every gate, the prompt and the decision read the same answer.
          title: >
            Reachability is "unconfirmed" until the lead answers on any channel
            and
          type: string
        reached_at:
          format: date-time
          type: string
        reached_via:
          description: >
            ("email\_reply", "call", "chat", "sms", "booking"), empty while
            unconfirmed.
          title: >
            ReachedVia is the kind of the first inbound that confirmed
            reachability
          type: string
        timezone:
          description: >
            DEVELOPMENT's working-hours reading, which is what an email is timed
            against. A text and a call are timed against PhoneHours instead.
          title: >
            Timezone, LocalTime, WithinWorkingHours and NextWorkingWindowStart
            are the
          type: string
        unanswered_outbound_in_a_row:
          description: >
            answer since the lead last responded. Only a response resets it; a
            capture does not, or a lead who re-submits a form is never four
            attempts deep.
          format: int64
          title: >
            UnansweredOutboundInARow is how many of our messages have gone
            without an
          type: integer
        within_working_hours:
          type: boolean
      title: >
        TimingFacts are the counts and clock readings a schedule rule needs,
        made by
      type: object
    leadaction.LeadZone:
      description: >
        evidence it was derived from. Timezone is empty exactly when Source is
        ZoneSourceDevelopment.


        Nothing here is ever derived from a person's name or the language they
        wrote in. Those correlate with a country and say nothing about where
        somebody is; a lead who writes in Portuguese may be sitting in Miami,
        and acting on that guess would be both wrong and the kind of inference
        the platform does not make about people.
      properties:
        evidence:
          title: |
            Evidence is the reading in words, for a card and for an operator.
          type: string
        source:
          type: string
        timezone:
          type: string
      title: >
        LeadZone is the clock a text or a call to one lead is timed against, and
        the
      type: object
    leadaction.ContactWindow:
      description: |
        now in a given zone, and when the window next opens if it is not.
      properties:
        local_time:
          type: string
        next_working_window_start:
          format: date-time
          title: |
            NextStart is set only when the window is shut.
          type: string
        timezone:
          type: string
        within_working_hours:
          title: |
            Within is whether the window is open at the moment it was read.
          type: boolean
      title: >
        ContactWindow is one working-hours reading: whether contact is allowed
        right
      type: object
  responses:
    APIError:
      content:
        application/json:
          schema:
            externalDocs:
              url: https://pkg.go.dev/encore.dev/beta/errs#Error
            properties:
              code:
                description: Error code
                example: not_found
                externalDocs:
                  url: https://pkg.go.dev/encore.dev/beta/errs#ErrCode
                type: string
              details:
                description: Error details
                type: object
              message:
                description: Error message
                type: string
            title: APIError
            type: object
      description: Error response
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).

````