> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinework.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Connection

> A connection holds your sign-in to one outside service, by OAuth or API key, so agents use its tools without the token.

A connection is a signed-in link from your workspace to one outside service in the integration catalog. Its id looks like `conn_` plus 16 characters. For the steps, see [Connect an outside service](/guides/add-a-connection).

An MCP server is a separate object, managed with the `mcp` CLI group. An MCP server can point at a connection with `--connection-id`. Signing in to an MCP server by OAuth also makes a connection. Its `integration.id` is `mcp-server:<id>` and its auth kind is `mcp_oauth`. See [Add an MCP server](/guides/add-an-mcp-server).

## States

| State | Meaning | Ends here |
| - | - | - |
| `pending` | A sign-in started and has not finished. | No |
| `active` | Signed in. Runs can use it. | No |
| `expired` | The sign-in no longer works. You can reauthorize it. | No |
| `revoked` | Access was withdrawn. You can reauthorize it. | No |

| From | To | Trigger |
| - | - | - |
| None | `pending` | Create for an `oauth` or `mcp_oauth` service. |
| None | `active` | Create for a `none` service, or an `api_key` service with valid key fields. |
| `pending` | `active` | The person approves access and the provider calls back. |
| `pending` | `revoked` | The callback's workspace, user or nonce does not match the pending sign-in. |
| `pending` | Deleted | `connection revoke`. A pending connection is deleted, not revoked. |
| `active` | `revoked` | `connection revoke` |
| `expired` or `revoked` | `pending` | `connection reauthorize` on an OAuth service, or create again with the same service and account label. |
| `expired` or `revoked` | `active` | Create again for a `none` service, or an `api_key` service with valid key fields. |

Create returns the existing connection when one with the same service, owner and account label is already `active`.

### Auth kinds

| `authKind` | Sign-in |
| - | - |
| `none` | No sign-in. Create makes it `active` at once. |
| `api_key` | You pass key fields. Services with an optional key also report `api_key`. |
| `oauth` | A person approves access at the provider's authorize URL. |
| `mcp_oauth` | OAuth to a remote MCP server. |

### Setup status

Create and reauthorize return a `connection_setup_result`. Its `status` is `connected` or `human_required`.

| `nextStep.kind` | What to do |
| - | - |
| `none` | Nothing. The connection is ready. |
| `oauth_redirect` | Open `nextStep.authorizeUrl` in a browser and approve access. |
| `api_key_fields` | Send the listed `fields` as `apiKeyFields`. |

## Fields

The connection, from `GET /api/v1/connections/:id`:

<ResponseField name="object" type="string" required>Always `connection`.</ResponseField>
<ResponseField name="id" type="string" required>Connection id, `conn_` prefix.</ResponseField>

<ResponseField name="integration" type="object" required>
  The service it signs in to.

  <Expandable title="integration">
    <ResponseField name="id" type="string" required>Catalog id, such as `linear`, or `mcp-server:<id>`.</ResponseField>
    <ResponseField name="label" type="string" required>Display name.</ResponseField>
    <ResponseField name="authKind" type="string" required>`none`, `api_key`, `oauth` or `mcp_oauth`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="owner" type="object" required>
  Who owns it.

  <Expandable title="owner">
    <ResponseField name="type" type="string" required>`company` when the workspace uses it, else `user`.</ResponseField>
    <ResponseField name="id" type="string" required>Workspace slug, or the user's id.</ResponseField>
    <ResponseField name="label" type="string" required>Workspace name, or the user's id.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="accountLabel" type="string" required>Your label for the account. May be empty.</ResponseField>
<ResponseField name="status" type="string" required>`pending`, `active`, `expired` or `revoked`.</ResponseField>
<ResponseField name="grantedScopes" type="string[]" required>Scopes the provider granted. May be empty.</ResponseField>
<ResponseField name="lastUsedAt" type="string">Last runtime use, ISO 8601. Null when never used.</ResponseField>
<ResponseField name="createdAt" type="string" required>ISO 8601.</ResponseField>
<ResponseField name="updatedAt" type="string" required>ISO 8601.</ResponseField>

