<!-- Generated by `just docs` from proto/automaton/v1/connections.proto. Edit the source, not this file. -->

# ConnectionsService

Connect an account on behalf of one of your users, read the state of one, and disconnect it. Your code never sees a credential: it starts a flow, sends the person to the address that comes back, and afterwards holds an account id.

Every call is a POST to `https://api.atmon.ai/automaton.v1.ConnectionsService/<Call>` with a JSON body, and authenticates with `Authorization: Bearer <your project key>`. Field names in JSON are lowerCamelCase, so the field written `tool_slug` below is `toolSlug` on the wire. [How to call the API](./index.md) has the whole convention.

## Calls

| Call | Request | Response | Summary |
| --- | --- | --- | --- |
| `InitiateConnection` | `InitiateConnectionRequest` | `InitiateConnectionResponse` | Starts an auth flow for one entity and one toolkit. |
| `SubmitAPIKey` | `SubmitAPIKeyRequest` | `SubmitAPIKeyResponse` | Completes an api_key connection. |
| `SubmitBasicAuth` | `SubmitBasicAuthRequest` | `SubmitBasicAuthResponse` | Completes a basic connection. |
| `GetConnectedAccount` | `GetConnectedAccountRequest` | `GetConnectedAccountResponse` | Reads one connected account's status and granted scopes. |
| `ListConnectedAccounts` | `ListConnectedAccountsRequest` | `ListConnectedAccountsResponse` | Lists an entity's connected accounts, optionally for one toolkit. |
| `RevokeConnectedAccount` | `RevokeConnectedAccountRequest` | `RevokeConnectedAccountResponse` | Revokes an account: the stored credential is dropped and the provider's revocation endpoint is called where the toolkit declares one. |
| `UpdateAccountVariables` | `UpdateAccountVariablesRequest` | `UpdateAccountVariablesResponse` | Corrects an account's account_variables in place, so a typo in a subdomain is an edit rather than a reconnection. |

### InitiateConnection

Starts an auth flow for one entity and one toolkit. For an OAuth toolkit it
answers the authorization URL to send the end user to; the account stays
PENDING until the flow completes, and the flow expires after ten minutes.
The project's scope caps are enforced here, before a consent URL exists,
because an approved scope is a real grant whatever happens afterward.

Request `InitiateConnectionRequest`, response `InitiateConnectionResponse`.

```http
POST /automaton.v1.ConnectionsService/InitiateConnection HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "entityId": "...",
  "toolkitSlug": "...",
  "requestedScopes": ["..."],
  "redirectUri": "...",
  "credentialKind": "...",
  "accountVariables": {"...": "..."},
  "authScheme": "..."
}
```

The response:

```json
{
  "connectedAccountId": "...",
  "authorizationUrl": "..."
}
```

### SubmitAPIKey

Completes an api_key connection. It is the only RPC that carries a raw
credential, and it carries it one way: the account goes ACTIVE and the key
is readable by nothing afterwards.

Request `SubmitAPIKeyRequest`, response `SubmitAPIKeyResponse`.

```http
POST /automaton.v1.ConnectionsService/SubmitAPIKey HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "connectedAccountId": "...",
  "apiKey": "..."
}
```

The response:

```json
{}
```

### SubmitBasicAuth

Completes a basic connection. It carries the two halves of an HTTP basic
credential the same way SubmitAPIKey carries one key: one way, into the
vault, and the account goes ACTIVE.

Request `SubmitBasicAuthRequest`, response `SubmitBasicAuthResponse`.

```http
POST /automaton.v1.ConnectionsService/SubmitBasicAuth HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "connectedAccountId": "...",
  "username": "...",
  "password": "..."
}
```

The response:

```json
{}
```

### GetConnectedAccount

Reads one connected account's status and granted scopes. No method on this
service ever returns credential material.

Request `GetConnectedAccountRequest`, response `GetConnectedAccountResponse`.

```http
POST /automaton.v1.ConnectionsService/GetConnectedAccount HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "id": "..."
}
```

The response:

