> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://agentinbox.space/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://agentinbox.space/_mcp/server.

# Руководство: мультитенантность

> Поды, API-ключи с областью действия и фильтрация вебхуков для мультитенантной почты на Agent Inbox.

Если вы строите платформу, где у каждого клиента должна быть своя почтовая инфраструктура, схема такая: один **Pod** на клиента, выданный ему ограниченный API-ключ и вебхуки, направленные в нужный endpoint.

## Pod = изоляция тенанта

У каждого тенанта свой **Pod**. Все ресурсы (ящики, домены, цепочки, черновики) живут внутри него и изолированы от других подов. Подробности — на странице [Поды](/pods).

```python
from agentinbox import Agentinbox

client = Agentinbox()

# Use client_id to map to your internal tenant ID so
# you don't need to maintain a separate mapping table
pod = client.pods.create(client_id="tenant-acme-123")
```

```typescript
import { AgentinboxClient } from "agentinbox";

const client = new AgentinboxClient({ apiKey: process.env.AGENTINBOX_API_KEY! });

const pod = await client.pods.create({ clientId: "tenant-acme-123" });
```

Дальше создаёте ресурсы клиента:

```python
inbox = client.pods.inboxes.create(
    pod.pod_id,
    username="support",
    display_name="Acme Support"
)

domain = client.pods.domains.create(pod.pod_id, domain="acme.com")
```

```typescript
const inbox = await client.pods.inboxes.create(pod.podId, {
  username: "support",
  displayName: "Acme Support",
});

const domain = await client.pods.domains.create(pod.podId, {
  domain: "acme.com",
});
```

**`CLI`**

```bash title="CLI"
# create an inbox in the pod
agentinbox pods:inboxes create \
  --pod-id <pod-id> \
  --username support \
  --display-name "Acme Support"

# add a custom domain to the pod
agentinbox pods:domains create \
  --pod-id <pod-id> \
  --domain acme.com
```

## API-ключи с областью действия

По умолчанию ключи на уровне организации видят всё во всех подах. **Ограниченные (scoped)** ключи привязаны к одному поду или одному ящику: ключ для пода Acme трогает только ресурсы Acme.

Так можно выдать ключ сервису или агенту клиента, не открывая всю организацию.

### Ключи на уровне пода

Доступ ко всем ресурсам пода (ящики, цепочки, черновики, домены).

```python
# Create a key that can only access Acme's pod
scoped_key = client.pods.api_keys.create(
    pod.pod_id,
    name="acme-service-key"
)

# This is the only time you'll see the full key, so store it
print(scoped_key.api_key)
```

```typescript
const scopedKey = await client.pods.apiKeys.create(pod.podId, {
  name: "acme-service-key",
});

// This is the only time you'll see the full key, so store it
console.log(scopedKey.apiKey);
```

**`CLI`**

```bash title="CLI"
# create a key scoped to acme's pod
agentinbox api-keys create \
  --name "acme-service-key" \
  --pod-id <pod-id>
```

### Ключи на уровне ящика

Ещё уже: только один ящик и связанные с ним цепочки, сообщения и черновики. Подходит, когда агенту или интеграции нужен один адрес.

```python
# Create a key that can only access the support inbox
inbox_key = client.inboxes.api_keys.create(
    inbox.inbox_id,
    name="support-inbox-key"
)

print(inbox_key.api_key)
```

```typescript
const inboxKey = await client.inboxes.apiKeys.create(inbox.inboxId, {
  name: "support-inbox-key",
});

console.log(inboxKey.apiKey);
```

> **Warning**
>
> Полный ключ возвращается **один раз** при создании. Потеряли — удалите ключ и создайте новый.

Список и удаление ограниченных ключей для пода или ящика:

```python
# Pod-scoped keys
keys = client.pods.api_keys.list(pod.pod_id)
client.pods.api_keys.delete(pod.pod_id, scoped_key.api_key_id)

# Inbox-scoped keys
inbox_keys = client.inboxes.api_keys.list(inbox.inbox_id)
client.inboxes.api_keys.delete(inbox.inbox_id, inbox_key.api_key_id)
```

