# Chat and streaming

> Hold a conversation with an agent from your own app — one message at a time, as a whole reply or as it is written.

The same agents your visitors talk to on your website can chat from your app, your back office or a
channel we do not support yet. A chat needs the `chats:write` permission.

## Start a chat

The first message starts it:

**curl**

```sh
#!/bin/sh
# Start a chat
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/chats" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  -H "Idempotency-Key: chat-20931" \
  --json '{
  "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
  "message": "Hello, I would like to book a table.",
  "variables": {
    "first_name": "Sara"
  }
}'
```

**JavaScript**

```js
// Start a chat
const response = await fetch("https://api.aigently.ai/v1/chats", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "chat-20931",
  },
  body: JSON.stringify({
    "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
    "message": "Hello, I would like to book a table.",
    "variables": {
      "first_name": "Sara"
    }
  }),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

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

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/chats",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
        "Idempotency-Key": "chat-20931",
    },
    json={
        "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
        "message": "Hello, I would like to book a table.",
        "variables": {
            "first_name": "Sara",
        },
    },
    timeout=60,
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Start a chat
package main

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

func main() {
	payload := []byte(`{
  "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
  "message": "Hello, I would like to book a table.",
  "variables": {
    "first_name": "Sara"
  }
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/chats", 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", "chat-20931")
	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 chat
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": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
              "message": "Hello, I would like to book a table.",
              "variables": {
                "first_name": "Sara"
              }
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/chats"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .header("Content-Type", "application/json")
            .header("Idempotency-Key", "chat-20931")
            .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
// Start a chat
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/chats")
{
    Content = new StringContent(
        """
        {
          "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
          "message": "Hello, I would like to book a table.",
          "variables": {
            "first_name": "Sara"
          }
        }
        """,
        Encoding.UTF8,
        "application/json"),
};
request.Headers.Add("Idempotency-Key", "chat-20931");
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 chat
$curl = curl_init("https://api.aigently.ai/v1/chats");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
        "Content-Type: application/json",
        "Idempotency-Key: chat-20931",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "agent_id" => "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
        "message" => "Hello, I would like to book a table.",
        "variables" => [
            "first_name" => "Sara",
        ],
    ]),
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

The answer is a `chat.turn`: the agent's greeting in `opening` (first turn only — show it above the
reply), its `reply`, and the `conversation_id` to send the next message to. `variables` are read only
when a chat starts, so nothing said later can change what the agent was told about the person. An
`Idempotency-Key` is optional here: the same request with the same key returns the same chat.

## Send the next message

**curl**

```sh
#!/bin/sh
# Send the next message
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  --json '{
  "message": "For four people, at seven."
}'
```

**JavaScript**

