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
69 changes: 68 additions & 1 deletion fern/apis/api/definition/accounts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ types:
type: uuid
docs: ID of provider.

AccountStatus:
enum:
- disabled
docs: |
`disabled` refuses every new sign-in for the account until it is
re-enabled. Access tokens already issued are not revoked.

UpdateAccountStatus:
enum:
- enabled
- disabled
docs: |
Status to set. `disabled` stops the inbox from signing in at the
provider; `enabled` re-enables it. An enabled account reads back with no
`status`.

Account:
docs: One inbox signed in at one provider.
properties:
Expand All @@ -34,6 +50,12 @@ types:
sign_in_count:
type: integer
docs: Number of sign-ins at provider.
status:
type: optional<AccountStatus>
docs: Present only while the account is disabled. Absent means the inbox may sign in.
disabled_at:
type: optional<datetime>
docs: Time at which the account was disabled. Present only while `status` is `disabled`.

ListAccountsResponse:
properties:
Expand All @@ -42,6 +64,14 @@ types:
next_page_token: optional<global.PageToken>
accounts: list<Account>

UpdateAccountRequest:
docs: |
Fields to change on the account. Fields you omit are left unchanged and
unknown fields are rejected. Expects at least one field; today that is
`status`.
properties:
status: optional<UpdateAccountStatus>

service:
url: Http
base-path: /accounts
Expand All @@ -52,7 +82,10 @@ service:
method: GET
path: ""
display-name: List Accounts
docs: Lists accounts across all providers.
docs: |
Lists accounts across all providers, scoped to the API key: an
organization key sees every account, a pod key its pod's, an inbox key
its inbox's. Requires `inbox_read`.
request:
name: ListAccountsRequest
query-parameters:
Expand All @@ -67,8 +100,42 @@ service:
method: GET
path: /{account_id}
display-name: Get Account
docs: |
Returns one account by ID. An account outside the key's scope is a 404.
Requires `inbox_read`.
path-parameters:
account_id: AccountId
response: Account
errors:
- global.NotFoundError

update:
method: PATCH
path: /{account_id}/update
display-name: Update Account
docs: |
Updates one account. Set `status` to `disabled` to stop the inbox from
signing in at the provider again, or to `enabled` to re-enable it.
Idempotent: disabling an already disabled account keeps its original
`disabled_at`, and enabling an enabled account is a no-op.

Find the `account_id` with List Accounts. An account exists only after an
inbox's first sign-in at a provider, so it cannot be disabled in advance.
A disable applies to that inbox at that provider whichever sign-in key is
used: the provider's next authorization ends in `access_denied`, and a code
issued earlier is refused with `invalid_grant`. Access tokens already
issued stay valid until they expire, and the provider's own session is
unaffected.

Requires `account_update`, which sign-in keys (`type: public_key`) cannot
hold, so call this with a bearer API key. An account outside the key's
scope is a 404. A 409 means the account changed during the write; read it
again and retry.
path-parameters:
account_id: AccountId
request: UpdateAccountRequest
response: Account
errors:
- global.ValidationError
- global.NotFoundError
- global.ConflictError
5 changes: 5 additions & 0 deletions fern/apis/api/definition/api-keys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ types:
docs: |
Share the organization owner's name and email with providers at sign-in. One permission
for both values.
account_update:
type: optional<boolean>
docs: |
Update accounts: disable or re-enable an inbox's sign-in at a provider. Reading accounts
needs only `inbox_read`.
pod_read:
type: optional<boolean>
docs: Read pods.
Expand Down
1 change: 1 addition & 0 deletions fern/apis/api/definition/inboxes/__package__.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ navigation:
- metrics.yml
- events.yml
- api-keys.yml
- accounts.yml

imports:
global: ../__package__.yml
Expand Down
43 changes: 43 additions & 0 deletions fern/apis/api/definition/inboxes/accounts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json

imports:
global: ../__package__.yml
inboxes: __package__.yml
accounts: ../accounts.yml

service:
url: Http
base-path: /inboxes/{inbox_id}/accounts
path-parameters:
inbox_id: inboxes.InboxId
auth: true

endpoints:
list:
method: GET
path: ""
display-name: List Accounts
docs: |
Lists accounts held by the inbox, across all providers. Requires `inbox_read`.
request:
name: ListAccountsRequest
query-parameters:
limit: optional<global.Limit>
page_token: optional<global.PageToken>
ascending: optional<global.Ascending>
response: accounts.ListAccountsResponse
errors:
- global.ValidationError

get:
method: GET
path: /{account_id}
display-name: Get Account
docs: |
Returns one account held by the inbox. An account elsewhere is a 404. Requires
`inbox_read`.
path-parameters:
account_id: accounts.AccountId
response: accounts.Account
errors:
- global.NotFoundError
1 change: 1 addition & 0 deletions fern/apis/api/definition/pods/__package__.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ navigation:
- lists.yml
- metrics.yml
- api-keys.yml
- accounts.yml

imports:
global: ../__package__.yml
Expand Down
43 changes: 43 additions & 0 deletions fern/apis/api/definition/pods/accounts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json

imports:
global: ../__package__.yml
pods: __package__.yml
accounts: ../accounts.yml

service:
url: Http
base-path: /pods/{pod_id}/accounts
path-parameters:
pod_id: pods.PodId
auth: true

endpoints:
list:
method: GET
path: ""
display-name: List Accounts
docs: |
Lists accounts held by inboxes in the pod, across all providers. Requires `inbox_read`.
request:
name: ListAccountsRequest
query-parameters:
limit: optional<global.Limit>
page_token: optional<global.PageToken>
ascending: optional<global.Ascending>
response: accounts.ListAccountsResponse
errors:
- global.ValidationError

