# Campaigns

> Call a list of people at a pace you set, only in calling hours, and hear how each call went.

A campaign is a list of people for an outbound agent to call, at the pace you set and only in the
hours you allow. It is the same campaign the console shows on the agent's **Campaigns** tab: one
you start through the API, a colleague can pause in the console, and the other way round.

Campaigns call real people, so making or changing one needs a **live key** with `campaigns:write`,
and reading them needs `campaigns:read`. A test key reads no campaigns. To try your integration
first, [schedule single calls](/guides/outbound-calls#schedule-a-call-for-later) to the test numbers.

## Create one

**curl**

```sh
#!/bin/sh
# Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  -H "Idempotency-Key: reminders-october" \
  --json '{
  "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
  "name": "Visit reminders, October",
  "calling_hours": {
    "start_hour": 9,
    "end_hour": 18,
    "days": [
      "mon",
      "tue",
      "wed",
      "thu",
      "fri"
    ]
  },
  "contacts": [
    {
      "to_number": "+12025550123",
      "variables": {
        "first_name": "Sara"
      },
      "metadata": {
        "crm_lead_id": "L-20931"
      },
      "external_id": "lead-20931"
    },
    {
      "to_number": "+12025550124",
      "variables": {
        "first_name": "Omar"
      },
      "timezone": "America/Chicago",
      "external_id": "lead-20932"
    }
  ]
}'
```

**JavaScript**

```js
// Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
const response = await fetch("https://api.aigently.ai/v1/campaigns", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "reminders-october",
  },
  body: JSON.stringify({
    "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
    "name": "Visit reminders, October",
    "calling_hours": {
      "start_hour": 9,
      "end_hour": 18,
      "days": [
        "mon",
        "tue",
        "wed",
        "thu",
        "fri"
      ]
    },
    "contacts": [
      {
        "to_number": "+12025550123",
        "variables": {
          "first_name": "Sara"
        },
        "metadata": {
          "crm_lead_id": "L-20931"
        },
        "external_id": "lead-20931"
      },
      {
        "to_number": "+12025550124",
        "variables": {
          "first_name": "Omar"
        },
        "timezone": "America/Chicago",
        "external_id": "lead-20932"
      }
    ]
  }),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
        "Idempotency-Key": "reminders-october",
    },
    json={
        "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
        "name": "Visit reminders, October",
        "calling_hours": {
            "start_hour": 9,
            "end_hour": 18,
            "days": ["mon", "tue", "wed", "thu", "fri"],
        },
        "contacts": [{"to_number": "+12025550123", "variables": {"first_name": "Sara"}, "metadata": {"crm_lead_id": "L-20931"}, "external_id": "lead-20931"}, {"to_number": "+12025550124", "variables": {"first_name": "Omar"}, "timezone": "America/Chicago", "external_id": "lead-20932"}],
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	payload := []byte(`{
  "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
  "name": "Visit reminders, October",
  "calling_hours": {
    "start_hour": 9,
    "end_hour": 18,
    "days": [
      "mon",
      "tue",
      "wed",
      "thu",
      "fri"
    ]
  },
  "contacts": [
    {
      "to_number": "+12025550123",
      "variables": {
        "first_name": "Sara"
      },
      "metadata": {
        "crm_lead_id": "L-20931"
      },
      "external_id": "lead-20931"
    },
    {
      "to_number": "+12025550124",
      "variables": {
        "first_name": "Omar"
      },
      "timezone": "America/Chicago",
      "external_id": "lead-20932"
    }
  ]
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns", bytes.NewReader(payload))
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Idempotency-Key", "reminders-october")
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
              "name": "Visit reminders, October",
              "calling_hours": {
                "start_hour": 9,
                "end_hour": 18,
                "days": [
                  "mon",
                  "tue",
                  "wed",
                  "thu",
                  "fri"
                ]
              },
              "contacts": [
                {
                  "to_number": "+12025550123",
                  "variables": {
                    "first_name": "Sara"
                  },
                  "metadata": {
                    "crm_lead_id": "L-20931"
                  },
                  "external_id": "lead-20931"
                },
                {
                  "to_number": "+12025550124",
                  "variables": {
                    "first_name": "Omar"
                  },
                  "timezone": "America/Chicago",
                  "external_id": "lead-20932"
                }
              ]
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .header("Content-Type", "application/json")
            .header("Idempotency-Key", "reminders-october")
            .method("POST", HttpRequest.BodyPublishers.ofString(body))
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns")
{
    Content = new StringContent(
        """
        {
          "agent_id": "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
          "name": "Visit reminders, October",
          "calling_hours": {
            "start_hour": 9,
            "end_hour": 18,
            "days": [
              "mon",
              "tue",
              "wed",
              "thu",
              "fri"
            ]
          },
          "contacts": [
            {
              "to_number": "+12025550123",
              "variables": {
                "first_name": "Sara"
              },
              "metadata": {
                "crm_lead_id": "L-20931"
              },
              "external_id": "lead-20931"
            },
            {
              "to_number": "+12025550124",
              "variables": {
                "first_name": "Omar"
              },
              "timezone": "America/Chicago",
              "external_id": "lead-20932"
            }
          ]
        }
        """,
        Encoding.UTF8,
        "application/json"),
};
request.Headers.Add("Idempotency-Key", "reminders-october");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Create a campaign. With a live key: a campaign calls real people. It is a draft until you start it.
$curl = curl_init("https://api.aigently.ai/v1/campaigns");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
        "Content-Type: application/json",
        "Idempotency-Key: reminders-october",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "agent_id" => "8a1f3c2e-0b6d-4f5e-9a7c-2d4e6f8a0b1c",
        "name" => "Visit reminders, October",
        "calling_hours" => [
            "start_hour" => 9,
            "end_hour" => 18,
            "days" => ["mon", "tue", "wed", "thu", "fri"],
        ],
        "contacts" => [
            [
                "to_number" => "+12025550123",
                "variables" => [
                    "first_name" => "Sara",
                ],
                "metadata" => [
                    "crm_lead_id" => "L-20931",
                ],
                "external_id" => "lead-20931",
            ],
            [
                "to_number" => "+12025550124",
                "variables" => [
                    "first_name" => "Omar",
                ],
                "timezone" => "America/Chicago",
                "external_id" => "lead-20932",
            ],
        ],
    ]),
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

