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

# Preview Campaign Step Delays

> Preview a wait (delay) change on a launched campaign — NO write.

Reports, per changed step, how many not-yet-started leads will USE the new
wait and how many people already in the sequence KEEP their current one, plus
`blocking` reasons an apply would refuse right now (stale version, a send in
progress). A stale version or a busy campaign never fails this call — they are
reported so the UI can render an honest confirm dialog. Org-scoped like every
other campaign write path.



## OpenAPI

````yaml https://api.bavlio.com/openapi.json post /api/v1/campaigns/{campaign_id}/step-delays/preview
openapi: 3.1.0
info:
  title: Bavlio API
  description: >-
    Outreach automation API — lead enrichment, email campaigns, LinkedIn
    automation, and AI agent. Full docs at https://docs.bavlio.com.
  version: 1.0.0
  x-guidance:
    product: Bavlio Data API
    protocols:
      - x402
    description: >-
      Pay-per-call B2B data endpoints for AI agents. USDC on Base mainnet via
      x402 v2. See /openapi.json for pricing. Landing page + marketing details:
      https://bavlio.com/developers
    network: eip155:8453
    docs_url: https://docs.bavlio.com
  x-payment-merchant-address: '0xcca4725e0e931982830bff523b141e1a7a58d3e7'
servers: []
security: []
tags:
  - name: API Keys
    description: >-
      Create, list, and revoke API keys for programmatic access. JWT
      authentication required.
  - name: credits
    description: Credit balance, cost estimates, and transaction history.
  - name: campaigns
    description: Create, manage, launch, and monitor email campaigns.
  - name: campaign-personalization
    description: AI-powered email subject and body personalization for campaigns.
  - name: email-finder
    description: Find and verify email addresses for prospects.
  - name: sender-profiles
    description: Manage sender identity profiles for outreach.
  - name: sender-profile-chat
    description: Interactive chat for building sender profiles.
  - name: templates
    description: Email template CRUD.
  - name: suppression
    description: Do-not-contact suppression list management.
  - name: datasets
    description: Lead dataset listing, download, and updates.
  - name: user-extractions
    description: Uploaded lead file management and processing.
  - name: enrichment-chat
    description: Interactive chat for data enrichment sessions.
  - name: enrichment-processing
    description: Batch lead enrichment processing.
  - name: File Upload (CSV/Excel)
    description: Upload and preview CSV/Excel lead files.
  - name: connected-accounts
    description: LinkedIn account connections via Unipile.
  - name: linkedin-content
    description: LinkedIn post creation, scheduling, and management.
  - name: linkedin-playground
    description: Direct LinkedIn actions (profile lookup, messaging, engagement).
  - name: bavimail-proxy
    description: BaviMail domain, alias, and email management.
  - name: agent
    description: Bavi AI agent chat with SSE streaming.
  - name: notifications
    description: Notification preferences and delivery.
  - name: referral
    description: Referral code management and stats.
  - name: feedback
    description: User feedback submission.
  - name: authentication
    description: JWT token verification and user info.
  - name: users
    description: User quota and usage information.
  - name: Health
    description: Health checks and system status.
  - name: Personalization
    description: Core lead personalization engine.
  - name: Session Management
    description: Enrichment session lifecycle.
  - name: Session Processing
    description: Session data processing.
  - name: webhooks
    description: Inbound webhook handlers (Stripe, BaviMail, Unipile).