get:
method: GET
path: /{account_id}
display-name: Get Account
docs: |
Returns one account held by inboxes in the pod. An account elsewhere is a 404. Requires
`inbox_read`.
path-parameters:
account_id: accounts.AccountId
response: accounts.Account
errors:
- global.NotFoundError
29 changes: 29 additions & 0 deletions fern/apis/cli/openapi-overrides.yml
Original file line number Diff line number Diff line change
Expand Up @@ -906,3 +906,32 @@ paths:
- pods
- inboxes
x-fern-sdk-method-name: search
/v0/accounts/{account_id}/update:
patch:
x-fern-sdk-group-name:
- accounts
x-fern-sdk-method-name: update
/v0/inboxes/{inbox_id}/accounts:
get:
x-fern-sdk-group-name:
- inboxes
- accounts
x-fern-sdk-method-name: list
/v0/inboxes/{inbox_id}/accounts/{account_id}:
get:
x-fern-sdk-group-name:
- inboxes
- accounts
x-fern-sdk-method-name: get
/v0/pods/{pod_id}/accounts:
get:
x-fern-sdk-group-name:
- pods
- accounts
x-fern-sdk-method-name: list
/v0/pods/{pod_id}/accounts/{account_id}:
get:
x-fern-sdk-group-name:
- pods
- accounts
x-fern-sdk-method-name: get
73 changes: 73 additions & 0 deletions fern/changelog/2026-09-21.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
tags: ["accounts-api", "pods-api", "inboxes-api", "new-feature"]
---

## Summary

Disable an inbox's sign-in at a provider, and re-enable it later, with one call. Accounts now carry a `status`, and accounts can be listed and read under pods and inboxes so an agent can audit sign-ins at exactly the scope its key holds.

### What's new?

**New endpoints:**

- `PATCH /v0/accounts/{account_id}/update` - Update one account. Send `{ "status": "disabled" }` to refuse every new sign-in for that inbox at that provider, or `{ "status": "enabled" }` to re-enable it. Requires the new `account_update` permission.
- `GET /v0/pods/{pod_id}/accounts` and `GET /v0/pods/{pod_id}/accounts/{account_id}` - List and read the accounts held by inboxes in one pod.
- `GET /v0/inboxes/{inbox_id}/accounts` and `GET /v0/inboxes/{inbox_id}/accounts/{account_id}` - List and read the accounts held by one inbox.

**New features:**

- **Account status**: a disabled account carries `status: "disabled"` and `disabled_at` on every read. An account without `status` can sign in.
- **Idempotent updates**: disabling an already disabled account keeps its original `disabled_at`, and enabling an enabled account is a no-op, so a retry never conflicts.
- **`account_update` permission**: a separate grant for the write. Unrestricted keys hold it; restricted keys get it only when granted. Reading accounts still needs only `inbox_read`.
- **Scoped views**: the pod and inbox forms return the same account object as `GET /v0/accounts`, narrowed to the pod or inbox in the path. An account outside that scope is a 404.

### Use cases

Build agents that:

- Cut off a provider that is misbehaving, without deleting the sign-in record
- Pause one inbox's access to a provider during an incident and restore it afterward
- Audit, per pod or per inbox, which providers each agent has signed in to
- Give a tenant's key visibility into its own accounts and nothing beyond its pod

<CodeBlocks>
```python title="Python"
from agentmail import AgentMail

client = AgentMail(api_key="your-api-key")

# disable an inbox's sign-in at a provider
account = client.accounts.update(
account_id="3f1c2a4e-8b7d-4c6e-9a1f-2d3e4f5a6b7c",
status="disabled",
)
print(account.status, account.disabled_at)

# list the accounts one inbox holds
accounts = client.inboxes.accounts.list(inbox_id="agent@agentmail.to")
for item in accounts.accounts:
print(item.provider_name, item.status or "ok")
```

```typescript title="TypeScript"
import { AgentMail } from "agentmail";

const client = new AgentMail({ apiKey: "your-api-key" });

// disable an inbox's sign-in at a provider
const account = await client.accounts.update("3f1c2a4e-8b7d-4c6e-9a1f-2d3e4f5a6b7c", {
status: "disabled",
});
console.log(account.status, account.disabledAt);

// list the accounts one inbox holds
const accounts = await client.inboxes.accounts.list("agent@agentmail.to");
for (const item of accounts.accounts) {
console.log(item.providerName, item.status ?? "ok");
}
```
</CodeBlocks>

<Note>
See [Update Account](https://docs.agentmail.to/api-reference/accounts/update) in the API reference for the request and response shapes.
</Note>
2 changes: 1 addition & 1 deletion fern/pages/guides/agentid-sign-in.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ if (key.type === "public_key") console.log(key.status);
```
</CodeBlocks>

A single check reads the current status. Repeat with a delay while it is `pending`, stopping at expiry or an error. `active` means the key is ready to sign in as the inbox. Use [List Provider Accounts](https://docs.agentmail.to/api-reference/providers/list-accounts) to inspect accounts at the provider.
A single check reads the current status. Repeat with a delay while it is `pending`, stopping at expiry or an error. `active` means the key is ready to sign in as the inbox. Use [List Provider Accounts](https://docs.agentmail.to/api-reference/providers/list-accounts) to inspect accounts at the provider. To stop an inbox from signing in at a provider again, disable its account with [Update Account](https://docs.agentmail.to/api-reference/accounts/update).

## Manage sign-in keys

Expand Down
Loading
Loading