---
title: "Preview one step for one contact with the waiting changes"
method: POST
path: "/sequences/{sequence_id}/changes/step-preview/"
tags: ["sequences"]
---

# Preview one step for one contact with the waiting changes

`POST /sequences/{sequence_id}/changes/step-preview/`

## Request

- Base URL: `https://api.getoneprofile.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getoneprofile.ai/sequences/{sequence_id}/changes/step-preview/`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

The step as the contact would get it once the change set is applied, applying nothing and saving nothing. Nothing is written while the changes leave the step's words as they are or the contact keeps the words they have (`changed` false), or when another contact of `candidate_record_ids` is offered in place of one the changes do not reach. A step the agent writes is written again from the changes, spending one of the day's free previews per step written; the words are kept for 30 minutes for this contact and these changes, so another step's Preview and a test send read them and spend nothing.

## Path parameters

- `sequence_id` string, uuid, required

## Request body

- SequenceChangesStepPreviewSchema: One step for one contact, as a person's waiting changes would have it.
  - `candidate_record_ids` string[]: Contacts the Preview may show instead, in the order it lists them. When the changes do not reach `record_id` and one of these they do reach, that one is offered as `suggested_record_id` and nothing is written.
  - `changes` SequenceChangesSchema, required: A person's waiting changes to a live sequence, as Review changes holds them.
    - `agent` OutboundAgentUpdateSchema: A change to an agent, sent whole against the version it was read at. The configuration travels as one object rather than as fifteen optional fields: the AI writing tab reads the agent, edits it and sends it back, and ``expected_version`` makes two people editing at once a 409 rather than a silent overwrite.
      - `config` OutboundAgentConfig: The versioned configuration one outbound agent carries, stored as JSON on its row. Everything a person sets on the Agent tab that is not already a column of the saved-agent row (``model_key`` and the research switch live there). ``message_prompts`` is keyed by an opaque slot key the caller mints (the step id); the agent never learns what a slot means.
        - `campaign` CampaignBrief: What one sequence's campaign is: the tested campaign it runs and why. Written by tailoring and by nothing else; the Agent tab reads it and never sends it back (the service keeps the stored one on every other write). Tailoring now rewrites the steps' instructions themselves and writes only ``reason`` and ``goal`` here. ``audience``, ``why_now``, ``angle`` and ``step_notes`` are what it wrote before: kept, and still read by the writer, for the agents that carry them. They were written by a model from the audience table, so they are background, never facts: only ``intent``, the person's own words, may be stated as fact.
          - `angle` string
          - `audience` string
          - `campaign_key` string, required
          - `chosen_by_person` boolean
          - `goal` string
          - `intent` string
          - `minted` string[]
          - `reason` string
          - `step_notes` object
          - `table_name` string
          - `tailoring_off` boolean
          - `why_now` string, nullable
        - `cta_label` string, nullable
        - `cta_url` string, nullable
        - `goal` string
        - `language` string
        - `message_prompts` object
        - `person_research` boolean
        - `personality_preset` 'direct' | 'consultative' | 'bold' | 'custom': The voice an agent writes in. Each preset is a real prompt; CUSTOM is the author's.
        - `personality_prompt` string
        - `reply_drafting` boolean
        - `reply_prompt` string
        - `translate` boolean
        - `write_from_excluded_column_ids` string[]
      - `expected_version` integer, required: The version the editor was opened at.
      - `model_key` string, nullable: The model it writes with: swift, core or deep.
      - `research` boolean, nullable: Whether the first message may research the person on the web.
    - `audience` AudiencePutSchema
      - `column_mapping` ColumnMappingSchema: Which audience columns carry a meaning, all detected from the table. One field per channel (email, LinkedIn) plus the company domain the coverage line reports on. Leave the whole object out when binding an audience and the columns are detected server-side. Send it only to correct a detection: a first name, a company or a last-contacted date is no longer part of this, because every column on the table is already addressable as a `{{Column name}}` variable.
        - `company_domain_column_id` string, uuid, nullable: The account identity, reported on by the audience coverage counts. Unmapped, the coverage count is zero.
        - `email_column_id` string, uuid, nullable: Where the send address is read from, and the deduplication key: two rows sharing an address are one contact.
        - `email_deliverability_column_id` string, uuid, nullable: The column holding each address's deliverability verdict, usually the Verify email column over the email column; detected when the table has one. Read only while an email column is mapped, and dropped without one. An address is emailed only when its verdict is one of `email_deliverability_results`; any other verdict skips the email step (LinkedIn steps still run), and an address not verified yet waits for its verdict. Unmapped, every address is emailed unverified.
        - `email_deliverability_results` EmailDeliverability[]: The verdicts the sequence emails: valid, catch-all, risky, unknown, invalid or disposable. Valid alone by default; an empty list reads as the default. An answer the column could not give counts as unknown.
        - `linkedin_url_column_id` string, uuid, nullable: Required by LinkedIn steps; a row without one skips them.
      - `existing_contacts` 'keep' | 'restart'
      - `expected_updated_at` string, date-time, nullable
      - `pause_if_running` boolean
      - `table_id` string, uuid, required
      - `view_id` string, uuid, nullable: A saved view narrowing the audience to its rows; null is the whole table.
    - `copy_from_sequence_id` string, uuid, nullable
    - `expected_agent_version` integer, nullable
    - `expected_updated_at` string, date-time, required
    - `graph` SequenceGraphPutSchema
      - `edges` SequenceEdgeSchema[]
        - `branch` 'default' | 'yes' | 'no' | 'connected' | 'already_connected' | 'not_accepted': Which way an edge leaves its step. DEFAULT for message-step flow; YES/NO for a condition's two branches. A connection request leaves by the answer it got: CONNECTED (accepted, and already connected unless that has a branch of its own), ALREADY_CONNECTED (present only when the request splits it out) and NOT_ACCEPTED.
        - `from_step_id` string, uuid, nullable: None = the start node.
        - `to_step_id` string, uuid, nullable: None = an end node.
      - `expected_updated_at` string, date-time, nullable: Autosave concurrency token; a mismatch means another tab saved first (409).
      - `steps` SequenceStepSchemaInput[]
        - `agent_prompt_key` string, nullable: Read-only: the agent's slot for this step's prompt (the step's id). Set by the server on every agent-written step; ignored on write.
        - `agent_subject` boolean: On an agent-written email that starts a thread: the agent writes the subject line too, and `subject_segments` is ignored. Off, the typed subject is sent. Ignored on a reply and on written steps.
        - `authoring` 'written' | 'agent': Who writes an email step's words. WRITTEN is the default and what every step was before: the author's own subject and body, with variables, snippets and spintax resolved per contact. AGENT holds an instruction instead of a body; the whole email is written for each contact at send time from that instruction, the row and the workspace's written business context, and priced per contact.
        - `body_segments` TemplateSegmentSchema[]
          - `builtin` 'sender_first_name' | 'sender_full_name' | 'sender_title' | 'sender_booking_link' | 'sending_domain' | 'sequence_name' | 'greeting' | 'today' | 'yesterday' | 'tomorrow' | 'day_plus_2' | 'day_plus_3' | 'day_plus_4': Non-column variables the composer offers. Weekday variables render the *name* of the day ("Thursday") so a cadence can reference real days without weekly rewrites.
          - `column_id` string, uuid, nullable
          - `kind` 'text' | 'column' | 'builtin' | 'spintax', required: One piece of a subject or body template.
          - `options` string[]: Spintax alternatives; one is chosen per contact.
          - `required` boolean
          - `text` string
        - `condition_kind` 'has_email' | 'has_linkedin_url' | 'linkedin_connected' | 'bounced' | 'replied' | 'linkedin_invite_accepted' | 'linkedin_open_profile': The branching conditions. ``BOUNCED`` and ``REPLIED`` are only pickable after a message step exists earlier on the same path: enforced at save, explained in the picker.
        - `condition_window_days` integer, nullable: How many days the step waits for its answer. On a check that waits (Replied?, Email bounced?), its window. On a connection request, how long it waits for the request to be accepted before the contact goes down its `not_accepted` branch: null is saved as 14, and a change holds every contact already waiting to the new window.
        - `id` string, uuid, required
        - `kind` 'message' | 'condition' | 'linkedin_profile_view' | 'linkedin_connection_request' | 'linkedin_message' | 'linkedin_inmail' | 'linkedin_withdraw_invitation' | 'linkedin_follow' | 'linkedin_like_post', required: What a graph node does. The LINKEDIN_* kinds do not send anything themselves. They enqueue browser work and park the contact until it runs, because the action happens in the customer's own browser and that browser may be closed. Every kind here is something the product can carry out end to end. There is deliberately no phone-call or text-message kind: the engine cannot dial or text, and a step it can only hand to a person to do elsewhere is a to-do list wearing a sequence's clothes, not a channel this product supports.
        - `position` integer
        - `prompt` string: What the agent is asked to write at this step. Stored on the agent, not the step, and saved with the graph: this is the one way to change it.
        - `reply_to_step_id` string, uuid, nullable: The earlier email step this email replies to: it goes out under that step's send to the contact (Re: its subject, In-Reply-To its Message-ID), from the mailbox that sent it. Must be an email step that comes before this one on the graph. Null starts a new thread. Only on email steps.
        - `requires_approval` boolean: Hold this step for approval even when its channel would run on its own. One-way: true adds an approval, false never removes the one the channel's resolved mode already wants.
        - `subject_segments` TemplateSegmentSchema[], nullable: The subject of an email that starts a thread. Ignored on a reply (`reply_to_step_id`), whose subject is the thread's, and on an email whose subject the agent writes (`agent_subject`). An email that starts a thread with neither fails preflight until a subject is added. A blank subject on an email after another one, with no `reply_to_step_id`, is saved as a reply to the nearest earlier email, which is what it used to mean.
          - `builtin` 'sender_first_name' | 'sender_full_name' | 'sender_title' | 'sender_booking_link' | 'sending_domain' | 'sequence_name' | 'greeting' | 'today' | 'yesterday' | 'tomorrow' | 'day_plus_2' | 'day_plus_3' | 'day_plus_4': Non-column variables the composer offers. Weekday variables render the *name* of the day ("Thursday") so a cadence can reference real days without weekly rewrites.
          - `column_id` string, uuid, nullable
          - `kind` 'text' | 'column' | 'builtin' | 'spintax', required: One piece of a subject or body template.
          - `options` string[]: Spintax alternatives; one is chosen per contact.
          - `required` boolean
          - `text` string
        - `title` string, nullable: What the step is called on its card, in the customer's words: set by the library campaign that laid it out, or by whoever saves the graph. Null names it by its place in the cadence (Opener, New angle, Break-up). Sending the stored title back, or leaving it out, keeps it while the step keeps its kind and who writes it; a different title renames the step, and an empty one clears the name.
        - `wait_amount` integer
        - `wait_unit` 'days' | 'hours'
    - `mapping` ColumnMappingSchema: Which audience columns carry a meaning, all detected from the table. One field per channel (email, LinkedIn) plus the company domain the coverage line reports on. Leave the whole object out when binding an audience and the columns are detected server-side. Send it only to correct a detection: a first name, a company or a last-contacted date is no longer part of this, because every column on the table is already addressable as a `{{Column name}}` variable.
      - `company_domain_column_id` string, uuid, nullable: The account identity, reported on by the audience coverage counts. Unmapped, the coverage count is zero.
      - `email_column_id` string, uuid, nullable: Where the send address is read from, and the deduplication key: two rows sharing an address are one contact.
      - `email_deliverability_column_id` string, uuid, nullable: The column holding each address's deliverability verdict, usually the Verify email column over the email column; detected when the table has one. Read only while an email column is mapped, and dropped without one. An address is emailed only when its verdict is one of `email_deliverability_results`; any other verdict skips the email step (LinkedIn steps still run), and an address not verified yet waits for its verdict. Unmapped, every address is emailed unverified.
      - `email_deliverability_results` EmailDeliverability[]: The verdicts the sequence emails: valid, catch-all, risky, unknown, invalid or disposable. Valid alone by default; an empty list reads as the default. An answer the column could not give counts as unknown.
      - `linkedin_url_column_id` string, uuid, nullable: Required by LinkedIn steps; a row without one skips them.
    - `prompt_proposal` OutboundPromptProposalAcceptSchema: Accept a proposal, writing the prompts as the agent's next version.
      - `base_version` integer, required: The version the proposal was written against.
      - `feedback_cutoff` string, date-time, nullable: The proposal's read time; feedback edited later stays pending.
      - `feedback_ids` string[]: The feedback the proposal read, stamped as consumed.
      - `items` object, required: The accepted prompt per slot key; the reply prompt travels under 'reply'.
    - `restore_version` integer, nullable
    - `review_token` string, nullable
    - `sender_profile_ids` string[], nullable
    - `settings` SequenceUpdateSchema
      - `bcc_addresses` string[], nullable
      - `cc_addresses` string[], nullable
      - `continuous_enrolment` boolean, nullable
      - `email_approval_mode` string, nullable
      - `expected_updated_at` string, date-time, nullable: The updated_at the client last saw; a stale value is refused with 409 instead of clobbering a concurrent editor.
      - `linkedin_approval_mode` string, nullable
      - `linkedin_check_max_seconds` integer, nullable: Retired. Acceptance checks now use the service-managed interval.
      - `linkedin_check_min_seconds` integer, nullable: Retired. Acceptance checks now use the service-managed interval.
      - `multithreading` object, nullable
      - `name` string, nullable
      - `opportunity_values` object, nullable
      - `opt_out_footer_text` string, nullable
      - `plain_text_only` boolean, nullable
      - `sending_priority` string, nullable
      - `sending_window` SendingWindowSchema: When a sequence may send, as the send engine reads it. Without ``days`` it sends at any hour of any day, which is the settings page's Send any time: it keeps its timezone and its minimum gap between emails. Every rule here is checked on save, running or not, and a window that breaks one is refused with a 422 naming the part at fault.
        - `days` SendingDaysSchema: The days a sequence may send on. A day left out, or null, is closed.
          - `fri` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `mon` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `sat` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `sun` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `thu` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `tue` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
          - `wed` SendingHoursSchema: One open day's sending hours, read on the window's timezone.
            - `end` string, required: When it closes, as HH:MM, after start on the same day. Hours cannot run past midnight, so 23:59 is the latest close.
            - `start` string, required: When sending opens, as HH:MM on a 24-hour clock, such as 09:00.
        - `min_interval_minutes` integer, nullable: The shortest gap between two emails from one mailbox, a whole number from 0 to 240 minutes. Null or 0 spaces them automatically from each mailbox's daily capacity.
        - `sender_timezone` boolean, nullable: Whether the hours are read in each sender's own timezone, so a New York sender and a London one each send 9 to 5 their time. False reads them in `timezone` for everyone. Absent or null means each sender's; an update that leaves it out keeps what is stored.
        - `timezone` string, nullable: The IANA timezone the hours are read in, such as Europe/Berlin: the one timezone when `sender_timezone` is false, else the fallback for a sender with none. UTC when absent.
      - `spending_limit_tenths` integer, nullable
    - `training_revision` integer, nullable
    - `training_rewrite` boolean
    - `training_session_id` string, uuid, nullable
    - `training_slot_keys` string[], nullable
  - `record_id` string, uuid, required: The contact the step is shown for.
  - `step_id` string, uuid, required
  - `write` boolean: False answers whether the changes change this step and reach the contact, and writes nothing for a step the agent writes: no preview is spent. A typed step is filled in either way, which costs nothing.

## Response `200`

Successful Response

- SequenceChangesStepPreviewReadSchema: What a step's Preview shows of the waiting changes for one contact.
  - `changed` boolean, required: Whether the waiting changes change this step's words for this contact. False: the step reads as it does live (the changes touch something else, or the contact keeps the words they have: a person's own, words a person approved or a send holds, or, for a lesson, words already written for them or a thread marked Looks good), and nothing was written.
  - `message` RenderedMessageReadSchema
    - `agent_written` boolean: The agent wrote this for the contact. A preview is one draft; the real send is written fresh, unless `approved`.
    - `approved` boolean: The words were marked Looks good, with the contact's thread on the AI writing tab or as this one email from the Builder, and still stand, so they are what sends. They stop standing, and the agent writes the step fresh, once the agent's writing or the contact's row changes.
    - `body` string, required
    - `closing` EmailClosingReadSchema: What a send adds below an email's words: the sender's signature, then the opt-out footer.
      - `opt_out_footer` string, nullable, required: The sequence's opt-out footer, last of all, as the email carries it: its unsubscribe link written `[unsubscribe here](url)` in a formatted sequence and as the bare URL in a plain-text one. In a preview the link unsubscribes nobody. Null when the sequence has no footer.
      - `signature` string, nullable, required: The signature the email ends with, its sender variables filled in as the send fills them: the mailbox about to send, or else the first sender, the sender a preview writes as. Null when the sender has none.
    - `in_thread` boolean, required
    - `is_sample` boolean
    - `message_id` string, uuid, nullable: The agent's message these words are, on an agent-written step: the sample on screen, or the one marked Looks good. The looks-good route takes it back as `message_id`, so a mark lands only on the words that were read.
    - `missing_columns` string[], required
    - `pinned` boolean: The words are pinned for this contact by a person (their own edit) or by the approval queue, and send exactly as they are. A thread marked Looks good is `approved` instead.
    - `sample_created_at` string, date-time, nullable
    - `sample_version` integer, nullable
    - `sent` SentStepEmailReadSchema: The email a step got out to one contact, as it went.
      - `body` string, nullable, required: The email's words as they were sent, without the signature and opt-out footer the send added below them.
      - `email_html` string, nullable: The email's HTML part exactly as it was handed to Gmail: the words, the sender's signature and the opt-out footer, every line, blank line and space as the contact got them. Null for a send from before the send kept its email.
      - `sent_at` string, date-time, required
      - `subject` string, nullable, required
    - `spam_findings` SpamFindingReadSchema[]: The spam check and the placeholder scan, run on the agent's words for this contact. Always empty for a written step, whose text the composer's own check covers. Advice, never a refusal.
      - `code` string, required
      - `detail` string, required: The evidence: "3 in the message. One is safest, two at most."
      - `excerpt` string, nullable
      - `excerpts` string[]: Every piece of text the finding is about; excerpt is the first.
      - `message` string, required: Title and detail as one sentence, for a single line.
      - `severity` string, required: warning (likely hurts) or info (worth knowing).
      - `title` string, required: The problem, named: "Too many links".
    - `subject` string, required
    - `waiting_for_approval` boolean: This contact's message at this step waits for a person's approval on its Tasks card, where its words are changed, approved or rejected. Set by the preview route; false once the card is decided, or when the step never asked.
    - `written` boolean: False only when the preview was asked to write nothing and nothing is written for this contact at this step yet: subject and body are empty.
  - `reaches` boolean: Whether the changes reach this contact: false once this step has gone to them, when they are past it or down another branch, when they are done with the sequence and the changes do not carry them on, or when they are not in it.
  - `sent_at` string, date-time, nullable: When this step went to the contact, when it has.
  - `suggested_record_id` string, uuid, nullable: A contact of `candidate_record_ids` the changes do reach, offered in place of `record_id`, which they do not; `message` is then empty.

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: The sequence, the step, the contact or a writing setup is gone.
- `409`: The audience is not configured yet.
- `422`: The changes set an Effort the catalog does not offer (`model_key`), or the agent wrote nothing usable with them (`step_id`, error type `draft_not_written`).
- `429`: Today's free agent-written previews are spent.
- `500`: Internal server error.
- `502`: Bad gateway error.
- `503`: Service unavailable.

---

[API](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile.md) · [All operations](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/llms.txt) · [OpenAPI document](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/revisions/4a6bd71e3f02?raw)
