> ## 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.

# EvaluateLeadNextActionAPI mirrors the erdo_evaluate_lead_next_action MCP


> tool. Nothing is stored and nothing is sent.




## OpenAPI

````yaml /api/openapi.json post /v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions/evaluate
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}/next-actions/evaluate:
    post:
      summary: |
        EvaluateLeadNextActionAPI mirrors the erdo_evaluate_lead_next_action MCP
      description: |
        tool. Nothing is stored and nothing is sent.
      operationId: POST:mcp.EvaluateLeadNextActionAPI
      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
      requestBody:
        content:
          application/json:
            schema:
              properties:
                agent_id:
                  title: >
                    AgentID tries the draft as that concierge. Read only
                    alongside body: a dry run

                    of the saved playbook uses its saved concierge.
                  type: string
                body:
                  title: >
                    Body is a draft playbook text to evaluate with instead of
                    the saved one.
                  type: string
              type: object
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  agent:
                    $ref: '#/components/schemas/leadaction.PlaybookAgent'
                  evaluation:
                    $ref: '#/components/schemas/leadaction.Evaluation'
                  read_back:
                    $ref: '#/components/schemas/leadaction.ReadBack'
                  timeline:
                    $ref: '#/components/schemas/leadaction.LeadTimeline'
                type: object
          description: Success response
        default:
          $ref: '#/components/responses/APIError'
