---
title: "Create Simulation"
method: POST
path: "/engagements/simulations"
tags: ["authenticated", "engagements"]
---

# Create Simulation

`POST /engagements/simulations`

## Request

- Base URL: `https://api-docs.upsmith.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api-docs.upsmith.com/engagements/simulations`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

## Headers

- `organization-id` string, nullable
- `x-user-id` string, nullable
- `timezone` string
- `x-actor-email` string, nullable
- `x-request-source` string, nullable
- `x-operation-id` string, nullable
- `x-commit-message` string, nullable
- `x-confirmation-id` string, nullable
- `x-chat-id` string, nullable

## Request body

- CreateSimulationBody
  - `agent_id` string, required
  - `contact_id` string, nullable
  - `campaign_id` string, nullable
  - `targeting_rules` object[], nullable
  - `synthetic_lead` boolean
  - `lead_intake` string, nullable
  - `lead_source` string
  - `lead_name` string, nullable
  - `model` string, nullable
  - `variables` object, nullable
  - `version_overrides` object, nullable
  - `include_engagement_history` boolean
  - `source_engagement_id` string, nullable
  - `variant_id` string, nullable

## Response `200`

Successful Response

- EngagementDetailPublic
  - `engagement_id` string, required
  - `created_at` string, date-time, required
  - `status` 'active' | 'inactive' | 'sent' | 'paused' | 'aborted' | 'never_replied', required
  - `type` 'contact' | 'training' | 'ask_jenny' | 'slack', required
  - `is_ai_allowed_to_respond` boolean, required
  - `organization_id` string, required
  - `source` string, nullable
  - `manually_booked_at` string, date-time, nullable
  - `manually_booked_by_user_id` string, nullable
  - `hidden_at` string, date-time, nullable
  - `hidden_by_user_id` string, nullable
  - `has_agent_booking` boolean
  - `booking_status` 'booked' | 'requested' | 'none': The engagement's booking outcome. ``requested`` is an agent booking request the office has not turned into a booking — an outcome of its own, not a booking.
  - `agent_v2_id` string, nullable
  - `agent_v2_name` string, nullable
  - `agent_v2_voice_enabled` boolean, nullable
  - `agent_roster_key` string, nullable
  - `agent_variants` AgentVariantRefPublic[]
    - `id` string, required
    - `name` string, nullable
    - `is_production` boolean
    - `description` string, nullable
  - `objective_state` object, nullable
  - `override_agent_paused` boolean
  - `turn_number` integer, nullable
  - `metadata` object, nullable
  - `contact` ContactBasicPublic: Mirrors the ``ContactData`` GraphQL fragment (contact_id-keyed). COMMERCIAL name handling, the computed ``do_not_contact`` and ``href`` are produced by the router serializer, not read straight off the model.
    - `contact_id` string, required
    - `first_name` string, required
    - `last_name` string, nullable
    - `last_contacted` string, date-time, nullable
    - `href` string, nullable
    - `do_not_contact` boolean, required
  - `last_event` TimelineEventPublic
    - `id` string, required
    - `organization_id` string, required
    - `engagement_id` string, required
    - `origin` 'internal' | 'external', required
    - `role` 'user' | 'agent' | 'contact' | 'provider', required
    - `type` 'action' | 'interaction' | 'llm_response', required
    - `user_id` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `deleted_at` string, date-time, nullable
    - `user_initials` string, nullable
    - `interaction` TimelineEventInteractionPublic
      - `id` string, required
      - `channel_id` string, required
      - `contact_method_id` string, nullable
      - `image_id` string, nullable
      - `external_id` string, nullable
      - `twilio_message_status` string, nullable
      - `twilio_error_code` string, nullable
      - `telnyx_message_status` string, nullable
      - `telnyx_error_code` string, nullable
      - `metadata_json` object, nullable
      - `status` 'pending' | 'delivered' | 'received' | 'failed', required
      - `content` string, required
      - `translated_content` string, nullable
      - `deliver_after` string, date-time, nullable
      - `image` string, nullable
      - `image_content_type` string, nullable
      - `contact_method` ContactMethodPublic
        - `id` string, required
        - `type` 'phone' | 'email' | 'fax' | 'social', required
        - `social_platform` 'meta_messenger' | 'google_lsa' | 'instagram' | 'yelp' | 'thumbtack'
        - `phone_number` string, nullable
        - `email` string, nullable
        - `person_name` string, nullable
        - `person_role` string, nullable
    - `action` TimelineEventActionPublic
      - `id` string, required
      - `tool_module_id` string, required
      - `success` boolean, nullable
      - `type` 'booking' | 'booking_request'
      - `invocation_source` 'llm' | 'precall': Who invoked the tool that produced this action. ``LLM`` — the agent's model chose to call the tool mid-conversation. ``PRECALL`` — the system/operator ran the tool up front (before-turn hooks / pre-agent preload) and injected the result into the prompt; the model did not choose it. ``NULL`` means **legacy / unknown** — the row predates reliable tagging (ENG-4234) and is NOT necessarily LLM-invoked. Precalled tools were saved as actions before #4213 (2026-04-04), then not saved at all until ENG-4234, so historical rows are a mix we deliberately don't reconstruct. Readers must treat NULL as its own bucket, never fold it into ``LLM``. Only rows written after ENG-4234 shipped carry a trustworthy value.
      - `input` string, required
      - `output` string, required
      - `tool_module` ToolModulePublic
        - `id` string, required
        - `created_at` string, date-time, nullable
        - `updated_at` string, date-time, nullable
        - `deleted_at` string, date-time, nullable
        - `title` string, required
        - `description` string, required
        - `function` string, required
        - `disabled_reason` string, nullable
        - `read_only` boolean, required
    - `llm_response` EventLLMResponsePublic
      - `id` string, required
      - `phase` 'ModelRequest' | 'ModelResponse', required: Phase for persisted LLM transcript rows. Like other engagement ``StrEnum`` + ``sa.Enum(native_enum=False)`` columns, PostgreSQL stores the member **name** (``REQUEST`` / ``RESPONSE``) for ``llmphase``. The **value** strings are pydantic-ai message kinds (``ModelRequest`` / ``ModelResponse``) and are not written to this column.
      - `model` string, required
      - `content` union
        - object
        - unknown[]
          - unknown
        - string
      - `duration_ms` integer, nullable
  - `escalation_status` 'open' | 'claimed' | 'resolved' | 'under_control', required: Rollup of an engagement's escalations. Adds ``under_control`` (no escalations) on top of the per-escalation open/claimed/resolved states.
  - `waiting_since` string, date-time, nullable
  - `escalation_reason` string, nullable
  - `escalation_summary` string, nullable
  - `escalation_id` string, nullable
  - `escalations` EscalationBasicPublic[]
    - `escalation_id` string, required
    - `escalation_group_id` string, required
    - `engagement_id` string, required
    - `customer` EscalationCustomerPublic: Who an escalation is about. ``service_address`` is carried alongside ``name`` rather than as a fallback: ServiceTitan names are frequently placeholders ("current resident"), so the address is often the only field that identifies the record. ``phone`` is the number the escalation can be followed up on. A lead that never gave a name and was never matched to a ServiceTitan customer has neither ``name`` nor ``service_address``, so it is often the only thing on the card that makes the customer reachable.
      - `contact_id` string, required
      - `name` string, nullable
      - `st_customer_id` integer, nullable
      - `service_address` string, nullable
      - `phone` string, nullable
      - `href` string, nullable
    - `position` number, required
    - `reason` 'emergency_request' | 'urgent_request' | 'contact_is_angry_or_hostile' | 'contact_other_person_request' | 'technical_issue' | 'contact_is_deceased' | 'wrong_number' | 'appointment_not_booked' | 'contact_no_response' | 'sales_or_billing_follow_up' | 'membership_cancellation' | 'estimate_acceptance' | 'no_availability' | 'cannot_serve_needs_review' | 'scheduling_assistance_needed' | 'other' | 'lead_no_response' | 'do_not_contact_inbound' | 'ambiguous_st_customer_match', required
    - `summary` string, required
    - `note` string, nullable
    - `status` 'open' | 'claimed' | 'resolved', required
    - `assigned_to` UserBasicPublic
      - `user_id` string, required
      - `first_name` string, nullable
      - `last_name` string, nullable
      - `email` string, required
      - `image` string, nullable
    - `resolved_at` string, date-time, nullable
    - `archived_at` string, date-time, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `note_events` EscalationNoteEventPublic[]
      - `id` string, required
      - `event_type` string, required
      - `note_text` string, nullable
      - `created_at` string, date-time, required
      - `saved_by` UserBasicPublic
        - `user_id` string, required
        - `first_name` string, nullable
        - `last_name` string, nullable
        - `email` string, required
        - `image` string, nullable
    - `resolution` EscalationResolutionPublic: The transition that put the escalation in its current resolved state. ``saved_by`` is null whenever ``source`` is not ``user`` — an automatic resolve has no person behind it — so clients must branch on ``source`` rather than treating a missing user as unknown attribution.
      - `id` string, required
      - `event_type` string, required
      - `source` string, required
      - `created_at` string, date-time, required
      - `saved_by` UserBasicPublic
        - `user_id` string, required
        - `first_name` string, nullable
        - `last_name` string, nullable
        - `email` string, required
        - `image` string, nullable
  - `pending_customer_message_count` integer
  - `ai_pause` AiResponsePausePublic
    - `source` 'operator' | 'escalation_auto_mute' | 'hook' | 'google_lsa_csr_takeover' | 'provider_takeover', required
    - `reason` string, nullable
    - `changed_at` string, date-time, required
    - `operator_name` string, nullable
  - `contact_method` ContactMethodPublic
    - `id` string, required
    - `type` 'phone' | 'email' | 'fax' | 'social', required
    - `social_platform` 'meta_messenger' | 'google_lsa' | 'instagram' | 'yelp' | 'thumbtack'
    - `phone_number` string, nullable
    - `email` string, nullable
    - `person_name` string, nullable
    - `person_role` string, nullable
  - `call_duration_seconds` integer, nullable
  - `lsa_account_name` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/upsmith/apis/upsmith-api.md) - [All operations](https://skmtc.dev/upsmith/apis/upsmith-api/llms.txt) - [OpenAPI document](https://skmtc.dev/upsmith/apis/upsmith-api/revisions/86e2c2bd93af?raw)