paths:
  /api/v1/campaigns/{campaign_id}/step-delays/preview:
    post:
      tags:
        - campaigns
      summary: Preview Campaign Step Delays
      description: >-
        Preview a wait (delay) change on a launched campaign — NO write.


        Reports, per changed step, how many not-yet-started leads will USE the
        new

        wait and how many people already in the sequence KEEP their current one,
        plus

        `blocking` reasons an apply would refuse right now (stale version, a
        send in

        progress). A stale version or a busy campaign never fails this call —
        they are

        reported so the UI can render an honest confirm dialog. Org-scoped like
        every

        other campaign write path.
      operationId: >-
        preview_campaign_step_delays_api_v1_campaigns__campaign_id__step_delays_preview_post
      parameters:
        - name: campaign_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Campaign Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignStepDelayEditRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignStepDelayEditResponse'
        '401':
          description: Unauthorized - Invalid or missing JWT token
        '404':
          description: Campaign not found
        '409':
          description: Stale version, wrong status, or a send in progress
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    CampaignStepDelayEditRequest:
      properties:
        delays:
          items:
            $ref: '#/components/schemas/CampaignStepDelayEntry'
          type: array
          maxItems: 20
          minItems: 1
          title: Delays
          description: Requested wait changes (unique step numbers).
        scope:
          $ref: '#/components/schemas/CampaignStepDelayScope'
          description: >-
            Cohort the edit applies to. Only 'new_and_unstarted' exists:
            already-started leads always keep the waits they were enrolled
            under.
          default: new_and_unstarted
        expected_updated_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Expected Updated At
          description: >-
            The campaign's `updated_at` the edit was composed against. REQUIRED
            on apply (optimistic concurrency); optional on preview, where a
            mismatch is reported as `version_conflict` instead of failing.
      additionalProperties: false
      type: object
      required:
        - delays
      title: CampaignStepDelayEditRequest
      description: Request body for the delay preview and the delay apply routes.
    CampaignStepDelayEditResponse:
      properties:
        campaign_id:
          type: string
          format: uuid
          title: Campaign Id
          description: Campaign ID
        status:
          $ref: '#/components/schemas/CampaignStatus'
          description: Campaign status at read time
        version:
          type: string
          format: date-time
          title: Version
          description: >-
            campaigns.updated_at AFTER this call; send back as
            expected_updated_at.
        scope:
          $ref: '#/components/schemas/CampaignStepDelayScope'
          description: Applied scope (echo)
        applied:
          type: boolean
          title: Applied
          description: True only when this call wrote the new waits
        already_applied:
          type: boolean
          title: Already Applied
          description: >-
            True when the stored sequence already carried the requested waits,
            so nothing was written. On an apply the version CAS has already
            passed, so it means 'nothing has changed since the version you sent'
            — it is NOT a receipt that an earlier submission of yours committed.
          default: false
        version_conflict:
          type: boolean
          title: Version Conflict
          description: 'Preview only: expected_updated_at is no longer the current version.'
          default: false
        blocking:
          items:
            type: string
          type: array
          title: Blocking
          description: >-
            Machine reasons an apply would refuse right now ('version_conflict',
            'send_in_progress', 'protection_incomplete', 'protection_unknown');
            empty means clear.
        changes:
          items:
            $ref: '#/components/schemas/CampaignStepDelayChange'
          type: array
          title: Changes
          description: Per-step analysis of the request
        cohorts:
          $ref: '#/components/schemas/CampaignStepDelayCohorts'
          description: Campaign-wide cohort snapshot
        steps:
          items:
            $ref: '#/components/schemas/CampaignStepDelaySummary'
          type: array
          title: Steps
          description: >-
            The full sequence. `delay_days` is the proposed sequence on a
            preview and the persisted one on an apply; `stored_delay_days` is
            always what is persisted.
        notes:
          items:
            type: string
          type: array
          title: Notes
          description: Short human-readable strings safe to render verbatim.
      type: object
      required:
        - campaign_id
        - status
        - version
        - scope
        - applied
        - cohorts
      title: CampaignStepDelayEditResponse
      description: Preview/apply result for a launched-campaign delay edit.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    CampaignStepDelayEntry:
      properties:
        step:
          type: integer
          maximum: 200
          minimum: 1
          title: Step
          description: >-
            Step number (1-indexed) whose wait is being changed. Step 1 is
            rejected.
        delay_days:
          type: integer
          maximum: 365
          minimum: 0
          title: Delay Days
          description: >-
            Days to wait before this step once the lead reaches it. 0 = no
            artificial wait (eligible at the next dispatcher opportunity, still
            inside the campaign's window and caps). Strict integer:
            fractional/boolean/string values are rejected.
      additionalProperties: false
      type: object
      required:
        - step
        - delay_days
      title: CampaignStepDelayEntry
      description: 'One requested wait change: step -> delay_days.'
    CampaignStepDelayScope:
      type: string
      enum:
        - new_and_unstarted
      title: CampaignStepDelayScope
      description: >-
        Which cohort a launched-campaign delay edit applies to.


        Exactly ONE member. The field exists so the UI has to state the scope

        explicitly (and so an older client that still sends the rejected

        `include_in_flight` value gets a 422 instead of silently retiming people
        who

        were already invited).


        `NEW_AND_UNSTARTED`: the new waits apply to leads that have not started
        the

        sequence and to every future enrollment; already-started leads keep the
        waits

        they were enrolled under. No stored `next_action_at` is moved either
        way.
    CampaignStatus:
      type: string
      enum:
        - draft
        - launching
        - scoring
        - review_ready
        - scheduled
        - active
        - draining
        - paused
        - completed
        - failed
      title: CampaignStatus
      description: |-
        Campaign status values.

        `SCORING` and `REVIEW_READY` are the commonality-threshold extension
        states (plan 2026-05-21-commonality-threshold-filter, AC-17 / B2).
        They are enumerated here so that every reader of `campaigns.status`
        sees the full set; the DB CHECK constraint expansion lives in PR-2a.
        Until `COMMONALITY_THRESHOLD_ENABLED=true`, no campaign row should
        carry these values in production (feature-flag-gated at the
        customer-entry endpoints — see plan §"Feature flag policy").
    CampaignStepDelayChange:
      properties:
        step:
          type: integer
          title: Step
          description: Step number
        channel:
          type: string
          title: Channel
          description: Step channel as stored ('email'/'linkedin')
        action_type:
          anyOf:
            - type: string
            - type: 'null'
          title: Action Type
          description: LinkedIn action type, when applicable
        current_delay_days:
          anyOf:
            - type: integer
            - type: 'null'
          title: Current Delay Days
          description: >-
            Currently stored wait. Null when the stored value is not a plain
            integer in 0..365 (malformed, negative or out of range) — render it
            as invalid, never as a valid negative number of days.
        delay_days:
          type: integer
          title: Delay Days
          description: Requested (and, after apply, stored) wait
        is_change:
          type: boolean
          title: Is Change
          description: False when the stored wait already equals the request
        current_delay_malformed:
          type: boolean
          title: Current Delay Malformed
          description: >-
            True when the stored wait was not a plain integer in 0..365. While
            any already-started lead exists, an apply naming such a step is
            REFUSED (409 campaigns_delay_edit_unprotectable): their pre-edit
            wait cannot be frozen.
          default: false
        leads_affected:
          type: integer
          title: Leads Affected
          description: >-
            Scope estimate at read time: not-yet-started leads (and future
            enrollments) that are ELIGIBLE to use the new wait. Not a promise
            that each one will be contacted.
          default: 0
        leads_protected:
          type: integer
          title: Leads Protected
          description: >-
            Already-in-the-sequence, non-terminal leads that KEEP the current
            wait for this step (the apply freezes them onto their enrolment
            waits). 'In the sequence' is broader than 'invited': it includes
            email-only contacts, connected leads, paused and rate-limited leads.
            A scope count, not a promise of future sends.
          default: 0
        leads_waiting_acceptance_protected:
          type: integer
          title: Leads Waiting Acceptance Protected
          description: >-
            The narrower subset of the protected leads that is waiting for a
            connection acceptance. Scope only — acceptance is the prospect's
            decision.
          default: 0
        leads_past_step:
          type: integer
          title: Leads Past Step
          description: Already-started leads that are past this step (current_step > step).
          default: 0
      type: object
      required:
        - step
        - channel
        - delay_days
        - is_change
      title: CampaignStepDelayChange
      description: One requested change plus the cohort counts it would touch.
    CampaignStepDelayCohorts:
      properties:
        leads_not_started:
          type: integer
          title: Leads Not Started
          description: >-
            Non-terminal leads with nothing sent yet: they are in scope for the
            new waits. Eligibility, not a promise of delivery.
          default: 0
        leads_in_flight:
          type: integer
          title: Leads In Flight
          description: >-
            Non-terminal leads already in the sequence: the protected cohort
            (broader than 'invited' — see
            CampaignStepDelayChange.leads_protected).
          default: 0
        leads_waiting_acceptance:
          type: integer
          title: Leads Waiting Acceptance
          description: Invited, waiting for acceptance
          default: 0
        leads_sending_now:
          type: integer
          title: Leads Sending Now
          description: >-
            Leads currently 'processing' (mid-send). Any of these BLOCKS an
            apply.
          default: 0
        leads_finished:
          type: integer
          title: Leads Finished
          description: 'Terminal leads: never read, never written'
          default: 0
        earliest_in_flight_step:
          anyOf:
            - type: integer
            - type: 'null'
          title: Earliest In Flight Step
          description: >-
            Lowest current_step among already-started leads; null when none
            remain.
        leads_pinned:
          type: integer
          title: Leads Pinned
          description: Leads THIS call froze onto their current waits (0 on preview).
          default: 0
        leads_already_pinned:
          type: integer
          title: Leads Already Pinned
          description: >-
            Started leads that were already carrying a freeze from an earlier
            edit.
          default: 0
        leads_protection_unknown:
          type: integer
          title: Leads Protection Unknown
          description: >-
            Started leads every hand-off is HOLDING because their timing record
            can't be read (an unreadable metadata document, or an earlier freeze
            the reader can't use for a step they can still take). Any of these
            BLOCKS an apply (409 campaigns_delay_edit_protection_unknown);
            nothing is repaired automatically.
          default: 0
      type: object
      title: CampaignStepDelayCohorts
      description: >-
        Campaign-wide lead cohort snapshot at read time (advisory counts).


        Advisory: the apply re-evaluates every safety decision INSIDE its
        transaction,

        so a count that moved between the preview and the apply cannot make an
        unsafe

        write land — it can only make the confirm copy slightly stale.
    CampaignStepDelaySummary:
      properties:
        step:
          type: integer
          title: Step
          description: Step number
        channel:
          type: string
          title: Channel
          description: Step channel as stored
        action_type:
          anyOf:
            - type: string
            - type: 'null'
          title: Action Type
          description: LinkedIn action type, when applicable
        delay_days:
          anyOf:
            - type: integer
            - type: 'null'
          title: Delay Days
          description: >-
            On a preview: the PROPOSED wait for this step (the request overlaid
            on the stored sequence). On an apply: the persisted post-call wait.
            Null when the underlying stored value is not a plain integer in
            0..365 and the request did not name it.
        stored_delay_days:
          anyOf:
            - type: integer
            - type: 'null'
          title: Stored Delay Days
          description: >-
            The wait actually PERSISTED for this step at the time of this call —
            never the proposal. Null when the stored value is not a plain
            integer in 0..365 (malformed, negative or out of range): render that
            as invalid, not as valid negative days. Use this (or
            changes[].current_delay_days) as the baseline a proposal is diffed
            against.
        editable:
          type: boolean
          title: Editable
          description: >-
            False for step 1, whose wait is never executed (it starts with the
            campaign).
      type: object
      required:
        - step
        - channel
        - editable
      title: CampaignStepDelaySummary
      description: >-
        One step of the resulting sequence (delay view only).


        Two different numbers, deliberately (contract V2.2 §0.1): `delay_days`
        is the sequence a

        PREVIEW is proposing, `stored_delay_days` is what is actually persisted
        right now. A UI that

        diffs its proposal against a preview's `delay_days` finds no difference
        for every step the

        user touched, which is exactly how the editor became impossible to
        apply.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.