# Finish a connect

POST

/v1/connections/finish

```sh
curl --request POST \
  --url https://example.com/v1/connections/finish \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "state": "example", "code": "example", "installationId": "example", "setupAction": "example", "error": "example", "errorDescription": "example" }'
```

Takes what the app sent back to the callback page (`state` and `code`, with `installationId` and `setupAction` from a GitHub App; or `error`). Only whoever started the request may finish it. Exchanges the code, learns the account, compares what was granted with what was asked and stores the connection; a run that waits goes on. A company connect that doesn’t name `reconnects` lands on the company’s connection of the account signed in to, when there is one, so one account never has two. `reconnected` says whether it landed on a connection that was already there, which keeps its name. A new connection whose name another connection took meanwhile is named the first free `<name> 2`, `<name> 3` and so on, and `labelTaken` gives the name it was to get. A person who declined, or who granted less than asked, gets `kind: 'declined'` and the request’s `missing` permissions. A GitHub App’s attaches an installation only if the person’s GitHub account reaches it: the one GitHub sent back, the one they picked at the start, or the only one they reach. When they reach several and picked none, it answers `kind: 'pick-installation'`: the request stays `pending` with its `installations` to pick from (start it again with `installationId`, or `newInstallation`).

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.5.0/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the workspace is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5). A request whose key store can’t be read before it runs is answered `503 idempotency-store-unavailable` with `Retry-After`: nothing ran, so retry with the same key (from 0.3.0).

## Request Bodyrequired

application/json

What the app sent back to the callback page, each as a string. Only whoever started the request may finish it.

object

**state**

required

The one the app sent back.

string

\>= 1 characters <= 2048 characters

**code**

string

\>= 1 characters <= 2048 characters

**installationId**

string

\>= 1 characters <= 2048 characters

**setupAction**

string

\>= 1 characters <= 2048 characters

**error**

string

\>= 1 characters <= 2048 characters

**errorDescription**

string

\>= 1 characters <= 2048 characters

##### Examplegenerated

```json
{
  "state": "example",
  "code": "example",
  "installationId": "example",
  "setupAction": "example",
  "error": "example",
  "errorDescription": "example"
}
```

## Responses

### 200

The outcome.

application/json

object

**kind**

required

`declined`: the person declined, or granted less than asked (`request.missing`). `pick-installation`: a GitHub App’s person reaches several installations and picked none; the request stays `pending` with its `installations`.

string

Allowed values: connected declined pick-installation

**request**

required

A connect: the owner goes to `connectUrl`, consents at the app, and comes back. A run that needs a connection it lacks waits on one.

object

**requestId**

required

string format: uuid

**envName**

required

string

**appId**

required

string format: uuid

**app**

required

The app’s entry id.

string

**owner**

required

Who owns a connection: one member (`kind: member`, with their `userId`), or the company (`kind: company`, the workspace’s own account).

object

**kind**

required

string

Allowed values: member company

**userId**

The member. Present when `kind` is `member`.

string

**permissions**

required

The permissions asked for: the ones needed, and the ones already granted.

Array\<string>

**says**

required

What each asks for, in plain words.

Array\<string>

**reconnects**

The connection it connects again; the same account is required.

string format: uuid

**label**

The name asked for the connection it makes. A reconnect keeps the connection’s.

string

**runId**

The run that waits for it.

string

**name**

The tool’s name for the connection, when a run waits.

string

**startedBy**

Who started it last: `<kind>:<id>`. Only they may finish it.

string

**state**

required

string

Allowed values: pending connected declined expired failed

**failure**

Why it failed: `account-mismatch`, `app-refused`, `store-write-failed`.

string

**missing**

The permissions the app granted less than asked.

Array\<string>

**installations**

A GitHub App’s, while it’s `pending`: the app’s installations the person’s GitHub account reaches, when the finish found several and none was picked. They pick one and start again with `installationId`, or install the app on another account with `newInstallation`.

Array\<object>

A GitHub App installation the person can reach.

object

**installationId**

required

string

**account**

required

The account it’s on: its login, or the enterprise’s slug.

object

**id**

required

The account’s stable id at the app.

string

**label**

required

What people see: an email address, a login.

string

**connectionId**

The connection it made.

string format: uuid

**connectUrl**

required

Where the owner starts.

string format: uri

**expiresAt**

required

string format: date-time

**createdAt**

required

string format: date-time

**createdBy**

required

string

**connection**

Present when `kind` is `connected`.

object

**connectionId**

required

string format: uuid

**envName**

required

string

**appId**

required

The registration it goes through.

string format: uuid

**app**

required

The app’s entry id.

string

**owner**

required

Who owns a connection: one member (`kind: member`, with their `userId`), or the company (`kind: company`, the workspace’s own account).

object

**kind**

required

string

Allowed values: member company

**userId**

The member. Present when `kind` is `member`.

string

**account**

required

Whose account a connection is at the app.

object

**id**

required

The account’s stable id at the app.

string

**label**

required

What people see: an email address, a login.

string

**label**

The name people know it by: the one asked for at the connect, else the account’s label. Absent on a connection made before connections had names; show `account.label` then.

string

**main**

Only ever `true`: the person’s main connection through its registration in its env, the one their agents use when no choice names another. A person’s first connection has none; their second connection makes the first their main. The company’s has none.

