GoRunner docs
Search or askSearch⌘K

Create a connection from code

Find the fields an app needs, then create the connection, or send a person to sign in.

6 min · Needs curl and jq

You need

A token that may add connections
Custom, with connection:read and connection:write. The three ready-made choices have connection:read but not connection:write. Permissions
The app’s ID
Such as seatable or gmail, from List apps or the app’s page.

1.Read the app’s fields

Get an app says what a connection holds, in auth:

required
Whether the app uses a connection at all.
type
none, api_key, bearer_token, basic_auth, oauth2 or custom.
fields[]
Each one’s name, title, type and required. A password field is a secret.
oauth2
There only when the app signs in with a browser: its authorizationUrl, tokenUrl and scopes.

An app with auth.oauth2 signs in, whatever its type (Gmail’s is custom): go to step 5.

bash
curl -s "$GORUNNER/api/pieces/seatable" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" | jq .data.auth
Output
{
  "type": "custom",
  "required": true,
  "description": "Connect one SeaTable base. …",
  "fields": [
    { "name": "serverUrl", "title": "Server URL",
      "type": "string", "required": false, … },
    { "name": "apiToken", "title": "API Token",
      "type": "password", "required": true, … }
  ]
}

2.Create it

Send the app’s ID, a name, and a config whose keys are the field names.

A field the app marks required must have a value, or the answer is 400 naming it. The rest are stored as sent: a misspelled key of an optional field shows only when you test or use the connection, so test it next. To check the details first without saving anything, send the same body to Test sign-in details before saving.

You should see 201 and the connection, without its secrets.

bash
curl -s -X POST "$GORUNNER/api/connections" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pieceId": "seatable",
       "name": "SeaTable · Orders",
       "config": {"apiToken": "'"$SEATABLE_TOKEN"'"}}'
Output
{
  "data": {
    "id": "0054f06e-…",
    "pieceId": "seatable",
    "name": "SeaTable · Orders",
    "authType": "api_key",
    "status": "active",
    "account": "cloud.seatable.io",
    …
  }
}

3.Test it

Test a connection asks the service. The answer is 200 either way: read success, and error when it is false.

You should see "success": true. Here it is false, because the SeaTable token was made up.

bash
curl -s -X POST "$GORUNNER/api/connections/$CONNECTION_ID/test" \
  -H "Authorization: Bearer $GORUNNER_TOKEN"
Output
{
  "data": {
    "success": false,
    "status": "error",
    "cause": "refused",
    "error": "seatable: cloud.seatable.io rejected the API token (403) — …"
  }
}

4.Use it in a workflow

Put its ID in a step’s connectionId, beside pieceId. Never put secrets in config. The connection must be in the workflow’s workspace.

Workflow definition

json
{
  "name": "add_order",
  "type": "PIECE",
  "pieceId": "seatable",
  "actionId": "seatable",
  "connectionId": "0054f06e-…",
  "config": { … }
}

5.Start a sign-in

For an app that signs in, such as Gmail, start a sign-in. The connection is made only when the person finishes it.

returnUrl is where the browser comes back. It must be https, or http on localhost. Its origin must be the installation’s own dashboard, one of its allowed websites, added in Platform › Partner keys, or one of the embed account’s own.

returnUrl is where the browser comes back. On Cloud it must be on GoRunner’s own dashboard, https://app.gorunner.io. Cloud keeps no allowed websites you can add.

The answer has authorizeUrl: the service’s own sign-in page. It works once, for 10 minutes.

bash
curl -s -X POST "$GORUNNER/api/connections/oauth2/start" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pieceId": "gmail", "name": "Gmail · Ada",
       "returnUrl": "https://app.acme.example/done"}'
Output
{
  "data": {
    "attemptId": "lWbsqG2aUqJ8vtM5oPw09w",
    "authorizeUrl": "https://accounts.google.com/o/oauth2/v2/auth?…",
    "expiresAt": "2026-10-25T18:33:22Z"
  }
}

6.Send the person

Open authorizeUrl in the browser of the person who should sign in. Give it to no one else.

When they finish, the browser comes back to returnUrl with:

It worked
status=connected, connectionId and attemptId.
It didn’t
status=error, attemptId and a reason: denied, service_error, failed, cancelled or expired.

No token or secret is ever in that address. Your own query parameters are kept.

7.Read the outcome

Your server can also ask: pending, connected with connectionId, or failed with reason. Only the token that started it can read it, for an hour after the link runs out.

To stop it, call Stop a sign-in.

bash
curl -s "$GORUNNER/api/connections/oauth2/attempts/$ATTEMPT_ID" \
  -H "Authorization: Bearer $GORUNNER_TOKEN"
Output
{
  "data": {
    "attemptId": "lWbsqG2aUqJ8vtM5oPw09w",
    "status": "pending",
    "expiresAt": "2026-10-25T18:33:22Z"
  }
}
Can I bring tokens my own sign-in got?

Yes. In config, send accessToken, refreshToken, expiresAt (RFC 3339) and tokenType, with the app that issued them: your clientId and clientSecret, or "oauthApp": "installation". GoRunner refreshes them like a sign-in’s. A refresh token with no app to refresh it is refused.

What if a connection stops working?

Change its secrets with Rename or update a connection. Sign an app that signs in again with Start a sign-in for a connection, which needs the person in a browser.

Can an AI app create a connection?

No. A person makes it, in GoRunner or by this API. An app can build a workflow that needs one: it saves as a draft, and publishing names the step that needs a connection.

Checked against 12d85b3b