```typescript
// Pod-scoped keys
const keys = await client.pods.apiKeys.list(pod.podId);
await client.pods.apiKeys.delete(pod.podId, scopedKey.apiKeyId);

// Inbox-scoped keys
const inboxKeys = await client.inboxes.apiKeys.list(inbox.inboxId);
await client.inboxes.apiKeys.delete(inbox.inboxId, inboxKey.apiKeyId);
```

## Маршрутизация вебхуков

Один endpoint на всех клиентов обычно не нужен. При создании **Webhook** можно ограничить область списком `pod_ids` или `inbox_ids`, чтобы события приходили только по нужным ресурсам.

```python
# Only fires for events in Acme's pod
webhook = client.webhooks.create(
    url="https://your-server.com/webhooks/acme",
    event_types=["message.received", "message.sent"],
    pod_ids=[pod.pod_id]
)

# Or narrow it down to specific inboxes
webhook = client.webhooks.create(
    url="https://your-server.com/webhooks/acme-support",
    event_types=["message.received"],
    inbox_ids=[inbox.inbox_id]
)
```

```typescript
const webhook = await client.webhooks.create({
  url: "https://your-server.com/webhooks/acme",
  eventTypes: ["message.received", "message.sent"],
  podIds: [pod.podId],
});

// Or narrow it down to specific inboxes
const inboxWebhook = await client.webhooks.create({
  url: "https://your-server.com/webhooks/acme-support",
  eventTypes: ["message.received"],
  inboxIds: [inbox.inboxId],
});
```

## Полный сценарий онбординга

Как выглядит подключение нового клиента от начала до конца:

```python
from agentinbox import Agentinbox

client = Agentinbox()

def onboard_tenant(tenant_id: str, domain_name: str):
    # Create isolated pod
    pod = client.pods.create(client_id=tenant_id)

    # Provision inbox + domain
    inbox = client.pods.inboxes.create(
        pod.pod_id,
        username="support",
        display_name=f"{tenant_id} Support"
    )
    domain = client.pods.domains.create(pod.pod_id, domain=domain_name)

    # Pod-scoped key for the tenant
    key = client.pods.api_keys.create(pod.pod_id, name=f"{tenant_id}-key")

    # Inbox-scoped key for the support inbox
    inbox_key = client.inboxes.api_keys.create(
        inbox.inbox_id, name=f"{tenant_id}-support-key"
    )

    # Webhook for their events
    webhook = client.webhooks.create(
        url=f"https://your-server.com/webhooks/{tenant_id}",
        event_types=["message.received"],
        pod_ids=[pod.pod_id]
    )

    return {
        "pod_id": pod.pod_id,
        "inbox_id": inbox.inbox_id,
        "pod_api_key": key.api_key,  # deliver securely to tenant
        "inbox_api_key": inbox_key.api_key,
        "webhook_id": webhook.webhook_id,
    }
```

```typescript
import { AgentinboxClient } from "agentinbox";

const client = new AgentinboxClient({ apiKey: process.env.AGENTINBOX_API_KEY! });

async function onboardTenant(tenantId: string, domainName: string) {
  // Create isolated pod
  const pod = await client.pods.create({ clientId: tenantId });

  // Provision inbox + domain
  const inbox = await client.pods.inboxes.create(pod.podId, {
    username: "support",
    displayName: `${tenantId} Support`,
  });
  const domain = await client.pods.domains.create(pod.podId, {
    domain: domainName,
  });

  // Pod-scoped key for the tenant
  const key = await client.pods.apiKeys.create(pod.podId, {
    name: `${tenantId}-key`,
  });

  // Inbox-scoped key for the support inbox
  const inboxKey = await client.inboxes.apiKeys.create(inbox.inboxId, {
    name: `${tenantId}-support-key`,
  });

  // Webhook for their events
  const webhook = await client.webhooks.create({
    url: `https://your-server.com/webhooks/${tenantId}`,
    eventTypes: ["message.received"],
    podIds: [pod.podId],
  });

  return {
    podId: pod.podId,
    inboxId: inbox.inboxId,
    podApiKey: key.apiKey, // deliver securely to tenant
    inboxApiKey: inboxKey.apiKey,
    webhookId: webhook.webhookId,
  };
}
```