boolean

**permissions**

required

The entry’s permission keys the app granted.

Array\<string>

**state**

required

`working`: runs can use it. `needs-reconnecting`: the app no longer accepts it; connect again. `removing`: a disconnect is under way. `removed`: gone.

string

Allowed values: working needs-reconnecting removing removed

**stateReason**

Why it’s in its state: `invalid-grant` (the app withdrew the grant), `no-refresh-token` (the app gave no way to renew it), `token-missing` (its stored token is gone), `store-write-failed` (its token couldn’t be stored at the connect), `installation-missing` (the GitHub App was uninstalled from the account), `installation-permissions` (an owner took permissions from the installation), `suspended` (the installation was suspended), `sessions-revoked` (Google revoked the person’s sessions), `access-expired` (the app gave no refresh token, and its access token ends within a minute, or has ended). Other values may be added; `state` says what to do.

string

**installationId**

A GitHub App’s installation.

string

**accessEndsAt**

When its access token ends, for a connection the app gave no refresh token. From one minute before then, it needs reconnecting (`access-expired`), and reads say so too. Absent for one that renews.

string format: date-time

**connectedAt**

required

When its current grant was stored: the connect, or the last reconnect.

string format: date-time

**lastUsedAt**

When a run last used it, to the hour.

string format: date-time

**createdAt**

required

string format: date-time

**createdBy**

required

string

**updatedAt**

required

string format: date-time

**removedAt**

string format: date-time

**removedBy**

string

**removedReason**

string

Allowed values: disconnected revoked-at-app uninstalled registration-removed replaced

**withdrawn**

What a removal did at the app. `at-app`: access was withdrawn there. `app-failed`: the app refused the revoke; the token is gone here all the same. `kept-for-other-connection`: another live connection uses the same grant. `not-supported`: the app has no revoke that would touch only this connection.

string

Allowed values: at-app app-failed kept-for-other-connection not-supported

**reconnected**

Present when `kind` is `connected`. `true`: the finish landed on a connection that was already there, the one `reconnects` named or the owner’s connection of the same account, the company’s or the person’s, and that connection keeps its name. `false`: it made a new one.

boolean

**labelTaken**

Present when `kind` is `connected` and the name the new connection was to get (the one asked for, else the account’s label after the name rules) was already another connection’s by the finish. The connection is then named the first free `<name> 2`, `<name> 3` and so on, which `connection.label` holds.

string

**reviewerSeat**

On a server with approvals, for a member who connected: whether they have a reviewer seat, which approving a send from their own account needs.

boolean

**main**

Present when `kind` is `connected`, `reconnected` is `false`, and the person already had a connection through the registration in the env.

object

**state**

required

string

Allowed values: set kept missing

**connectionId**

With `set` and `kept`.

string format: uuid

##### Example

```json
{
  "kind": "connected",
  "request": {
    "owner": {
      "kind": "member"
    },
    "state": "pending"
  },
  "connection": {
    "owner": {
      "kind": "member"
    },
    "state": "working",
    "removedReason": "disconnected",
    "withdrawn": "at-app"
  },
  "main": {
    "state": "set"
  }
}
```

### 400

Malformed body, unknown field or no `state`; or neither `code` nor `error` (`bad-input`, `details.field`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

Someone other than whoever started the request (`connection-not-yours`); or a GitHub App’s person whose GitHub account doesn’t reach the installation, or any (`connection-installation-unreachable`, with `details.connectRequestId`: start that request again with `newInstallation` to install the app on their account).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

No request for that state (`connection-request-not-found`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

Idempotency-Key was reused with a different body; the request was finished already (`connection-request-used`); a reconnect signed in to another account (`connection-account-mismatch`); or the connection is still being disconnected: disconnect it again to finish, then connect (`connection-removing`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 410

The request expired (`connection-request-expired`), or the sign-in took longer than its state lives: start again (`connection-state-expired`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 502

The app wouldn’t give a token for the code: its client id, secret or redirect URL (`connection-app-refused`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 503

The connection couldn’t be stored just now (`connection-store-failed`), or the `Idempotency-Key` store couldn’t be read before the request ran (`idempotency-store-unavailable`, with `Retry-After`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [Error code `bad-input`](https://docs.kindgi.com/v0.5.0/reference/errors.md#bad-input)
- [Error code `connection-account-mismatch`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-account-mismatch)
- [Error code `connection-app-refused`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-app-refused)
- [Error code `connection-installation-unreachable`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-installation-unreachable)
- [Error code `connection-not-yours`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-not-yours)
- [Error code `connection-removing`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-removing)
- [Error code `connection-request-expired`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-request-expired)
- [Error code `connection-request-not-found`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-request-not-found)
- [Error code `connection-request-used`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-request-used)
- [Error code `connection-state-expired`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-state-expired)
- [Error code `connection-store-failed`](https://docs.kindgi.com/v0.5.0/reference/errors.md#connection-store-failed)
- [Error code `idempotency-store-unavailable`](https://docs.kindgi.com/v0.5.0/reference/errors.md#idempotency-store-unavailable)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.5.0/reference/errors.md#request-too-large)
- [Every error code](https://docs.kindgi.com/v0.5.0/reference/errors.md)
