Finish a connect
const url = 'https://example.com/v1/connections/finish';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"state":"example","code":"example","installationId":"example","setupAction":"example","error":"example","errorDescription":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”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
Section titled “Request Bodyrequired”What the app sent back to the callback page, each as a string. Only whoever started the request may finish it.
object
The one the app sent back.
Examplegenerated
{ "state": "example", "code": "example", "installationId": "example", "setupAction": "example", "error": "example", "errorDescription": "example"}Responses
Section titled “Responses”The outcome.
object
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.
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
The app’s entry id.
Who owns a connection: one member (kind: member, with their userId), or the company (kind: company, the workspace’s own account).
object
The member. Present when kind is member.
The permissions asked for: the ones needed, and the ones already granted.
What each asks for, in plain words.
The connection it connects again; the same account is required.
The name asked for the connection it makes. A reconnect keeps the connection’s.
The run that waits for it.
The tool’s name for the connection, when a run waits.
Who started it last: <kind>:<id>. Only they may finish it.
Why it failed: account-mismatch, app-refused, store-write-failed.
The permissions the app granted less than asked.
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.
A GitHub App installation the person can reach.
object
The account it’s on: its login, or the enterprise’s slug.
object
The account’s stable id at the app.
What people see: an email address, a login.
The connection it made.
Where the owner starts.
Present when kind is connected.
object
The registration it goes through.
The app’s entry id.
Who owns a connection: one member (kind: member, with their userId), or the company (kind: company, the workspace’s own account).
object
The member. Present when kind is member.
Whose account a connection is at the app.
object
The account’s stable id at the app.
What people see: an email address, a login.
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.
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.
The entry’s permission keys the app granted.
working: runs can use it. needs-reconnecting: the app no longer accepts it; connect again. removing: a disconnect is under way. removed: gone.
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.
A GitHub App’s installation.
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.
When its current grant was stored: the connect, or the last reconnect.
When a run last used it, to the hour.
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.
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.
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.
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.
Present when kind is connected, reconnected is false, and the person already had a connection through the registration in the env.
object
With set and kept.
Example
{ "kind": "connected", "request": { "owner": { "kind": "member" }, "state": "pending" }, "connection": { "owner": { "kind": "member" }, "state": "working", "removedReason": "disconnected", "withdrawn": "at-app" }, "main": { "state": "set" }}Malformed body, unknown field or no state; or neither code nor error (bad-input, details.field).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}Missing / malformed / expired / revoked bearer token.
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}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).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}No request for that state (connection-request-not-found).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}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).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}The request expired (connection-request-expired), or the sign-in took longer than its state lives: start again (connection-state-expired).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}The request body is over the API’s limit (request-too-large; details.maxBytes says how much).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}Server error (unmapped domain code or framework crash).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}The app wouldn’t give a token for the code: its client id, secret or redirect URL (connection-app-refused).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}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).
object
object
Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).
Optional, kind-specific.
object
Server-assigned request id; also echoed via X-Request-Id header.
Examplegenerated
{ "error": { "code": "example", "message": "example", "details": {}, "requestId": "example" }}See also
- Error code
bad-input - Error code
connection-account-mismatch - Error code
connection-app-refused - Error code
connection-installation-unreachable - Error code
connection-not-yours - Error code
connection-removing - Error code
connection-request-expired - Error code
connection-request-not-found - Error code
connection-request-used - Error code
connection-state-expired - Error code
connection-store-failed - Error code
idempotency-store-unavailable - Error code
request-too-large - Every error code