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 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 tenant 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 tenant’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 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 tenant’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 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.
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.
Example
{ "kind": "connected", "request": { "owner": { "kind": "member" }, "state": "pending" }, "connection": { "owner": { "kind": "member" }, "state": "working", "removedReason": "disconnected", "withdrawn": "at-app" }}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