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

# Launch Run

> Fans the draft out into one conversation per contact and moves it DRAFT to RUNNING. Queued, not dialled: a 2xx means the conversations are on the dispatch queue, and each contact's outcome arrives as a conversation.* webhook rather than in this response. All-or-nothing - the whole batch is admitted against billing first, and a run the company cannot afford is refused whole and stays a draft. Send scheduledAt to start it later instead, at most 30 days ahead: the run becomes SCHEDULED, dispatches nothing, and is admitted against billing again when it fires.

Fans a draft run out into one conversation per contact, now or at a time you choose.

## Starting it later

Send `scheduledAt` and the run is booked instead of dispatched. It moves to `SCHEDULED`, queues nothing, and starts on its own when the time arrives.

```javascript theme={null}
POST /runs/{id}/launch   { "scheduledAt": "2026-09-15T09:00:00Z" }

{ "status": "SCHEDULED", "affectedCalls": 0 }
```

`affectedCalls` is `0` because nothing was queued. That is the honest count, not an omission.

Up to **30 days ahead** — the same ceiling `scheduleTime` has always had on a single conversation, enforced by the same rule so one call and a batch of them cannot disagree about how far ahead you may plan. Further out, or not a valid ISO 8601 date-time, is a `400` and the run stays a `DRAFT`.

A scheduled run behaves like any other draft until it fires: attach more contacts, or `cancel` it to call the whole thing off. Cancelling clears the appointment, so a cancelled run never starts.

Contacts attached before the run fires are **held, not dialled** — [Attach Contacts](/api-reference/runs/attach-run-contacts) returns `queued: 0` and they go out with everyone else at `scheduledAt`. A booked run has not started, so dispatching them on arrival would call those contacts days early.

### What happens at the appointed time

The run is launched by exactly the code path an immediate launch takes — the same admission, the same fan-out — so a run that starts on Monday morning is indistinguishable from one somebody launched by hand on Monday morning. It may begin up to a minute late; a scheduled start is an appointment, not a deadline.

<Warning>
  **Scheduling is not a reservation.** The batch is admitted against your billing when you schedule
  it *and again when it fires*, and it is the second one that decides. Nothing is held in between,
  so a balance that has moved can leave a booked run unable to start.

  A run refused at its time **stays `SCHEDULED` and is retried**, with the reason in
  `scheduleError` on [Get Run](/api-reference/runs/get-run). Topping up is enough to make it go;
  there is nothing to re-book. Poll `scheduleError`, or a run you booked for a Monday can sit there
  unstarted with nobody watching.
</Warning>

## Queued, not dialled

A `2xx` from this endpoint means the conversations exist and are **on the dispatch queue**. It does not mean anybody has been called.

The provider is called later by the dispatch worker, outside your request. So:

* **per-contact outcomes do not come back in this response.** They arrive as [`conversation.*` webhooks](/guides/webhooks), and each payload carries `runId` so you can attribute it to this run without a lookup
* **a `2xx` is not a promise that every call connects.** Contacts who never answer end up `UNREACHABLE`, which [Get Run](/api-reference/runs/get-run) counts in its own bucket

```json theme={null}
{ "status": "RUNNING", "affectedCalls": 42 }
```

`affectedCalls` is how many conversations were queued, not how many calls were placed.

## All of it, or none of it

