diff --git a/fern/pages/core-concepts/inboxes.mdx b/fern/pages/core-concepts/inboxes.mdx index fc3a145..b2899ee 100644 --- a/fern/pages/core-concepts/inboxes.mdx +++ b/fern/pages/core-concepts/inboxes.mdx @@ -126,6 +126,34 @@ console.log(`Total Inboxes: ${allInboxes.count}`); domains](/guides/domains/managing-domains) to learn more. +## 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. + + +```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); +``` + + +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. @@ -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. """ @@ -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. */ diff --git a/fern/pages/core-concepts/pods.mdx b/fern/pages/core-concepts/pods.mdx index bce31c3..3b07228 100644 --- a/fern/pages/core-concepts/pods.mdx +++ b/fern/pages/core-concepts/pods.mdx @@ -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 @@ -281,9 +281,9 @@ Pod: "Marketing-Agent" - 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. ## Next Steps diff --git a/fern/pages/guides/multi-tenancy.mdx b/fern/pages/guides/multi-tenancy.mdx index c141bde..bd18157 100644 --- a/fern/pages/guides/multi-tenancy.mdx +++ b/fern/pages/guides/multi-tenancy.mdx @@ -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). + ```python # Create a key that can only access Acme's pod @@ -84,21 +88,55 @@ console.log(scopedKey.apiKey); ``` +### 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. + + +```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); +``` + + The full API key is only returned **once** at creation. If you lose it, delete it and create a new one. -You can list and delete scoped keys for any pod: +You can list and delete scoped keys for any pod or inbox: ```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); ``` @@ -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}", @@ -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, } ``` @@ -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}`, @@ -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, }; } diff --git a/fern/pages/knowledge-base/getting-api-key.mdx b/fern/pages/knowledge-base/getting-api-key.mdx index 0fcb9ff..270b6b9 100644 --- a/fern/pages/knowledge-base/getting-api-key.mdx +++ b/fern/pages/knowledge-base/getting-api-key.mdx @@ -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: diff --git a/fern/pages/knowledge-base/pods-multi-tenant.mdx b/fern/pages/knowledge-base/pods-multi-tenant.mdx index 7defccd..0f6e319 100644 --- a/fern/pages/knowledge-base/pods-multi-tenant.mdx +++ b/fern/pages/knowledge-base/pods-multi-tenant.mdx @@ -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.