# API keys

> Organization keys, test and live, what each may do and reach — and the older per-agent chat keys.

An API key belongs to your organization. Owners and admins make them on the console's **API keys**
page; making one asks for your password again, and every owner is emailed.

## Test and live

| | Test key, `ag_test_…` | Live key, `ag_live_…` |
|---|---|---|
| Calls | Only [test numbers](/guides/test-mode#test-numbers). Nothing rings. | Real phones. |
| Conversations it reads | Test-key conversations only. | Real ones only. |
| Its events go to | Test receivers. | Live receivers. |
| From a browser | Yes — this site's *Try it* uses one. | Never: `403 live_key_in_browser`. |

## What a key may do

| Permission | Lets it |
|---|---|
| `agents:read` | Read agents and their variables. |
| `phone_numbers:read` | List the organization's numbers. |
| `calls:write` | Place and end calls, and make test calls. |
| `chats:write` | Start and continue chats, upload pictures. |
| `conversations:read` | Read and sync conversations. |
| `transcripts:read` | Read what was said. |
| `recordings:read` | Get recording links. Never part of a preset: tick it on its own. |

The presets — *Read only*, *Outbound calling* and *Chat* — are starting points. A key asking for
something it may not do gets `403 permission_denied`, naming the permission.

## What a key reaches

A key reaches every project, or only the projects you choose — and within them, if you like, only
some agents. Anything outside is `404`, exactly as if it did not exist. `GET /v1/me` says what a key
is and what it reaches:

**curl**

```sh
#!/bin/sh
# Check your key
curl -sS --fail-with-body -X GET "https://api.aigently.ai/v1/me" \
  -H "Authorization: Bearer $AIGENTLY_API_KEY"
```

**JavaScript**

```js
// Check your key
const response = await fetch("https://api.aigently.ai/v1/me", {
  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
# Check your key
# pip install httpx
import os

import httpx

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

**Go**

```go
// Check your key
package main

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

func main() {
	request, err := http.NewRequest("GET", "https://api.aigently.ai/v1/me", 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
// Check your key
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/me"))
            .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
// Check your key
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/me")
{
};
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
// Check your key
$curl = curl_init("https://api.aigently.ai/v1/me");
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";
```

## Where a key works from

A key can be limited to your servers' addresses when it is made: single addresses like
`203.0.113.7`, or ranges like `198.51.100.0/24`, IPv4 or IPv6, up to 20. A request from anywhere
else is refused with `403 address_not_allowed`, and `detail` names the address it came from — the
address your server is seen from, after any proxy of your own. A key made without addresses works
from anywhere. `reach.addresses` in `GET /v1/me` lists them; they cannot be changed afterwards, so
a server that moves needs a new key.

## Rotating and revoking

**Rotate** a key that may have leaked, or when somebody who knew it leaves: it gets a new secret,
and the old one keeps working for the time you choose — none, an hour, a day or a week — so your
servers can be updated first. **Revoke** stops a key on its very next request. An organization can
have 25 keys in use. A key whose maker has left the organization is flagged, never cancelled for
you.

## See what a key has been doing

Each key's **Requests** page in the console, in the key's menu under **API keys**, lists every request
it made in the last 30 days: the method, the path, the status, the error `code` of a refusal, how long
the answer took, and the request id. Use it to see why a request was refused. Request bodies are never
kept.

## If a key leaks

Rotate it, or revoke it, on the API keys page. If the key was pushed to a public repository on
GitHub, it may already be revoked: GitHub's secret scanning recognises our keys by their shape, and
reports them to us. A working key found that way is revoked within seconds — a rotated key's old
secret just stops — and every owner of the organization is emailed with where it was found. Make a
new key for the servers that used it, and remove the secret from the repository, history included.

## The older chat keys

Before organization keys, each agent had its own **secret key** (`ag_sk_…`) on its
**Connect → Server API** tab, for chatting with that one agent. Those keys keep working and can be
rotated, but reach nothing else. Anything new should use an API key.