The whole contact list is admitted against your billing **before any of it is dispatched**. A run you cannot afford in full is refused whole with a [`402`](/guides/error-handling#billing-errors), nothing is queued, and the run stays a `DRAFT` you can launch once you have topped up.

Partial admission — dialling as far as the balance goes — was deliberately rejected. It turns one request into an outcome nobody can predict or undo: you cannot un-call the contacts that already went out.

The `402` carries a `billing` object with what the batch needed and what was available, so you can size a smaller batch or work out the top-up.

<Warning>
  Admission is a **preflight, not a reservation**. Nothing is held, so two different runs
  launched at the same moment can both pass, and your balance can move between admission and
  the dial. What protects the money is the per-call reservation made before each individual
  call, which fails closed. What admission buys you is being refused **up front** instead of
  discovering the problem one failed call at a time.
</Warning>

## Launching twice

`DRAFT` is the only status a run can be launched from, so a second launch is a `422` rather than a second fan-out — including when two launches arrive at the same instant. The run is locked for the duration, so concurrent launches cannot both pass the check.

## A run with no contacts

`422`. There is nothing to dial, and a `RUNNING` run with no conversations would report a meaningless aggregate.

## Error Scenarios

* **400 Bad Request**: `scheduledAt` is not a valid ISO 8601 date-time, or is more than 30 days ahead
* **402 Payment Required**: the company cannot afford the batch. Nothing dispatched, run stays a draft. A scheduled run is admitted at schedule time too, so an unaffordable run is refused before it is booked rather than at its start time
* **404 Not Found**: the run does not exist, or belongs to another company
* **422 Unprocessable Entity**: the run is not a draft, has no contacts, or its agent can no longer back a dispatch
* **403 Forbidden**: the API key does not hold `write:runs`

## Related Resources

<CardGroup cols={2}>
  <Card title="Get Run" icon="layer-group" href="/api-reference/runs/get-run">
    Watch the aggregate as it dials
  </Card>

  <Card title="Pause Run" icon="pause" href="/api-reference/runs/pause-run">
    Stop it without losing the work
  </Card>

  <Card title="Webhooks" icon="bolt" href="/guides/webhooks">
    Per-contact outcomes as they happen
  </Card>

  <Card title="Billing Errors" icon="credit-card" href="/guides/error-handling#billing-errors">
    Reading the 402 body
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /runs/{id}/launch
openapi: 3.0.0
info:
  title: InstaView API
  description: |-
    InstaView API Documentation

    ## Authentication

    All endpoints require API key authentication using Bearer token:
    ```
    Authorization: Bearer sk_your_api_key_here
    ```

    ## API Key Management

    The API Key module provides comprehensive key management for:
    - **Direct Client Keys**: Company-scoped keys for your applications
    - **ATS Partner Keys**: Resource-scoped keys for ATS integrations

    ### Key Features
    - HMAC-SHA256 hashing for API key storage
    - Configurable rate limiting
    - Comprehensive audit logging
    - Company-level isolation
    - Resource scoping for ATS partners
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.instaview.sk
    description: Production API Gateway
security: []
tags: []
paths:
  /runs/{id}/launch:
    post:
      tags:
        - Runs
      summary: Launch a run
      description: >-
        Fans the draft out into one conversation per contact and moves it DRAFT
        to RUNNING. Queued, not dialled: a 2xx means the conversations are on
        the dispatch queue, and each contact's outcome arrives as a
        conversation.* webhook rather than in this response. All-or-nothing -
        the whole batch is admitted against billing first, and a run the company
        cannot afford is refused whole and stays a draft. Send scheduledAt to
        start it later instead, at most 30 days ahead: the run becomes
        SCHEDULED, dispatches nothing, and is admitted against billing again
        when it fires.
      operationId: PublicRunsController_launchRun_v1
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
        - name: companyId
          required: false
          in: query
          description: >-
            Required for ATS API keys to specify which company to access.
            Ignored for standard company API keys.
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicLaunchRunDto'
      responses:
        '201':
          description: Run launched, or scheduled to launch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicRunLifecycleDto'
        '400':
          description: >-
            Bad Request - scheduledAt is not a valid ISO 8601 date-time, or is
            more than 30 days in the future.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: >-
            Payment Required - the batch costs more than the balance allows, or
            there is no active subscription. All-or-nothing: nothing is
            dispatched and the run stays a draft. A scheduled run is admitted at
            schedule time too, so an unaffordable run is refused before it is
            booked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRefusalResponse'
        '422':
          description: >-
            Unprocessable Entity - the run is not in a status this transition is
            legal from.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer: []
components:
  schemas:
    PublicLaunchRunDto:
      type: object
      properties:
        scheduledAt:
          type: string
          format: date-time
          description: >-
            When the run should start dialling, ISO 8601, at most 30 days ahead.
            Omit it to start immediately. With it, the run becomes `SCHEDULED`
            and dispatches nothing until the time arrives.
          example: '2026-09-15T09:00:00Z'
    PublicRunLifecycleDto:
      type: object
      properties:
        status:
          type: string
          description: The run's status after the transition
          enum:
            - DRAFT
            - SCHEDULED
            - RUNNING
            - PAUSED
            - COMPLETED
            - CANCELLED
          example: RUNNING
        affectedCalls:
          type: number
          description: >-
            How many of the run's calls this request moved: queued by a launch,
            withdrawn by a pause, restored by a resume, closed out by a cancel.
            Not calls placed - a 2xx means queued, not dialled.
          example: 42
      required:
        - status
        - affectedCalls
    ErrorResponse:
      type: object
      properties:
        statusCode:
          type: integer
          example: 400
        message:
          type: string
          example: Validation failed
        error:
          type: string
          example: Bad Request
      required:
        - statusCode
        - message
    BillingRefusalResponse:
      type: object
      description: >-
        A 402 refusal. `billing` is present when the billing system reported
        figures, and absent when it had none to report - treat an absent object
        as unknown rather than as zero.
      properties:
        statusCode:
          type: integer
          example: 402
        message:
          type: string
          example: >-
            Insufficient minutes available to schedule 10 interviews (50 minutes
            total). Please purchase additional minutes.
        error:
          type: string
          example: PAYMENT_REQUIRED
        billing:
          type: object
          description: >-
            The shortfall, in the company's own billing unit. Read
            `billingSystem` first: a minutes-based company carries
            `requiredMinutes` and `availableMinutes`, a credits-based one
            `requiredCredits` and `availableCredits`, matching the split `GET
            /billing/usage` returns.
          properties:
            billingSystem:
              type: string
              enum:
                - minutes
                - credits
              example: minutes
            requiredMinutes:
              type: number
              description: >-
                Minutes the request would cost. Present only for a minutes-based
                company.
              example: 50
            availableMinutes:
              type: number
              description: >-
                Minutes the company has left. Present only for a minutes-based
                company.
              example: 12
            requiredCredits:
              type: number
              description: >-
                Credits the request would cost. Present only for a credits-based
                company.
              example: 50
            availableCredits:
              type: number
              description: >-
                Credits the company has left. Present only for a credits-based
                company.
              example: 12
            suggestedAction:
              type: string
              enum:
                - purchase_minutes
                - upgrade_plan
                - increase_spending_cap
                - contact_support
              example: purchase_minutes
          required:
            - billingSystem
        traceId:
          type: string
          example: 4b1f0c9d2e6a47f8b3c5d7e9a1b2c3d4
      required:
        - statusCode
        - message
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: API key for authentication using Bearer scheme

````