<ResponseField name="auth" type="object" required>
  Auth facts. Never the secret values.

  <Expandable title="auth">
    <ResponseField name="kind" type="string" required>Same as `integration.authKind`.</ResponseField>
    <ResponseField name="expiresAt" type="string">Token expiry, ISO 8601. Null when the service has none.</ResponseField>
    <ResponseField name="fieldNames" type="string[]" required>Names of the credential fields held, such as `access_token`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="availableSurfaces" type="string[]" required>`mcp`, `native` or both.</ResponseField>
<ResponseField name="runtimeDelivery" type="object" required>How the token reaches a run. `local` is `device_injection` and `cloud` is `network_broker`.</ResponseField>

`GET /api/v1/connections` returns rows without `auth`, `availableSurfaces` and `runtimeDelivery`. The list envelope has `hasMore`, `nextCursor`, `returnedCount` and `totalCount`.

## CLI

| Command | Does |
| - | - |
| `connection create` | Start a connection. Needs `--integration-id` and `--owner-type`. Add `--api-key-fields` for a key service. |
| `connection list` | List connections, newest first. Filter with `--integration`, `--status` or `--q`. Sort with `--sort`. |
| `connection get <conn_…>` | Show one connection. |
| `connection reauthorize <conn_…>` | Restart sign-in for an `expired` or `revoked` connection. A person only. |
| `connection revoke <conn_…>` | Revoke a connection. |

```bash theme={null}
pinework connection create --integration-id linear --owner-type company --account-label "Acme Linear"
pinework connection list --status active --sort last_used_desc
pinework connection get conn_7Hq2Lm9Xc4Rt1Bz8
pinework connection reauthorize conn_7Hq2Lm9Xc4Rt1Bz8
```

## API

| Method | Path | Does |
| - | - | - |
| `GET` | `/api/v1/connections` | List connections you can see. Query: `integration`, `status`, `q`, `sort`, `limit`, `cursor`. |
| `POST` | `/api/v1/connections` | Start a connection. Body: `integrationId`, `ownerType`, `accountLabel`, `apiKeyFields`. A person only. |
| `GET` | `/api/v1/connections/:id` | Retrieve one connection. |
| `POST` | `/api/v1/connections/:id/revoke` | Revoke a connection. Returns `204`. |
| `POST` | `/api/v1/connections/:id/reauthorize` | Restart sign-in for an `expired` or `revoked` connection. A person only. |

You see connections you own and connections linked to the workspace. Others return `404`. `ownerType` is required, but the server does not use it. An agent can list, get and revoke.

## Errors

These codes arrive in an `error` object with `code`, `message` and `details`. That object has no `type` or `requestId`.

| Code | HTTP | When | What to do |
| - | - | - | - |
| `validation_error` | 400 | The call has no workspace context. | Sign in to a workspace, or pass one. |
| `integration_not_found` | 404 | `integrationId` is not in the catalog. | Check the catalog id. |
| `connection_not_found` | 404 | The id is not in your workspace, or you cannot see it. | Check the id with `connection list`. |
| `connection_owner_mismatch` | 403 | An agent called create or reauthorize, or the service needs an admin. | Ask a person to run it. |
| `connection_not_reauthorizable` | 409 | The connection is `pending` or `active`. | Reauthorize only an `expired` or `revoked` one. |
| `connection_not_revokable` | 400 | The revoke could not complete. | Read `message`, then retry. |
| `api_key_invalid` | 400 | A required key field is empty or fails the format check. | Pass every field the service lists. |
| `oauth_exchange_failed` | 400 | The OAuth setup, token exchange or MCP OAuth discovery failed. | Start the sign-in again. |
| `unsupported_auth_kind` | 400 | The service probe failed, or setup hit an internal error. | Retry. Then report it. |

See [API errors](/reference/api#errors) for the shared codes.

## Limits

* A pending sign-in lasts 10 minutes. After that, start it again.
* The OAuth callback takes 10 requests per minute from one IP address.
* A list page holds 1 to 100 connections, 25 by default.
* `q` on the list is at most 200 characters.

<CardGroup cols={2}>
  <Card title="Connect an outside service" href="/guides/add-a-connection">
    Sign in to a service from the dashboard or CLI.
  </Card>

  <Card title="Add an MCP server" href="/guides/add-an-mcp-server">
    Add a server by URL or command, the separate `mcp` object.
  </Card>

  <Card title="Secret" href="/reference/secret">
    Store keys that are not tied to a catalog service.
  </Card>

  <Card title="Run" href="/reference/run">
    The runs that use a connection's token.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.