```json
{
  "connectedAccount": {
    "id": "...",
    "entityId": "...",
    "toolkitSlug": "...",
    "status": "CONNECTION_STATUS_PENDING",
    "grantedScopes": ["..."],
    "createdAt": "2026-01-31T09:15:00Z",
    "credentialKind": "...",
    "accountVariables": {"...": "..."},
    "authScheme": "..."
  }
}
```

### ListConnectedAccounts

Lists an entity's connected accounts, optionally for one toolkit. This is
how to tell in advance whether a call would answer not_connected.

Request `ListConnectedAccountsRequest`, response `ListConnectedAccountsResponse`.

```http
POST /automaton.v1.ConnectionsService/ListConnectedAccounts HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "entityId": "...",
  "toolkitSlug": "..."
}
```

The response:

```json
{
  "connectedAccounts": [{
    "id": "...",
    "entityId": "...",
    "toolkitSlug": "...",
    "status": "CONNECTION_STATUS_PENDING",
    "grantedScopes": ["..."],
    "createdAt": "2026-01-31T09:15:00Z",
    "credentialKind": "...",
    "accountVariables": {"...": "..."},
    "authScheme": "..."
  }]
}
```

### RevokeConnectedAccount

Revokes an account: the stored credential is dropped and the provider's
revocation endpoint is called where the toolkit declares one. A provider
that refuses the revocation is logged, not surfaced; the account is revoked
locally either way.

Request `RevokeConnectedAccountRequest`, response `RevokeConnectedAccountResponse`.

```http
POST /automaton.v1.ConnectionsService/RevokeConnectedAccount HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "id": "..."
}
```

The response:

```json
{}
```

### UpdateAccountVariables

Corrects an account's account_variables in place, so a typo in a subdomain
is an edit rather than a reconnection. It changes where this account's
calls go, never what its credential is, and it refuses a variable that
decides the dialled origin: that value chose the server the account's
credential is presented to, and moving it afterwards would present a live
credential to a server the provider never issued it for. Changing the
origin is a new connection.

Request `UpdateAccountVariablesRequest`, response `UpdateAccountVariablesResponse`.

```http
POST /automaton.v1.ConnectionsService/UpdateAccountVariables HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "connectedAccountId": "...",
  "accountVariables": {"...": "..."}
}
```

The response:

```json
{
  "connectedAccount": {
    "id": "...",
    "entityId": "...",
    "toolkitSlug": "...",
    "status": "CONNECTION_STATUS_PENDING",
    "grantedScopes": ["..."],
    "createdAt": "2026-01-31T09:15:00Z",
    "credentialKind": "...",
    "accountVariables": {"...": "..."},
    "authScheme": "..."
  }
}
```

## Messages

### ConnectedAccount

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |
| `entity_id` | `string` | 2 |  |
| `toolkit_slug` | `string` | 3 |  |
| `status` | `ConnectionStatus` | 4 |  |
| `granted_scopes` | repeated `string` | 5 |  |
| `created_at` | `google.protobuf.Timestamp` | 6 |  |
| `credential_kind` | `string` | 7 | credential_kind is set instead of toolkit_slug when the account holds a credential for something that is not a catalog toolkit, such as a model provider. Exactly one of the two is ever set. |
| `account_variables` | map<`string`, `string`> | 8 | account_variables are the per-customer parts of the toolkit's base_url this account answered: a subdomain, an application id, a cluster address. They are not secret, which is why they come back on a read: they say where this account's calls go, never what the credential is. |
| `auth_scheme` | `string` | 9 | auth_scheme is the toolkit auth scheme this account was created under ("oauth2", "api_key", or "basic"), which is fixed for its life: the vault holds one credential of that shape. Connecting the same toolkit under another scheme is another account. No account is ever created under "client_credentials": that credential belongs to the project, so there is nothing per-entity to connect. |

### GetConnectedAccountRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |

### GetConnectedAccountResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account` | `ConnectedAccount` | 1 |  |

### InitiateConnectionRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `entity_id` | `string` | 1 |  |
| `toolkit_slug` | `string` | 2 |  |
| `requested_scopes` | repeated `string` | 3 | empty requests the toolkit default |
| `redirect_uri` | `string` | 4 | where the end user lands after consent |
| `credential_kind` | `string` | 5 | credential_kind names a non-catalog credential subject, from a closed set the server registers (model_provider:openai, model_provider:anthropic). It is mutually exclusive with toolkit_slug, takes the api_key path only, and accepts neither scopes nor a redirect: there is no provider app to consent to and no end user to send anywhere. An unregistered kind is refused. |
| `account_variables` | map<`string`, `string`> | 6 | account_variables answers the toolkit's account_variables block, keyed by declared name. Every variable the toolkit declares must be present and no other name may be: a missing one would leave a placeholder in the host of every call, and an extra one would sit on the account unused. |
| `auth_scheme` | `string` | 7 | auth_scheme names which of the toolkit's declared auth schemes to connect under, by type ("oauth2", "api_key", or "basic"). Empty takes the toolkit's default, which is the first scheme its catalog entry declares that this deployment can run and that an entity can connect under. A scheme the toolkit does not declare is refused rather than substituted, and the account records what it was created under. "client_credentials" is refused here: the project holds that credential, so a call under it needs no account. |

### InitiateConnectionResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account_id` | `string` | 1 | status PENDING until the flow completes |
| `authorization_url` | `string` | 2 | send the end user here for OAuth toolkits |

### ListConnectedAccountsRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `entity_id` | `string` | 1 |  |
| `toolkit_slug` | `string` | 2 | empty lists all toolkits for the entity |

### ListConnectedAccountsResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_accounts` | repeated `ConnectedAccount` | 1 |  |

### RevokeConnectedAccountRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |

### RevokeConnectedAccountResponse

No fields. The call takes its scope from the authenticated project.

### SubmitAPIKeyRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account_id` | `string` | 1 | connected_account_id is a PENDING account InitiateConnection returned under the api_key scheme, either for an api_key toolkit or for a credential kind. |
| `api_key` | `string` | 2 | api_key is the raw credential. It is write-only in the strict sense: it is sealed into the vault on arrival, no read on this service or any other ever returns it, and it is never written to a log line or a trace. Send it over TLS; a request that carries it should not be recorded by a proxy. |

### SubmitAPIKeyResponse

No fields. The call takes its scope from the authenticated project.

### SubmitBasicAuthRequest

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account_id` | `string` | 1 | connected_account_id is a PENDING account InitiateConnection returned under the basic scheme. |
| `username` | `string` | 2 | username and password are the two halves of the HTTP basic credential, and both are write-only in the same strict sense as api_key above: sealed into the vault on arrival, returned by no read, written to no log line and no trace. An empty password is accepted, because providers that carry the whole secret in the username half are common. A username holding a colon is refused: RFC 7617 splits the pair on the first colon, so the provider would read a different pair from the one sent. |
| `password` | `string` | 3 |  |

### SubmitBasicAuthResponse

No fields. The call takes its scope from the authenticated project.

### UpdateAccountVariablesRequest

UpdateAccountVariablesRequest corrects the per-customer parts of an existing
account's address. A mistyped subdomain otherwise costs a revoke and a
reconnect, which for an OAuth account means sending the end user through
consent again over a typo.

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account_id` | `string` | 1 |  |
| `account_variables` | map<`string`, `string`> | 2 | account_variables replaces the whole set, keyed by declared name, under the same rule InitiateConnection applies: every variable the toolkit declares must be present and no other name may be. A merge would let a caller send one name and leave the account holding a value nobody has looked at since it was first typed. |

### UpdateAccountVariablesResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `connected_account` | `ConnectedAccount` | 1 |  |

## Enums

### ConnectionStatus

| Value | # | Meaning |
| --- | --- | --- |
| `CONNECTION_STATUS_UNSPECIFIED` | 0 |  |
| `CONNECTION_STATUS_PENDING` | 1 | auth flow started, not completed |
| `CONNECTION_STATUS_ACTIVE` | 2 |  |
| `CONNECTION_STATUS_EXPIRED` | 3 | refresh failed, re-auth required |
| `CONNECTION_STATUS_REVOKED` | 4 |  |