Each contact is one person: `to_number`, their `variables`, and if you like their `timezone`, your
own `metadata` and an `external_id`. Everything is checked before anything is made. A number the
platform does not call, the same number twice, a time zone that is not one, or a value that does
not fit the agent is refused with `validation_failed`, and `errors` names each contact by its place
in the list — `contacts[3].variables.renewal_date`. Up to 1,000 contacts go in one request; send the
rest as [more people](#add-more-people).

**`Idempotency-Key` is required.** A retry with the same key returns the campaign the first attempt
made, and never makes a second one that would call everybody twice. See
[Safe retries](/guides/idempotency).

| Field | What it is |
|---|---|
| `pacing` | `max_concurrent` calls at once, `dials_per_minute`, `max_attempts` for each person, and `retry_backoff_minutes` between tries. |
| `calling_hours` | `start_hour`, `end_hour` and `days`, read in each contact's own `timezone` — or your organization's, for a contact without one. |
| `voicemail` | `hang_up` when a machine answers, or `leave_message` with the `message` to leave. |
| `callbacks` | Whether a person may ask the agent to call back at a time of their own. |
| `phone_number_id` | Which of the agent's numbers to call from. The agent's own unless you say. |
| `start`, `start_at` | Start now, or at a moment you choose. Leave both out to start it later. |

A campaign is a `draft` until it starts. Starting checks that it can place a call: the agent is
published, it has a number to call from, and every contact has the values the agent says out loud —
other than a field with a default, which fills in, or one that is not required. It also needs
somebody on its list: a campaign started with no contacts would finish at once, so it is refused
with `campaign_empty`. To send the contacts in batches, create it as a draft, add them, then start
it. A campaign with a `start_at` may be empty until then. If nobody is on its list when the time
comes, it starts and waits for its first contacts instead of finishing.

## Start, pause, resume and cancel

**curl**

```sh
#!/bin/sh
# Start a campaign
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// Start a campaign
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Start a campaign
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Start a campaign
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Start a campaign
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("POST", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Start a campaign
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Start a campaign
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/start");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
    CURLOPT_POSTFIELDS => "",
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

**curl**

```sh
#!/bin/sh
# Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("POST", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Pause a campaign. Calls in progress carry on; nobody new is called until you resume it.
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/pause");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
    CURLOPT_POSTFIELDS => "",
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

**curl**

```sh
#!/bin/sh
# Resume a campaign
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  --json '{
  "reset_attempts": false
}'
```

**JavaScript**

```js
// Resume a campaign
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "reset_attempts": false
  }),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Resume a campaign
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    json={
        "reset_attempts": False,
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Resume a campaign
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	payload := []byte(`{
  "reset_attempts": false
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume", bytes.NewReader(payload))
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	request.Header.Set("Content-Type", "application/json")
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Resume a campaign
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "reset_attempts": false
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .header("Content-Type", "application/json")
            .method("POST", HttpRequest.BodyPublishers.ofString(body))
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Resume a campaign
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume")
{
    Content = new StringContent(
        """
        {
          "reset_attempts": false
        }
        """,
        Encoding.UTF8,
        "application/json"),
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Resume a campaign
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/resume");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "reset_attempts" => false,
    ]),
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

**curl**

```sh
#!/bin/sh
# Cancel a campaign. For good: nobody else on the list is called.
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// Cancel a campaign. For good: nobody else on the list is called.
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Cancel a campaign. For good: nobody else on the list is called.
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Cancel a campaign. For good: nobody else on the list is called.
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Cancel a campaign. For good: nobody else on the list is called.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("POST", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Cancel a campaign. For good: nobody else on the list is called.
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Cancel a campaign. For good: nobody else on the list is called.
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/cancel");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
    CURLOPT_POSTFIELDS => "",
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

Pausing stops new calls. Calls in progress carry on, and resuming picks up where it stopped; send
`reset_attempts: true` to give everybody not reached so far every try back. Cancelling stops it for
good. Each answers with the campaign, and one that has already happened answers it as it is, so a
retry is harmless. A campaign that has finished — `completed` or `cancelled` — answers
`campaign_finished`.

A campaign can pause itself, too: when your organization's calls for the day are used up, or when
calls cannot be placed at all. `paused_reason` says why, and resuming is your decision.

While the deployment has phone calls switched off, creating, starting and resuming a campaign are
refused with `feature_disabled`. Pausing and cancelling still work.

## Add more people

**curl**

```sh
#!/bin/sh
# Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  --json '{
  "contacts": [
    {
      "to_number": "+12025550125",
      "variables": {
        "first_name": "Lina"
      },
      "external_id": "lead-20933"
    }
  ]
}'
```

**JavaScript**

```js
// Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "contacts": [
      {
        "to_number": "+12025550125",
        "variables": {
          "first_name": "Lina"
        },
        "external_id": "lead-20933"
      }
    ]
  }),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    json={
        "contacts": [{"to_number": "+12025550125", "variables": {"first_name": "Lina"}, "external_id": "lead-20933"}],
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	payload := []byte(`{
  "contacts": [
    {
      "to_number": "+12025550125",
      "variables": {
        "first_name": "Lina"
      },
      "external_id": "lead-20933"
    }
  ]
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts", bytes.NewReader(payload))
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	request.Header.Set("Content-Type", "application/json")
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "contacts": [
                {
                  "to_number": "+12025550125",
                  "variables": {
                    "first_name": "Lina"
                  },
                  "external_id": "lead-20933"
                }
              ]
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .header("Content-Type", "application/json")
            .method("POST", HttpRequest.BodyPublishers.ofString(body))
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts")
{
    Content = new StringContent(
        """
        {
          "contacts": [
            {
              "to_number": "+12025550125",
              "variables": {
                "first_name": "Lina"
              },
              "external_id": "lead-20933"
            }
          ]
        }
        """,
        Encoding.UTF8,
        "application/json"),
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Add people to a campaign. A number the campaign already holds is skipped, so sending a batch again is safe.
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "contacts" => [
            [
                "to_number" => "+12025550125",
                "variables" => [
                    "first_name" => "Lina",
                ],
                "external_id" => "lead-20933",
            ],
        ],
    ]),
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

A campaign that is a draft, running or paused takes more contacts, and a running one calls them in
its next calling hours. A number the campaign already holds is skipped and listed in
`already_in_campaign`, so sending a batch again is safe, even while the first is still being added.
`+15550100` and `15550100` are the same number. A campaign completes once nobody is left to call;
after that, start a new one.

## See how it went

**curl**

```sh
#!/bin/sh
# Read a campaign and its progress
curl -sS --fail-with-body -X GET "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// Read a campaign and its progress
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Read a campaign and its progress
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Read a campaign and its progress
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("GET", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// Read a campaign and its progress
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// Read a campaign and its progress
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// Read a campaign and its progress
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b");
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

`contacts` counts the people in each state. List them, with how it went for each:

**curl**

```sh
#!/bin/sh
# See how each call in a campaign went
curl -sS --fail-with-body -X GET "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// See how each call in a campaign went
const response = await fetch("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# See how each call in a campaign went
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    params={
        "status": "done",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// See how each call in a campaign went
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("GET", "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// See how each call in a campaign went
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// See how each call in a campaign went
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// See how each call in a campaign went
$curl = curl_init("https://api.aigently.ai/v1/campaigns/1c3e5a7b-9d0f-4b2c-8e4a-6c8e0a2c4e6b/contacts?status=done");
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

| `status` | What it means |
|---|---|
| `queued` | Waiting for their turn, or for their next try at `next_attempt_at`. |
| `dialing` | On a call now. |
| `done` | Reached — or a message was left on their machine. |
| `exhausted` | Every try was used without reaching them. |
| `failed` | Not called: the platform refused, and `error` says why — a number on your [do-not-call list](/guides/outbound-calls#numbers-that-must-not-be-called), for one. |

`conversation_id` is their latest conversation. Every conversation with them carries their
`metadata`, their `external_id` and the `campaign_id`, so a [webhook](/guides/webhooks) about it can
be tied back to its lead.

All your campaigns, newest first:

**curl**

```sh
#!/bin/sh
# List campaigns
curl -sS --fail-with-body -X GET "https://api.aigently.ai/v1/campaigns?limit=10" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// List campaigns
const response = await fetch("https://api.aigently.ai/v1/campaigns?limit=10", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# List campaigns
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://api.aigently.ai/v1/campaigns",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    params={
        "limit": 10,
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// List campaigns
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	request, err := http.NewRequest("GET", "https://api.aigently.ai/v1/campaigns?limit=10", nil)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := io.ReadAll(response.Body)
	if response.StatusCode >= 400 {
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	fmt.Println(string(body))
}
```

**Java**

```java
// List campaigns
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/campaigns?limit=10"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .method("GET", HttpRequest.BodyPublishers.noBody())
            .build();
        // HTTP/1.1: on a plain-http address Java's default asks to upgrade, which not every
        // server allows.
        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            System.err.println(response.body());
            System.exit(1);
        }
        System.out.println(response.body());
    }
}
```

**C#**

```csharp
// List campaigns
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AIGENTLY_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.aigently.ai/v1/campaigns?limit=10")
{
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(body);
    return 1;
}
Console.WriteLine(body);
return 0;
```

**PHP**

```php
<?php
// List campaigns
$curl = curl_init("https://api.aigently.ai/v1/campaigns?limit=10");
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

## Hear about it

Five events, sent to live receivers only:

| Event | When |
|---|---|
| `campaign.started` | It started calling — at once, at its start time, or again after a pause. |
| `campaign.paused` | Paused by somebody, or by itself, with `paused_reason`. |
| `campaign.completed` | Nobody is left to call. |
| `campaign.cancelled` | Stopped for good. |
| `campaign.contact_finished` | One person's final result: `done`, `exhausted` or `failed`. |

Each carries the campaign, or the contact, as the API reads it. A contact's number, values and
metadata are left out for a receiver that does not include values, as they are for conversations.

A campaign belongs to your organization, not to the key that made it: revoking the key does not stop
it. Pause or cancel it instead.
