Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions fern/pages/core-concepts/inboxes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,34 @@ console.log(`Total Inboxes: ${allInboxes.count}`);
domains](/guides/domains/managing-domains) to learn more.
</Tip>

## Inbox-scoped API keys

You can create API keys that are restricted to a single inbox. An inbox-scoped key can only access that inbox's threads, messages, and drafts. This is useful when you want to give an agent or integration the minimum access it needs.

<CodeBlocks>
```python
# Create a key scoped to one inbox
key = client.inboxes.api_keys.create(
new_inbox.inbox_id,
name="support-agent-key"
)

# The full key is only returned once
print(key.api_key)
```

```typescript title="TypeScript"
const key = await client.inboxes.apiKeys.create(newInbox.id, {
name: "support-agent-key",
});

// The full key is only returned once
console.log(key.apiKey);
```
</CodeBlocks>

See the [Multi-Tenancy guide](/multi-tenancy#inbox-scoped-keys) for more on scoped keys.

## Copy for Cursor / Claude

Copy one of the blocks below into Cursor or Claude for complete Inboxes API knowledge in one shot.
Expand All @@ -143,6 +171,9 @@ Copy one of the blocks below into Cursor or Claude for complete Inboxes API know
- inboxes.list(limit?, page_token?)
- inboxes.update(inbox_id, display_name)
- inboxes.delete(inbox_id)
- inboxes.api_keys.create(inbox_id, name) — inbox-scoped key
- inboxes.api_keys.list(inbox_id)
- inboxes.api_keys.delete(inbox_id, api_key_id)

Errors: SDK raises on 4xx/5xx. Rate limit: 429 with Retry-After.
"""
Expand Down Expand Up @@ -174,6 +205,9 @@ Copy one of the blocks below into Cursor or Claude for complete Inboxes API know
* - inboxes.list({ limit?, pageToken? })
* - inboxes.update(inboxId, { displayName })
* - inboxes.delete(inboxId)
* - inboxes.apiKeys.create(inboxId, { name }) — inbox-scoped key
* - inboxes.apiKeys.list(inboxId)
* - inboxes.apiKeys.delete(inboxId, apiKeyId)
*
* Errors: SDK throws on 4xx/5xx. Rate limit: 429 with Retry-After.
*/
Expand Down
8 changes: 4 additions & 4 deletions fern/pages/core-concepts/pods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ async function offboardCustomer(podId: string) {

**What's NOT Isolated to a Pod:**

- Organization-level API keys (these can access any resources in any pod). Use [scoped API keys](/multi-tenancy#step-3-create-scoped-api-keys) to restrict access to a single pod.
- Organization-level API keys (these can access any resources in any pod). Use [pod-scoped API keys](/multi-tenancy#pod-scoped-keys) to restrict access to a single pod, or [inbox-scoped API keys](/multi-tenancy#inbox-scoped-keys) to restrict access to a single inbox.

## Common Patterns and Use Cases

Expand Down Expand Up @@ -281,9 +281,9 @@ Pod: "Marketing-Agent"
</Accordion>

<Accordion title="Can I set custom permissions per pod?">
Yes! You can create scoped API keys that are restricted to a single pod. A
scoped key can only access resources within its pod. See the
[Multi-Tenancy guide](/multi-tenancy) for details on provisioning scoped keys.
Yes! You can create pod-scoped API keys that are restricted to a single pod, or
inbox-scoped API keys that are restricted to a single inbox within a pod. See the
[Multi-Tenancy guide](/multi-tenancy#scoped-api-keys) for details on provisioning scoped keys.
</Accordion>

## Next Steps
Expand Down
62 changes: 56 additions & 6 deletions fern/pages/guides/multi-tenancy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,14 @@ const domain = await client.pods.domains.create(pod.podId, {

## Scoped API Keys

By default, API keys are organization-level and can access everything across all pods. Scoped API keys are restricted to a single pod. If a key is scoped to Acme's pod, it can only touch Acme's resources. Nothing else.
By default, API keys are organization-level and can access everything across all pods. Scoped API keys restrict access to a single pod or a single inbox. If a key is scoped to Acme's pod, it can only touch Acme's resources. Nothing else.

This is useful when you want to hand a key to a tenant's service or agent without exposing your whole org.

### Pod-scoped keys

Pod-scoped keys can access all resources within a pod (inboxes, threads, drafts, domains).

<CodeBlocks>
```python
# Create a key that can only access Acme's pod
Expand All @@ -84,21 +88,55 @@ console.log(scopedKey.apiKey);
```
</CodeBlocks>

### Inbox-scoped keys

Inbox-scoped keys are even more restrictive: they only grant access to a single inbox and its threads, messages, and drafts. Use these when an agent or integration only needs to operate on one address.

<CodeBlocks>
```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);
```
</CodeBlocks>

<Warning>
The full API key is only returned **once** at creation. If you lose it, delete it and create a new one.
</Warning>

You can list and delete scoped keys for any pod:
You can list and delete scoped keys for any pod or inbox:

<CodeBlocks>
```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);
```
</CodeBlocks>

Expand Down Expand Up @@ -161,9 +199,14 @@ def onboard_tenant(tenant_id: str, domain_name: str):
)
domain = client.pods.domains.create(pod.pod_id, domain=domain_name)

# Scoped key for the tenant
# 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}",
Expand All @@ -174,7 +217,8 @@ def onboard_tenant(tenant_id: str, domain_name: str):
return {
"pod_id": pod.pod_id,
"inbox_id": inbox.inbox_id,
"api_key": key.api_key, # deliver securely to tenant
"pod_api_key": key.api_key, # deliver securely to tenant
"inbox_api_key": inbox_key.api_key,
"webhook_id": webhook.webhook_id,
}
```
Expand All @@ -197,11 +241,16 @@ async function onboardTenant(tenantId: string, domainName: string) {
domain: domainName,
});

// Scoped key for the tenant
// 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}`,
Expand All @@ -212,7 +261,8 @@ async function onboardTenant(tenantId: string, domainName: string) {
return {
podId: pod.podId,
inboxId: inbox.inboxId,
apiKey: key.apiKey, // deliver securely to tenant
podApiKey: key.apiKey, // deliver securely to tenant
inboxApiKey: inboxKey.apiKey,
webhookId: webhook.webhookId,
};
}
Expand Down
9 changes: 9 additions & 0 deletions fern/pages/knowledge-base/getting-api-key.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ const inbox = await client.inboxes.create();
console.log(`Connected! Created inbox: ${inbox.inboxId}`);
```

## Scoped API keys

In addition to organization-level keys, you can create keys scoped to a single pod or inbox. Scoped keys restrict access so that the key can only operate on resources within that pod or inbox.

- **Pod-scoped keys**: Create via `POST /pods/{pod_id}/api-keys` or `client.pods.api_keys.create()`
- **Inbox-scoped keys**: Create via `POST /inboxes/{inbox_id}/api-keys` or `client.inboxes.api_keys.create()`

See the [Multi-Tenancy guide](/multi-tenancy#scoped-api-keys) for details.

## Free tier

No credit card required to get started. The free tier includes:
Expand Down
1 change: 1 addition & 0 deletions fern/pages/knowledge-base/pods-multi-tenant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ When you delete an inbox, all associated messages, threads, and drafts are autom
- **Cross-pod email works normally.** Inboxes in different Pods can send and receive emails from each other, just like any other email addresses. Pods isolate data access, not email delivery.
- **Inboxes cannot move between Pods.** If you need to reassign an inbox, create a new one in the target Pod.
- **Pod-scoped API keys.** You can create API keys scoped to a specific Pod, so each tenant only has access to their own resources. Create them via `POST /pods/{pod_id}/api-keys`.
- **Inbox-scoped API keys.** For even finer control, you can create keys scoped to a single inbox. These keys can only access that inbox's threads, messages, and drafts. Create them via `POST /inboxes/{inbox_id}/api-keys`.
- **No limit on Pods.** You can create as many Pods as you need for your customers.
- **Domains can be scoped to one Pod or all Pods.** A domain cannot be shared across a subset of Pods.

Expand Down
Loading