```js
// Send the next message
const response = await fetch("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "message": "For four people, at seven."
  }),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Send the next message
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    json={
        "message": "For four people, at seven.",
    },
    timeout=60,
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Send the next message
package main

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

func main() {
	payload := []byte(`{
  "message": "For four people, at seven."
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages", 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
// Send the next message
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 = """
            {
              "message": "For four people, at seven."
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages"))
            .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
// Send the next message
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/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages")
{
    Content = new StringContent(
        """
        {
          "message": "For four people, at seven."
        }
        """,
        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
// Send the next message
$curl = curl_init("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages");
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([
        "message" => "For four people, at seven.",
    ]),
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

The agent keeps the history; you keep the id. There is nothing else to keep in step — no turn to
count, no transcript to send.

**One message at a time.** A message sent while the agent is still answering the previous one is
refused with `409 conversation_busy`: wait for the reply. A message sent again with the same
`Idempotency-Key` returns the same reply without asking the agent twice — streamed, if the retry
asks for a stream. If the first attempt's reply failed, the retry gets that failure again
(`502 upstream_unavailable`); send the message under a new key to ask once more.

A chat follows its agent: once the agent is unpublished, archived or deleted, the chat takes no
more messages (`409 agent_not_published`), and each message is answered by the agent's published
version, never by edits that have not been published.

## Stream the reply

Send `stream: true` — or `Accept: text/event-stream` — and the reply arrives as it is written, as
server-sent events:

**curl**

```sh
#!/bin/sh
# Stream the reply as it is written
curl -sS --fail-with-body -N -X POST "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  --json '{
  "message": "Can I bring my dog?",
  "stream": true
}'
```

**JavaScript**

```js
// Stream the reply as it is written
const response = await fetch("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "message": "Can I bring my dog?",
    "stream": true
  }),
});
if (!response.ok) {
  const problem = await response.json();
  throw new Error(`${problem.code}: ${problem.detail}`);
}
// Each event is an "event:" line, a "data:" line of JSON, and a blank line.
const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of response.body) {
  buffer += decoder.decode(chunk, { stream: true });
  let end;
  while ((end = buffer.indexOf("\n\n")) >= 0) {
    const [eventLine, dataLine] = buffer.slice(0, end).split("\n");
    buffer = buffer.slice(end + 2);
    const event = eventLine.slice("event: ".length);
    const data = JSON.parse(dataLine.slice("data: ".length));
    if (event === "delta") process.stdout.write(data.text);
    if (event === "done") console.log("\n", data);
    if (event === "error") throw new Error(`${data.code}: ${data.detail}`);
  }
}
```

**Python**

```python
# Stream the reply as it is written
# pip install httpx
import json
import os

import httpx

with httpx.stream(
    "POST",
    "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
    json={
        "message": "Can I bring my dog?",
        "stream": True,
    },
    timeout=60,
) as response:
    if response.is_error:
        raise SystemExit(response.read().decode())
    # Each event is an "event:" line, a "data:" line of JSON, and a blank line.
    event = None
    for line in response.iter_lines():
        if line.startswith("event: "):
            event = line.removeprefix("event: ")
        elif line.startswith("data: "):
            data = json.loads(line.removeprefix("data: "))
            if event == "delta":
                print(data["text"], end="", flush=True)
            elif event == "done":
                print("\n", data)
            elif event == "error":
                raise SystemExit(f"{data['code']}: {data['detail']}")
```

**Go**

```go
// Stream the reply as it is written
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	payload := []byte(`{
  "message": "Can I bring my dog?",
  "stream": true
}`)
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages", 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()
	if response.StatusCode >= 400 {
		body, _ := io.ReadAll(response.Body)
		fmt.Fprintln(os.Stderr, string(body))
		os.Exit(1)
	}
	// Each event is an "event:" line, a "data:" line of JSON, and a blank line.
	scanner := bufio.NewScanner(response.Body)
	scanner.Buffer(make([]byte, 64*1024), 1024*1024)
	event := ""
	for scanner.Scan() {
		line := scanner.Text()
		if name, found := strings.CutPrefix(line, "event: "); found {
			event = name
			continue
		}
		data, found := strings.CutPrefix(line, "data: ")
		if !found {
			continue
		}
		switch event {
		case "delta":
			var delta struct {
				Text string `json:"text"`
			}
			json.Unmarshal([]byte(data), &delta)
			fmt.Print(delta.Text)
		case "done":
			fmt.Println("\n" + data)
		case "error":
			fmt.Fprintln(os.Stderr, data)
			os.Exit(1)
		}
	}
}
```

**Java**

```java
// Stream the reply as it is written
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "message": "Can I bring my dog?",
              "stream": true
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages"))
            .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<Stream<String>> response =
            client.send(request, HttpResponse.BodyHandlers.ofLines());
        if (response.statusCode() >= 400) {
            response.body().forEach(System.err::println);
            System.exit(1);
        }
        // Each event is an "event:" line, a "data:" line of JSON, and a blank line.
        // Read each data line with your JSON library.
        response.body().filter(line -> !line.isEmpty()).forEach(System.out::println);
    }
}
```

**C#**

```csharp
// Stream the reply as it is written
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

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/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages")
{
    Content = new StringContent(
        """
        {
          "message": "Can I bring my dog?",
          "stream": true
        }
        """,
        Encoding.UTF8,
        "application/json"),
};
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode)
{
    Console.Error.WriteLine(await response.Content.ReadAsStringAsync());
    return 1;
}
// Each event is an "event:" line, a "data:" line of JSON, and a blank line.
using var reader = new StreamReader(await response.Content.ReadAsStreamAsync());
var eventName = "";
while (await reader.ReadLineAsync() is { } line)
{
    if (line.StartsWith("event: "))
    {
        eventName = line["event: ".Length..];
        continue;
    }
    if (!line.StartsWith("data: "))
    {
        continue;
    }
    var data = line["data: ".Length..];
    if (eventName == "delta")
    {
        Console.Write(JsonDocument.Parse(data).RootElement.GetProperty("text").GetString());
    }
    else if (eventName == "done")
    {
        Console.WriteLine("\n" + data);
    }
    else if (eventName == "error")
    {
        Console.Error.WriteLine(data);
        return 1;
    }
}
return 0;
```

**PHP**

```php
<?php
// Stream the reply as it is written
$event = "";
$buffer = "";
$failed = false;
$curl = curl_init("https://api.aigently.ai/v1/conversations/2e4a6c8d-0f1b-4d3e-a5c7-9e1b3d5f7a9c/messages");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "message" => "Can I bring my dog?",
        "stream" => true,
    ]),
    // Each event is an "event:" line, a "data:" line of JSON, and a blank line.
    CURLOPT_WRITEFUNCTION => function ($curl, $chunk) use (&$event, &$buffer, &$failed) {
        $buffer .= $chunk;
        while (($end = strpos($buffer, "\n")) !== false) {
            $line = substr($buffer, 0, $end);
            $buffer = substr($buffer, $end + 1);
            if (str_starts_with($line, "event: ")) {
                $event = substr($line, 7);
            } elseif (str_starts_with($line, "data: ")) {
                $data = substr($line, 6);
                if ($event === "delta") {
                    echo json_decode($data, true)["text"];
                } elseif ($event === "done") {
                    echo "\n", $data, "\n";
                } elseif ($event === "error") {
                    fwrite(STDERR, $data . "\n");
                    $failed = true;
                }
            }
        }
        return strlen($chunk);
    },
]);
curl_exec($curl);
if ($failed || curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $buffer . "\n");
    exit(1);
}
```

| Event | `data` |
|---|---|
| `delta` | `{"text": "…"}` — the next words of the reply. |
| `status` | `{"text": "…"}` — the agent started using one of its tools, in words for a person. |
| `done` | The whole `chat.turn`, exactly as the answer without streaming. |
| `error` | A [problem document](/errors) — the headers are long gone, so this is how a stream fails. |

**Read `done` rather than gluing the deltas together.** It carries the conversation id, what was
collected and whether the agent has finished, which the deltas do not.

## Send a picture

Where the agent accepts images, upload one first, then name it in the next message's
`attachment_ids` — up to four a message:

**curl**

```sh
#!/bin/sh
# Upload a picture to send. Send the id it returns in a message's attachment_ids.
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/attachments" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY" \
  -F "agent_id=3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f" \
  -F "file=@photo.jpg;type=image/jpeg"
```

**JavaScript**

```js
import { readFile } from "node:fs/promises";

// Upload a picture to send. Send the id it returns in a message's attachment_ids.
const form = new FormData();
form.append("agent_id", "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f");
form.append("file", new Blob([await readFile("photo.jpg")], { type: "image/jpeg" }), "photo.jpg");

const response = await fetch("https://api.aigently.ai/v1/attachments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AIGENTLY_API_KEY}`,
  },
  body: form,
});
const answer = await response.json();
if (!response.ok) throw new Error(`${answer.code}: ${answer.detail}`);
console.log(answer);
```

**Python**

```python
# Upload a picture to send. Send the id it returns in a message's attachment_ids.
# pip install httpx
import os

import httpx

with open("photo.jpg", "rb") as upload:
    response = httpx.post(
        "https://api.aigently.ai/v1/attachments",
        headers={
            "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
        },
        data={
            "agent_id": "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
        },
        files={"file": ("photo.jpg", upload, "image/jpeg")},
    )
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// Upload a picture to send. Send the id it returns in a message's attachment_ids.
package main

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

func main() {
	var form bytes.Buffer
	writer := multipart.NewWriter(&form)
	writer.WriteField("agent_id", "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f")
	part, _ := writer.CreateFormFile("file", "photo.jpg")
	contents, err := os.ReadFile("photo.jpg")
	if err != nil {
		panic(err)
	}
	part.Write(contents)
	writer.Close()
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/attachments", &form)
	if err != nil {
		panic(err)
	}
	request.Header.Set("Authorization", "Bearer "+os.Getenv("AIGENTLY_API_KEY"))
	request.Header.Set("Content-Type", writer.FormDataContentType())
	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
// Upload a picture to send. Send the id it returns in a message's attachment_ids.
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
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 boundary = Long.toHexString(System.nanoTime());
        ByteArrayOutputStream form = new ByteArrayOutputStream();
        form.write(("--" + boundary
            + "\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f\r\n--" + boundary
            + "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"photo.jpg\"\r\n"
            + "Content-Type: image/jpeg\r\n\r\n").getBytes());
        form.write(Files.readAllBytes(Path.of("photo.jpg")));
        form.write(("\r\n--" + boundary + "--\r\n").getBytes());
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.aigently.ai/v1/attachments"))
            .header("Authorization", "Bearer " + System.getenv("AIGENTLY_API_KEY"))
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            .method("POST", HttpRequest.BodyPublishers.ofByteArray(form.toByteArray()))
            .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
// Upload a picture to send. Send the id it returns in a message's attachment_ids.
using System.Net.Http.Headers;
using System.Text;

var form = new MultipartFormDataContent();
form.Add(new StringContent("3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f"), "agent_id");
var upload = new ByteArrayContent(File.ReadAllBytes("photo.jpg"));
upload.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
form.Add(upload, "file", "photo.jpg");

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/attachments")
{
    Content = form,
};
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
// Upload a picture to send. Send the id it returns in a message's attachment_ids.
$curl = curl_init("https://api.aigently.ai/v1/attachments");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("AIGENTLY_API_KEY"),
    ],
    CURLOPT_POSTFIELDS => [
        "agent_id" => "3c7d9e1f-5a2b-4c6d-8e0f-1a3b5c7d9e2f",
        "file" => new CURLFile("photo.jpg", "image/jpeg"),
    ],
]);
$body = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
    fwrite(STDERR, $body . "\n");
    exit(1);
}
echo $body, "\n";
```

A picture nobody sends is deleted after six hours.

## End it

**curl**

```sh
#!/bin/sh
# End a call or a chat
curl -sS --fail-with-body -X POST "https://api.aigently.ai/v1/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// End a call or a chat
const response = await fetch("https://api.aigently.ai/v1/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end", {
  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
# End a call or a chat
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://api.aigently.ai/v1/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end",
    headers={
        "Authorization": f"Bearer {os.environ['AIGENTLY_API_KEY']}",
    },
)
if response.is_error:
    raise SystemExit(response.text)
print(response.json())
```

**Go**

```go
// End a call or a chat
package main

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

func main() {
	request, err := http.NewRequest("POST", "https://api.aigently.ai/v1/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end", 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
// End a call or a chat
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/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end"))
            .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
// End a call or a chat
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/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end")
{
};
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
// End a call or a chat
$curl = curl_init("https://api.aigently.ai/v1/conversations/5f0c9a52-7d3e-4b1a-9c8f-6e2d4a0b3c1d/end");
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";
```

A chat also ends on its own after a while without messages. A message to an ended chat is refused
with `409 conversation_ended`; start a new one.

## Limits

| Limit | |
|---|---|
| A message | 4,000 characters. |
| Messages in one conversation | 20 a minute, and a ceiling on the whole conversation — `conversation_limit_reached` past it. |
| Messages per key | 1,200 a minute. |

Chats are charged like chats in the console, from the organization's credit — chats made with a test
key too, because they use the agent's real model. When the credit runs out, a message is refused
with `402 insufficient_credit`.