components:
  schemas:
    leadaction.PlaybookAgent:
      properties:
        id:
          type: string
        name:
          type: string
        phone_number:
          description: |
            it has none.
          title: >
            PhoneNumber is the number the concierge calls and texts from, empty
            when
          type: string
        slug:
          type: string
      title: |
        PlaybookAgent is the concierge a playbook names, as the API returns it.
      type: object
    leadaction.Evaluation:
      properties:
        action_input:
          $ref: '#/components/schemas/leadaction.ActionInput'
        action_kind:
          type: string
        due_at:
          format: date-time
          type: string
        facts:
          $ref: '#/components/schemas/leadaction.LeadFacts'
        mode:
          type: string
        model:
          type: string
        priority:
          type: string
        priority_reason:
          type: string
        proposed_kind:
          description: |
            ActionKind when the platform turned the proposal into a card.
          title: >
            ProposedKind is what the model chose before enforcement; it differs
            from
          type: string
        rationale:
          type: string
        reachability:
          description: >
            this decision was made, and ReachedVia what reached them. They
            decide which set of contact gaps applied.
          title: >
            Reachability is whether the lead had ever answered on any channel
            when
          type: string
        reached_via:
          type: string
        rule:
          type: string
        rule_verified:
          type: boolean
        stage:
          type: string
      title: |
        Evaluation is one decision about one lead, after enforcement.
      type: object
    leadaction.ReadBack:
      description: >
        Only Actions\[].Mode and WorkingHours are enforced; the stages and the
        priority ladder are a picture of the text drawn for the person who wrote
        it, and every evaluation still reads the whole text.
      properties:
        accompanying_email:
          allOf:
            - $ref: '#/components/schemas/leadaction.ReadBackAccompaniment'
          description: >
            person — a text or a call — may also send the lead an email at once.
            Like the cadence it is always filled in, so a read-back stored
            before it existed answers with the default rather than with an empty
            string.
          title: |
            AccompanyingEmail is whether a decision this playbook gates behind a
        actions:
          items:
            $ref: '#/components/schemas/leadaction.ReadBackAction'
          type: array
        cadence:
          allOf:
            - $ref: '#/components/schemas/leadaction.ReadBackCadence'
          description: >
            contact gaps in each reachability state and the new-lead grace.
            Unlike the priority ladder it IS enforced, and normalization always
            fills it in, so a read-back stored before it existed reads back as
            today's behaviour rather than as zeros. See ReadBackCadence in
            reachability.go.
          title: |
            Cadence is how fast this playbook may work one lead: the per-channel
        limits:
          items:
            $ref: '#/components/schemas/leadaction.ReadBackLimit'
          type: array
        priority:
          allOf:
            - $ref: '#/components/schemas/leadaction.ReadBackPriority'
          description: |
            none. Nothing enforces it — see ReadBackPriority.
          title: >
            Priority is the ladder read out of the text, or null when the text
            states
        stages:
          items:
            $ref: '#/components/schemas/leadaction.ReadBackStage'
          type: array
        working_hours:
          $ref: '#/components/schemas/leadaction.WorkingHours'
      title: >
        ReadBack is what Erdo understood from a playbook's text when it was
        saved.
      type: object
    leadaction.LeadTimeline:
      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
      title: |
        LeadTimeline is everything the evaluation reads about one lead.
      type: object
    leadaction.ActionInput:
      description: |
        it runs, and exactly what an approval card shows.
      properties:
        accompanying_email:
          allOf:
            - $ref: '#/components/schemas/leadaction.AccompanyingEmail'
          description: >
            while its card waits for a person. It is set only on a text or a
            call the playbook gates, and only when every rule a standalone
            automatic email obeys let it through. Null everywhere else.
          title: >
            AccompanyingEmail is the email that goes out at once beside this
            decision
        agent:
          allOf:
            - $ref: '#/components/schemas/leadaction.PlaybookAgent'
          description: >
            the decision so a card, a surface and the send itself all name the
            same person the lead already knows. Null when the playbook links
            none.
          title: >
            Agent is the concierge this decision was made and drafted as, stored
            with
        agent_instructions:
          allOf:
            - $ref: '#/components/schemas/leadaction.AgentInstructions'
          description: >
            email, a text or a call. Null when no concierge is linked, and for
            every action that contacts nobody.
          title: >
            AgentInstructions is what the concierge is told to achieve and say,
            for an
        dataset_id:
          type: string
        development:
          type: string
        downgrade_cause:
          type: string
        email:
          $ref: '#/components/schemas/leadaction.EmailDraft'
        handoff:
          $ref: '#/components/schemas/leadaction.HandoffDraft'
        hold_until:
          format: date-time
          type: string
        lead_contact:
          description: >
            surface can name a lead that never gave a name.


            It is a LABEL, and it must not be read as "where to reach this
            lead". It picks one channel by a fixed preference that has nothing
            to do with the lead, and for a lead who gave both it always answers
            the email — which is why the handoff alert told a desk to email
            somebody who had just texted. Anything telling a person which
            channel to use reads LeadEmail and LeadPhone below instead.
          title: >
            LeadContact is the lead's email, or its phone when it has no email,
            so a
          type: string
        lead_email:
          description: >
            engine held when the decision was made, each stated as itself rather
            than collapsed into one value.


            They exist because a decision that hands the lead to a PERSON is not
            a send. A send has to choose one channel and the engine chooses it;
            a desk task does not, and the desk is better served by seeing both
            and picking — so nothing downstream has to guess which channel a
            human would want. Both are empty for a lead that gave that channel
            nothing, which is itself worth showing: "no number on file" is why a
            card exists at all sometimes.


            Stored on the input, like everything else a card is drawn from,
            because the alert and the queue render from the stored decision and
            must not read the lead's record again — it may have changed, and a
            card three days old is meant to say what was true when it was filed.
          title: |
            LeadEmail and LeadPhone are every way of reaching this lead that the
          type: string
        lead_name:
          type: string
        lead_phone:
          type: string
        lead_reference:
          type: string
        lead_zone:
          allOf:
            - $ref: '#/components/schemas/leadaction.LeadZone'
          description: >
            decision for two reasons. It is what the delivery step reads, so a
            call approved at 19:00 in the development's zone waits for the
            lead's own morning without re-deriving anything; and it is what puts
            the reading in front of a person, including when Erdo could not
            determine the lead's timezone and kept the development's hours. It
            is set for a text and a call, and absent for every other kind, which
            are timed in the development's zone by definition.
          title: |
            LeadZone is the clock this text or call is timed in, stored with the
        note:
          type: string
        original_kind:
          type: string
        playbook_revision:
          description: >
            into the input because an approval card is drawn from the input
            alone. A person looking at a card three days old can otherwise not
            tell whether the rules behind it are the ones the development is
            running now. It is 0 on a decision a person recorded by hand and on
            every decision written before this existed.
          format: int64
          title: >
            PlaybookRevision is the revision of the playbook that decided this,
            copied
          type: integer
        reachability:
          description: >
            made, stored with it so a card can say why the lead is being chased
            this hard — or why it is not any more. See the constants in
            reachability.go.
          title: >
            Reachability and ReachedVia are the lead's state when this decision
            was
          type: string
        reached_via:
          type: string
        signature_name:
          description: >
            this decision was drafted — the name the draft below is actually
            signed with. It is stored beside Agent for the same reason Agent is
            stored at all: the send happens later, and it must go out under the
            name the body says, not under whatever the playbook has been changed
            to since. Empty falls back to Agent.Name; see signedAs.
          title: >
            SignatureName is the playbook's "signs messages as" name as it stood
            when
          type: string
        sms:
          allOf:
            - $ref: '#/components/schemas/leadaction.SMSDraft'
          title: >
            SMS is the text the concierge would send, when the proposal was a
            text.
        suggestion_id:
          type: string
        to:
          type: string
        to_name:
          type: string
      title: >
        ActionInput is the stored input of a suggestion: exactly what executes
        when
      type: object
    leadaction.LeadFacts:
      description: |
        means the lead has not told us.
      properties:
        bedrooms:
          $ref: '#/components/schemas/leadaction.LeadFact'
        broker:
          $ref: '#/components/schemas/leadaction.LeadFact'
        budget:
          $ref: '#/components/schemas/leadaction.LeadFact'
        financing:
          $ref: '#/components/schemas/leadaction.LeadFact'
        language:
          $ref: '#/components/schemas/leadaction.LeadFact'
        purpose:
          $ref: '#/components/schemas/leadaction.LeadFact'
        timeline:
          $ref: '#/components/schemas/leadaction.LeadFact'
      title: |
        LeadFacts is what the evaluation understood about the lead. A nil field
      type: object
    leadaction.ReadBackAccompaniment:
      description: >
        goes out beside a gated text or call.


        Mode is "automatic" (an email may go on its own beside the card) or
        "never". There is deliberately no "approval": an accompanying email that
        itself needed approving would be a second thing in front of a person
        about one lead, which is exactly the shape this engine does not have.
      properties:
        mode:
          type: string
        when:
          description: |
            empty when the text does not say.
          title: >
            When is one short sentence from the text saying when it wants one,
            or
          type: string
      title: >
        ReadBackAccompaniment is what the playbook's text says about the email
        that
      type: object
    leadaction.ReadBackAction:
      description: |
        person. Mode is after platform limits.
      properties:
        kind:
          type: string
        mode:
          type: string
        when:
          type: string
      title: >
        ReadBackAction is one action kind and whether the text lets it run
        without a
      type: object
    leadaction.ReadBackCadence:
      description: >
        the per-channel gaps in each reachability state, and how soon after
        capture the engine may start. It is read out of the text like everything
        else in the read-back, and then clamped to what the platform allows.
      properties:
        confirmed:
          allOf:
            - $ref: '#/components/schemas/leadaction.ContactGaps'
          title: |
            Confirmed applies from the first inbound of any kind onwards.
        new_lead_grace_minutes:
          format: int64
          title: |
            NewLeadGraceMinutes is how long after capture a lead is left alone.
          type: integer
        unconfirmed:
          allOf:
            - $ref: '#/components/schemas/leadaction.ContactGaps'
          title: >
            Unconfirmed applies while the lead has never answered on any
            channel.
      title: >
        ReadBackCadence is how fast the playbook's text says Erdo may work a
        lead:
      type: object
    leadaction.ReadBackLimit:
      description: |
        renderer can say which rules the operator wrote and which Erdo applied.
      properties:
        kind:
          type: string
        mode:
          type: string
        reason:
          type: string
      title: >
        ReadBackLimit is a restriction the platform imposed on top of the text,
        so a
      type: object
    leadaction.ReadBackPriority:
      description: >
        the playbook's own rules put a lead at each level, and the factors the
        playbook forbids taking into account.


        The three levels are fixed — high, medium, low. They are the platform's,
        not the playbook's: the evaluation's response schema constrains the
        model to exactly those three (see evaluationFormat in evaluate.go) and
        the queue's sort, its filters and its badges are built on them. A
        playbook cannot invent a tier; what it decides is which of its rules map
        onto each level.


        NOTHING HERE IS ENFORCED, and this struct is not a guard. The model
        still chooses a decision's priority and writes its priority\_reason, and
        no decision is rejected, downgraded or rewritten because its priority
        disagrees with this ladder. The read-back exists so an operator can SEE
        what Erdo understood — until now the ladder reached the model only as
        raw playbook text, so a misread rule was invisible until decisions came
        out wrong. Whether to enforce it is a product decision nobody has taken.
      properties:
        forbidden_factors:
          description: >
            a lead's country, city, name or language, say. Empty when it forbids
            none.
          items:
            type: string
          title: >
            ForbiddenFactors are the things the text says must NOT affect
            priority —
          type: array
        high:
          description: >
            one short phrase each, in the text's own terms ("budget $2M or
            more", "they replied to us", "timeline beyond a year").
          items:
            type: string
          title: >
            High, Medium and Low are the conditions the text gives for each
            level,
          type: array
        low:
          items:
            type: string
          type: array
        medium:
          items:
            type: string
          type: array
      title: >
        ReadBackPriority is the priority ladder Erdo read out of a playbook:
        which of
      type: object
    leadaction.ReadBackStage:
      description: |
        from that stage.
      properties:
        actions:
          items:
            type: string
          type: array
        name:
          type: string
        summary:
          type: string
      title: >
        ReadBackStage is one stage the text describes, with the actions it
        allows
      type: object
    leadaction.WorkingHours:
      description: |
        lowercase three-letter names; Start and End are "HH:MM" in Timezone.
      properties:
        days:
          items:
            type: string
          type: array
        end:
          type: string
        start:
          type: string
        timezone:
          type: string
      title: |
        WorkingHours is the window automatic contact is allowed in. Days are
      type: object
    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.AccompanyingEmail:
      description: >
        primary action needs a person.


        It exists because calls and texts are approval-gated and email is the
        only channel that runs unattended, so "call this lead" reached the lead
        with nothing at all until somebody acted on the card — which may be
        three days, or never. The accompanying email is what the lead actually
        receives inside the grace, while the phone call still has a person in
        front of it.


        It is NOT a second decision and must never become one. It rides the
        single row the gated action was always going to occupy, it shares that
        row's fingerprint, and it puts nothing extra in front of a person: the
        supersede rule and evaluationDue are untouched, and the card the
        approver sees is the call's, not the email's.


        SentAt is written back onto the stored input when the email actually
        goes, which is what lets the card say the lead has already heard from
        us. The authoritative state lives in the row's own
        accompanying\_email\_\* columns; this field is the copy the card is
        drawn from, and it is only ever written once.
      properties:
        body_markdown:
          type: string
        sent_at:
          description: >
            owed. It is the stored input's copy of
            accompanying\_email\_sent\_at.
          format: date-time
          title: >
            SentAt is when the email reached the mailer, or nil while it is
            still
          type: string
        subject:
          type: string
        to:
          type: string
        to_name:
          type: string
      title: >
        AccompanyingEmail is the email that goes out at once beside a decision
        whose
      type: object
    leadaction.AgentInstructions:
      description: >
        lead, written at decision time and stored with the decision.


        It is addressed to the AGENT, not to the desk: HandoffDraft.Summary
        stays exactly as it was, because a person reading a card needs a
        sentence and an agent about to write needs a brief. For a call this
        block IS the call brief.
      properties:
        instructions:
          title: |
            Instructions are what to say, what not to say, and in what register.
          type: string
        objective:
          title: |
            Objective is what this action is for, in one sentence.
          type: string
      title: >
        AgentInstructions is what the engine tells the concierge to do about
        this
      type: object
    leadaction.EmailDraft:
      properties:
        body_markdown:
          type: string
        subject:
          type: string
      title: |
        EmailDraft is an email the evaluation wrote for the lead.
      type: object
    leadaction.HandoffDraft:
      description: |
        channel the playbook wanted it done.
      properties:
        suggested_channel:
          description: >
            It is "email" on a handoff that exists because an email could not be
            sent — the desk writes it themselves — and empty on a handoff the
            model chose, where the desk decides how to reach the lead.


            It used to carry "sms" and "call" too, on decisions the platform
            rewrote into desk tasks. It no longer does: those are their own
            kinds. Decisions stored before that change still carry them and are
            still read.
          title: |
            SuggestedChannel says how a desk task should be carried out.
          type: string
        summary:
          type: string
      title: |
        HandoffDraft is what the desk should do personally, and through which
      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.SMSDraft:
      properties:
        body:
          type: string
      title: |
        SMSDraft is the text itself, short enough to arrive as one message.
      type: object
    leadaction.LeadFact:
      description: |
        read it from, so a person can check the reading against the row.
      properties:
        source_column:
          type: string
        value:
          type: string
      title: >
        LeadFact is one thing the evaluation read about the lead and the column
        it
      type: object
    leadaction.ContactGaps:
      description: >
        channel. Zero means "the text did not say", which reads as the default,
        never as "no gap at all".


        BetweenChannels is how long after a contact on one channel the next
        contact on a DIFFERENT channel waits ("text them two minutes after the
        call"). Empty when the playbook sets no such wait.
      properties:
        between_channels:
          items:
            $ref: '#/components/schemas/leadaction.ChannelStep'
          type: array
        call_minutes:
          format: int64
          type: integer
        email_minutes:
          format: int64
          type: integer
        sms_minutes:
          format: int64
          type: integer
      title: |
        ContactGaps is the least time between two contacts to one lead on ONE
      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
    leadaction.ChannelStep:
      description: |
        contact on After, a contact on Next waits Minutes.
      properties:
        after:
          type: string
        minutes:
          format: int64
          type: integer
        next:
          type: string
      title: |
        ChannelStep is one wait the playbook sets between two channels: after a
      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_...).

````