# List connectors (/docs/reference/connectors/list)

`GET https://api.corespeed.io/connectors`

#### Authorizations

* `Authorization` (string, header, required) — `Bearer <sk-cs-…>` — a CoreSpeed API key, or a user session JWT. `x-api-key: <sk-cs-…>` is accepted in its place.

The array holds **three entry variants**, discriminated by `kind`:

* **OAuth connector** — no `kind` field. Carries `auth` and a real `icon_url`.
* **Remote MCP connector** — `kind: "remote-mcp"`, an org-registered upstream
  MCP server. Carries a `remote` block, **no `auth`**, `icon_url: null`, and
  always an empty `accounts` array.
* **Device** — `kind: "device"`, a machine a member tied with
  [`cs tie`](/docs/cli/tie). Carries a `device` block, **no `auth`** and no
  `category`, `icon_url: null`, and always an empty `accounts` array.

A client that requires `auth` or a non-null `icon_url` will reject valid remote
and device entries. Branch on `kind` first: no `kind` is an OAuth connector,
and only `"remote-mcp"` has a `remote` block. Skip an entry whose `kind` you do
not recognize — it is a variant newer than your client.

#### Shared fields

* `id` (string, required) — Connector id (a slug for remote entries, `device__<slug>` for devices), and the prefix of its tool names (`notion__create_page`; remote tools carry the org marker on top: `org__<slug>__<tool>`; a device's are `device__<slug>__<tool>`).
* `kind` ("remote-mcp" | "device", optional) — `"remote-mcp"` on org-registered remote MCP connectors, `"device"` on tied machines. Absent on OAuth connectors — that absence is the discriminator.
* `category` (string, optional) — What the connector is for, from a closed vocabulary: `"crm"`, `"support"`, `"marketing"`, `"finance"`, `"commerce"`, `"hr"`, `"projects"`, `"calendar"`, `"messaging"`, `"social"`, `"docs"`, `"files"`, `"design"`, `"code"`, `"infra"`, `"data"`, `"web-search"`. Group or filter the catalog by it rather than by name. **Absent on remote and device entries** — the platform cannot know what an org-registered upstream fronts or what a tied machine is used for, so it publishes no guess. Treat a missing category as uncategorized; do not substitute a default.
* `name` (string, required) — Display name as the provider brands it, the registered display name, or the name a machine was tied under.
* `description` (string, required) — What the connector's tools can do. On remote entries this is generated and marks the upstream as self-described and unverified; on device entries it is one fixed sentence for every machine.
* `url` (string, required) — The connector's own resource URL on this host. On device entries it is the devices collection, `/devices`, not the machine; those routes answer a signed-in member's session and refuse an API key with `403 session_required`.
* `mcp_url` (string, required) — Always the unified `POST /mcp` endpoint. Per-connector MCP URLs are retired and return `404`.
* `status` (string, required) — `"connected" | "needs_reauth" | "disconnected"` for this caller. A remote entry can also read `"dcr_pending"`: an interrupted OAuth client registration that holds its slug until an admin removes it. A device entry always reads `"connected"`, from the moment the machine is tied until it is revoked; whether it is online right now is `device.state`.
* `icon_url` (string | null, required) — Icon URL. The field is `icon_url` — not `icon` — and it is `null` on remote and device entries. Some entries add `icon_dark_url`, artwork for dark backgrounds; when it is absent, reuse `icon_url`.
* `accounts` (array, required) — Connected accounts visible to this caller. Empty when disconnected, and always empty on remote and device entries.

#### OAuth connector only

* `auth` (object, required) — Grant metadata. `auth.type` is `"oauth2"` (see the grant shapes below), `"oauth1a"` (Trello and Zotero — an OAuth 1.0a ceremony with the same fields as the upstream-console shape), or `"api_key"` — an api-key connector is connected by pasting a key in the dashboard, its `scopes` is always empty, and it never carries a `credential_source` block. Any curated entry may additionally carry `auth.api_key_connect: true` (this environment accepts pasted keys for it — on an oauth2 connector that is a second connect door) and `auth.api_key_probe` (`false` = the paste is stored unverified with a fingerprint identity); OAuth-typed entries may carry `auth.oauth_configured` (`false` = no OAuth app can serve a connect — treat the key door as the only method). Each account's `auth_kind` names the door that created it. `auth.credential_setup_urls`, when present, links to the vendor's pages for creating an OAuth app (`oauth_app`) or a key (`api_key`). Never present on remote entries.
* `credential_source` (object, optional) — Curated OAuth entries only (never on `auth.type: "api_key"` connectors — a pasted key rides no app): which OAuth app carries this connector's authorizations for your organization. `active` is `"platform"` (CoreSpeed's app), `"org"` (your organization's own app), or `"none"` (the org mechanism is open but nothing is configured yet — connect is unavailable until an organization admin sets it up). `org_clients_enabled` says whether your organization may bring its own app. Organization admins additionally receive an `org` block (`client_id`, `has_client_secret`, `set_by`, `created_at`, `rotated_at`, and — on the single-connector read — `connections`). Absent on older deployments; treat absence as platform-served.
* `connection_methods` (array, optional) — The ways this caller can connect. Each method has an `id`, a `label`, a `kind` (`"oauth"` or `"static-credential"`), an optional `source` (`"platform"` or `"org"`), a `status` (`"ready"`, `"setup-required"`, or `"admin-required"`), a `description`, an optional `setup_url`, and a form `schema` for the fields it needs. Never the stored values.
* `tool_count` (number, optional) — How many tools this connector adds to `POST /mcp`. Absent from this index on connectors with `transport: "upstream-mcp"`, whose tools come from the vendor's server — [`GET /connectors/{id}`](/docs/reference/connectors/get) reports it once your connected account's hosted tools load, and omits it otherwise — and on a connector with no tool provider at all. Absent is not the same as zero.
* `transport` ("upstream-mcp", optional) — Present when the tools come from the vendor's own hosted MCP server, so the vendor can change them. `transport_notice` carries the line to show users. Absent on every other connector.
* `launch_stage` ("beta", optional) — Present only while the connector is in beta. Absent means generally available.
* `connected_workspaces` (number, optional) — How many workspaces across CoreSpeed have connected this connector — a popularity count, never which ones. Absent when zero.

#### Remote MCP connector only

* `remote.upstream_url` (string, required) — The registered upstream MCP server URL.
* `remote.auth_mode` (string, required) — How CoreSpeed authenticates to the upstream: `"oauth"`, `"static_bearer"`, or `"none"`. `"none"` additionally carries a `remote.warning`, and so does a `dcr_pending` entry.
* `remote.registration_id` (string, optional) — The registration's id. Pass it as `expected_registration_id` when you remove a `dcr_pending` reservation.
* `remote.tool_count` (number | null, required) — Tools in the last snapshot, or **`null` until the first successful snapshot** — a registration is kept when its initial snapshot fails or while OAuth authorization is still pending. The remote single-connector read (`GET /connectors/org/:slug`, see [Get connector](/docs/reference/connectors/get)) also returns `remote.tools`. Those are CoreSpeed **snapshot records**, not upstream MCP `Tool` objects: each carries the sanitized published `name`, the `original_name` it maps back to, a snake-case `input_schema`, and a `schema_hash`. A client reading `inputSchema` off these will find nothing.
* `remote.snapshot_fetched_at` (string | null, required) — When that snapshot was taken, `null` in the same no-snapshot case. So is `remote.full_schema_hash` — a bare SHA-256 as **64 lowercase hex characters**, with no `sha256:` prefix to strip. Treat all three as one signal: null means "this upstream has never answered yet", not "no tools". `remote.created_by_user_id` and `remote.created_at` are always present. Organization admins also receive `remote.custom_header_names`: the names, never the values, of the static headers sent upstream.
* `remote.oauth` (object, optional) — Present when the upstream uses OAuth: `client_source` (`"dcr"` or `"preregistered"`), `issuer`, sometimes `client_id`, and `scopes` — the scope list the authorize redirect will request, discovered from the upstream at registration. `scopes: []` means the upstream published none: authorization is still valid, no `scope` parameter is sent, and the server applies its default grant.

#### Device entry only

Device entries are listed while Tie is on for your organization: your own tied
machines, the ones shared with the organization, and the machines of the
agents you manage (your own agents, or every agent if you hold
`agents:manage`). Listing is not use. `POST /mcp` serves a machine's tools as
`device__<slug>__<tool>` only to its owner, or to everyone once it is shared
with the organization. So a private machine of an agent you manage is listed
here, but its tools are the agent's alone. A revoked machine leaves both. A key
bound to an environment sees no device entries, because no environment can
name a machine. If CoreSpeed cannot read the devices, the index
answers without them rather than failing, so a missing entry is not proof a
machine was revoked. Tying a machine and choosing what it shares is
[`cs tie`](/docs/cli/tie).

There is no top-level `tool_count`, `auth`, `credential_source`, or
`connection_methods` on a device entry, and `GET /connectors/device__<slug>`
is not a route (it answers `404 endpoint_not_found`): the entry is the whole of
a device on this API.

* `device.device_id` (string, required) — The device's id (`dev_…`), as `cs tie status` prints it.
* `device.slug` (string, required) — The device's segment in tool names, unique in the organization: lowercase letters, digits and hyphens. The entry's `id` is `device__<slug>`.
* `device.platform` (string, required) — The operating system the machine registered with: `"macos"`, `"windows"`, `"linux"`, or another platform name.
* `device.state` (string, required) — Whether the machine can take a call now. `"connected"`: linked. `"reconnecting"`: the link dropped seconds ago without a goodbye, and a call waits for it to come back. `"offline"`: not linked. `"paused"`: its owner paused it, and every call is refused with `paused_by_owner`. `"dormant"`: offline for 7 days, or never linked — a new MCP session's `tools/list` leaves its tools out until it links again. Read presence here, never from `status`, and treat a state you do not recognize as offline.
* `device.visibility` ("private" | "org", required) — `"private"`: only its owner gets its tools. When an agent owns it, the agent's managers also see it listed here and manage it, but do not get its tools. `"org"`: shared with every member and agent of the organization.
* `device.owner_user_id` (string, required) — The principal whose credential tied it: a member (`user_…`), or an agent (`agent_…`) when an agent key ran `cs tie`.
* `device.is_owner` (boolean, required) — Whether that principal is the caller.
* `device.last_seen_at` (string | null, required) — When CoreSpeed last heard from the machine, `null` before its first link.
* `device.tool_count` (number, required) — Tools in the list the machine published last; `0` before it publishes one.

#### Account fields

* `accounts[].id` (string, required) — Stable account id.
* `accounts[].alias` (string, required) — Human-chosen label. Use the alias to select an account instead of putting a provider account id or token into a prompt.
* `accounts[].identity` (string, required) — How the provider identifies the account (handle, workspace, mailbox).
* `accounts[].scope` (string, required) — `"private"` (owned by one member) or `"shared"` (available inside the organization).
* `accounts[].status` (string, required) — `"connected"` or `"needs_reauth"`. A `needs_reauth` account still contributes tools, and those calls fail until it is reauthorized.
* `accounts[].can_remove` (boolean, required) — Whether this caller may remove the account. Private accounts: the owning member only. Shared accounts: the creator or an organization admin.
* `accounts[].access` (string, required) — How this caller reaches the account: `"owner"` (your own), `"org"` (a shared account), or `"granted"` (someone else's account shared with you or with the whole organization).
* `accounts[].can_share` (boolean, required) — Whether this caller may share the account with a member, an agent, or the organization. The same rule as `can_remove`, and always `false` on connectors whose personal accounts are private only (Slack, and connectors with an organization integration).
* `accounts[].auth_kind` (string, required) — `"oauth2"` or `"api_key"`: the connect door that created the account. Reconnect through the same door.
* `accounts[].key_verified` (boolean, optional) — Key accounts only. `false` means the key was stored without a vendor check.
* `accounts[].organization_integration` (true, optional) — Present on the shared account that is the connector's organization integration — the vendor's app acting as itself.

#### The grant shapes

* `auth.scopes` (string\[], required) — Required request-time grants, described by `scope_descriptions` (same keys).
* `auth.optional_scopes` (string\[], optional) — Provider-optional grants, described by `optional_scope_descriptions`. An unavailable optional grant does not block the connection.
* `auth.user_scopes` (string\[], optional) — Grants issued to a separate authorizing-user token — tools that act with the connecting user's own identity — described by `user_scope_descriptions`.
* `auth.access_summary` (string, optional) — Used when the provider fixes access in its app registration instead of accepting meaningful request scopes. Then `scopes` is empty. Mutually exclusive with request scopes.
* `auth.organization_integration_scopes` (string\[], optional) — Extra grants the organization integration's install requests on top of `scopes`, described by `organization_integration_scope_descriptions`. Present with `auth.organization_integration: true`, which also means a member's own account connects as private only.

**curl**

```bash
curl https://api.corespeed.io/connectors \
  -H "Authorization: Bearer $CORESPEED_API_KEY"
```

**TypeScript**

```ts
const res = await fetch("https://api.corespeed.io/connectors", {
  headers: { Authorization: `Bearer ${process.env.CORESPEED_API_KEY}` },
});

const { connectors } = await res.json();
for (const connector of connectors) {
  console.log(connector.id, connector.status, connector.accounts.length);
}
```

**Python**

```python
import os
import httpx

res = httpx.get(
    "https://api.corespeed.io/connectors",
    headers={"Authorization": f"Bearer {os.environ['CORESPEED_API_KEY']}"},
)

for connector in res.json()["connectors"]:
    print(connector["id"], connector["status"], len(connector["accounts"]))
```

**Go**

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.corespeed.io/connectors", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CORESPEED_API_KEY"))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var out struct {
		Connectors []struct {
			ID       string `json:"id"`
			Status   string `json:"status"`
			Accounts []struct {
				Alias string `json:"alias"`
			} `json:"accounts"`
		} `json:"connectors"`
	}
	json.NewDecoder(res.Body).Decode(&out)
	for _, c := range out.Connectors {
		fmt.Println(c.ID, c.Status, len(c.Accounts))
	}
}
```

**200 OAuth** — OAuth connector entry

```json
{
  "connectors": [
    {
      "id": "notion",
      "connection_methods": [
        {
          "id": "corespeed-app",
          "label": "CoreSpeed",
          "kind": "oauth",
          "source": "platform",
          "status": "ready",
          "description": "CoreSpeed manages the app; you approve its access.",
          "schema": { "type": "object", "properties": {}, "required": [] }
        }
      ],
      "category": "docs",
      "name": "Notion",
      "description": "Search, read, create, and query pages and databases in a connected Notion workspace.",
      "auth": {
        "credential_setup_urls": {
          "oauth_app": "https://developers.notion.com/guides/get-started/public-connections",
          "api_key": "https://developers.notion.com/guides/get-started/authorization"
        },
        "type": "oauth2",
        "scopes": [],
        "access_summary": "Read & write pages and databases shared with this Notion connection, including their accessible children.",
        "oauth_configured": true
      },
      "url": "https://api.corespeed.io/connectors/notion",
      "mcp_url": "https://api.corespeed.io/mcp",
      "icon_url": "https://api.corespeed.io/icons/notion.svg",
      "status": "connected",
      "accounts": [
        {
          "id": "0199b7d2-…",
          "alias": "product-wiki",
          "identity": "Acme workspace",
          "scope": "private",
          "status": "connected",
          "can_remove": true,
          "access": "owner",
          "can_share": true,
          "auth_kind": "oauth2"
        }
      ],
      "tool_count": 5,
      "credential_source": {
        "active": "platform",
        "org_clients_enabled": false
      }
    }
  ]
}
```

**200 remote MCP** — Org-registered remote MCP entry

`kind: "remote-mcp"` — no `auth`, `icon_url: null`, `accounts` empty, and a
`remote` block instead. Remote entries share the one `connectors` array with
OAuth entries; the top-level shape never changes.

```json
{
  "connectors": [
    {
      "id": "acme-internal",
      "kind": "remote-mcp",
      "name": "Acme internal tools",
      "description": "Org-registered remote MCP server at mcp.acme.internal (upstream self-described, unverified).",
      "url": "https://api.corespeed.io/connectors/org/acme-internal",
      "mcp_url": "https://api.corespeed.io/mcp",
      "icon_url": null,
      "status": "connected",
      "accounts": [],
      "remote": {
        "upstream_url": "https://mcp.acme.internal/mcp",
        "auth_mode": "oauth",
        "registration_id": "0199a1c3-…",
        "oauth": {
          "client_source": "dcr",
          "issuer": "https://auth.acme.internal",
          "scopes": ["mcp.read", "mcp.write"]
        },
        "tool_count": 12,
        "snapshot_fetched_at": "2026-07-24T09:12:04Z",
        "full_schema_hash": "9f2c1ab4e7d05836bc41f0a29d7e655813cf84a0b2e7d691c5038af74be2019d",
        "created_by_user_id": "user_01J…",
        "created_at": "2026-07-02T18:40:11Z"
      }
    }
  ]
}
```

**200 device** — Tied machine entry

`kind: "device"` — no `auth`, no `category`, `icon_url: null`, `accounts`
empty, and a `device` block instead. `status` stays `"connected"` while the
machine is offline; `device.state` says where it is.

```json
{
  "connectors": [
    {
      "id": "device__hao-s-macbook",
      "kind": "device",
      "name": "Hao's MacBook",
      "description": "A machine tied by a member of this organization. Its tools run on that machine.",
      "url": "https://api.corespeed.io/devices",
      "mcp_url": "https://api.corespeed.io/mcp",
      "icon_url": null,
      "status": "connected",
      "accounts": [],
      "device": {
        "device_id": "dev_0199a8c4e1f27b3c9d4e5f60718293a4",
        "slug": "hao-s-macbook",
        "platform": "macos",
        "state": "connected",
        "visibility": "private",
        "owner_user_id": "user_01J…",
        "is_owner": true,
        "last_seen_at": "2026-10-14T16:15:29.820Z",
        "tool_count": 9
      }
    }
  ]
}
```

**200 provider-fixed** — scopes empty, access\_summary instead

No meaningful request scopes: `scopes` is empty and `access_summary` describes
the access the provider's app registration grants.

```json
{
  "auth": {
    "type": "oauth2",
    "scopes": [],
    "access_summary": "Read and write the repositories you choose when installing the app."
  }
}
```

**200 optional + user** — Non-blocking and act-as-you grants

```json
{
  "auth": {
    "type": "oauth2",
    "scopes": ["chat:write", "users:read"],
    "scope_descriptions": {
      "chat:write": "send messages as the CoreSpeed app",
      "users:read": "see workspace members"
    },
    "optional_scopes": ["canvases:write"],
    "optional_scope_descriptions": {
      "canvases:write": "create and edit canvases"
    },
    "user_scopes": ["chat:write", "im:write"],
    "user_scope_descriptions": {
      "chat:write": "send messages as you",
      "im:write": "start direct messages as you"
    }
  }
}
```