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

# Launch a campaign

> From zero to running interviews in one script.

This guide walks through creating a campaign, adding contacts, and launching it programmatically. Use it as a template.

## Prerequisites

* A workspace API key with the `interviews` scope. See [Authentication](/concepts/authentication).
* A list of contacts (name + email at minimum).

## End-to-end script

<CodeGroup>
  ```python Python theme={null}
  import os, httpx

  API_KEY = os.environ["ONTORA_API_KEY"]
  client = httpx.Client(
      base_url="https://api.ontora.com",
      headers={"X-API-Key": API_KEY},
      timeout=30.0,
  )

  # 1. Create the campaign
  campaign = client.post("/v1/interviews", json={
      "name": "Q2 facility-ops interviews",
      "goal": "Map current scheduling workflows",
      "channel": "in_app_chat",
      "topics": [
          {
              "name": "Daily scheduling",
              "goal": "Understand how shifts are assigned",
              "priority": "high",
              "questions": [
                  "Walk me through how this morning's shift assignment happened.",
              ],
          },
      ],
  }).raise_for_status().json()

  interview_id = campaign["id"]
  print(f"Created campaign {interview_id}")

  # 2. Add contacts
  client.post(f"/v1/interviews/{interview_id}/contacts/bulk", json={
      "contacts": [
          {"name": "Sam Rivera",    "email": "sam@example.com",  "department": "Operations"},
          {"name": "Jess Thompson", "email": "jess@example.com", "department": "Operations"},
      ],
  }).raise_for_status()

  # 3. Start the campaign
  client.post(f"/v1/interviews/{interview_id}/start").raise_for_status()
  print("Campaign started — sit back and wait for synthesis.completed")
  ```

  ```bash cURL theme={null}
  ID=$(curl -s -X POST https://api.ontora.com/v1/interviews \
    -H "X-API-Key: $ONTORA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Q2 facility-ops interviews",
      "goal": "Map current scheduling workflows",
      "channel": "in_app_chat",
      "topics": [{"name": "Daily scheduling", "goal": "Understand how shifts are assigned"}]
    }' | jq -r .id)

  curl -X POST https://api.ontora.com/v1/interviews/$ID/contacts/bulk \
    -H "X-API-Key: $ONTORA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"contacts":[{"name":"Sam","email":"sam@example.com"}]}'

  curl -X POST https://api.ontora.com/v1/interviews/$ID/start \
    -H "X-API-Key: $ONTORA_API_KEY"
  ```

  ```bash CLI theme={null}
  # Create + start in one step
  ontora campaigns create -c campaign.yaml --start

  # Or, if you want to add contacts from a CSV between create and start:
  ID=$(ontora campaigns create -c campaign.yaml | awk '/^id:/ {print $2}')
  ontora contacts import $ID contacts.csv
  ontora campaigns start $ID
  ```
</CodeGroup>

## Knowing when it's done

Don't poll. Subscribe to the [`synthesis.completed`](/webhooks/events) webhook — it fires once the synthesis pipeline finishes, and the report is queryable immediately after.

```bash theme={null}
curl -X POST https://api.ontora.com/v1/webhook-endpoints \
  -H "X-API-Key: $ONTORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "ws_...",
    "url": "https://example.com/webhooks/ontora",
    "events": ["synthesis.completed"]
  }'
```

If you can't run a webhook receiver, fall back to polling `GET /v1/interviews/{id}` every minute and watching the `status` field — but webhooks are strongly preferred.

## Common follow-ups

* [Export the report](/guides/export-transcripts) as Markdown.
* [Run a GraphRAG query](/guides/query-with-graphrag) over the transcripts.
