Start a connection sign-in from your own button
This page needs your own installation with an Embed license.
POST/
Starts a customer’s sign-in to a service from your own Connect button. No GoRunner screen.
Needs a partner key: Authorization: Bearer gre_…, from Platform › Partner keys. Call it from your server only.
Parameters
Path
accountIdRequired · stringYour ID for the customer’s account: 1 to 255 bytes of UTF-8, case-sensitive. URL-encode it: it may contain
/.
Body
pieceIdOptional · stringNeeded for a new connection: an app that signs in, such as
gmail. A trigger app is made under its action app. Optional withconnectionId.returnUrlRequired · URLWhere the browser comes back to: https (http only for localhost), on exactly one of the installation’s allowed websites or this account’s own; a wildcard entry allows none. No user name, password or
#fragment. Up to 2,048 characters.nameOptional · stringUp to 255 characters. Left blank: “Service · account” once the account is known.
connectionIdOptional · UUIDSign a customer’s connection in again instead. Not one you manage: replace those with Create or replace a managed connection.
workspaceIdOptional · stringThe workspace, by your ID. Left out: the default workspace.
What are the rarely used parameters?
configOptional · objectYour own app instead of the installation’s:
clientId,clientSecret,scope.modeOptional · stringAlways
redirect.
The sign-in
authorizeUrl is the service’s own consent page, with the installation’s app unless config names yours. It works once, for 10 minutes, and only for this key, account and app.
When the service sends the browser back, the connection is made as one made in the embed (managedBy: customer), audited as your key, and your page hears connection.created. Then the browser goes to returnUrl with status=connected&connectionId=…&attemptId=…, or status=error&reason=…. Never a token in that address.
Read the outcome with Check how a connection sign-in ended. Use connectionId in a workflow step.
Retries
It takes no Idempotency-Key: a stored answer would hand back a link already used. A retry starts a new sign-in; the unused one runs out.
Responses
- 200OK
Started: send the customer’s browser to
authorizeUrl. A field isn’t valid as written.
details.fieldnames it.This account may not use the app.
connectionIdnames a connection you manage.- 404
NOT_FOUNDNo such account, workspace or connection.
The account is deleted. Nothing starts in it.
returnUrlisn’t on an allowed website (details.origin).There is no app to sign in with for this service.
Something failed on the installation’s side. Try again.
- 503
UNAVAILABLESign-ins aren’t available on this server now, or the allowed websites couldn’t be read. Nothing started.
Any call can also answer: 401 UNAUTHORIZED · 403 EMBED_NOT_LICENSED · 403 IP_NOT_ALLOWED · 413 PAYLOAD_TOO_LARGE · 415 INVALID_CONTENT_TYPE · 429 RATE_LIMITED · 503 BUSY. See Errors.
Request
GORUNNER="https://automations.example.com"
GORUNNER_EMBED_KEY="YOUR_PARTNER_KEY" # gre_…
ACCOUNT_ID="acct-bakery"
URL="$GORUNNER/api/embed/v1/accounts/$ACCOUNT_ID/"
URL="${URL}connections/oauth2/start"
curl -X POST "$URL" \
-H "Authorization: Bearer $GORUNNER_EMBED_KEY" \
-H "Content-Type: application/json" \
-d '{
"pieceId": "gmail",
"returnUrl": "https://app.bakery.example/integrations/done?user=ada-123"
}'const GORUNNER = "https://automations.example.com";
const KEY = process.env.GORUNNER_EMBED_KEY; // gre_…
const ACCOUNT_ID = encodeURIComponent("acct-bakery");
const res = await fetch(
`${GORUNNER}/api/embed/v1/accounts/` +
`${ACCOUNT_ID}/connections/oauth2/start`,
{
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
pieceId: "gmail",
returnUrl: "https://app.bakery.example/integrations/done?user=ada-123",
}),
},
);
const { data } = await res.json();import os, requests
from urllib.parse import quote
GORUNNER = "https://automations.example.com"
KEY = os.environ["GORUNNER_EMBED_KEY"] # gre_…
ACCOUNT_ID = quote("acct-bakery", safe="")
res = requests.post(
f"{GORUNNER}/api/embed/v1/accounts/"
f"{ACCOUNT_ID}/connections/oauth2/start",
headers={"Authorization": f"Bearer {KEY}"},
json={
"pieceId": "gmail",
"returnUrl": "https://app.bakery.example/integrations/done?user=ada-123",
},
)
data = res.json()["data"]Response · 200 OK
{
"data": {
"attemptId": "Zq3XbN0iJm7dQe8TfR2wLg",
"authorizeUrl": "https://accounts.google.com/o/oauth2/v2/auth?…&state=…",
"expiresAt": "2026-10-25T09:10:00Z"
}
}Every field
Body
pieceIdOptional · stringRequired for a new connection: an app that signs in (declares OAuth2, or the generic
oauth2). A trigger’s app is made under its action app (gmailtrigger→gmail). WithconnectionId, optional and must be that connection’s.nameOptional · stringA new connection’s name. Blank: "{Service} · {account}" once the account is known. Not with
connectionId.connectionIdOptional · UUIDSign this existing connection (in the caller’s workspace) in again; it keeps its name and its app. Not a partner-managed one.
returnUrlRequired · URLWhere the browser comes back to: an absolute https address (http only for localhost), no user info or
#fragment, whose origin is exactly an allowed website (or, with an API token onPOST /api/connections/oauth2/start, this installation’s own app address). Its own query is kept;status,connectionId/reasonandattemptIdare set.modeOptional · stringOne of
redirect. Defaultredirect.configOptional · objectYour own app instead of the installation’s:
clientId,clientSecret,scope(and the generic app’sauthUrl,tokenUrl), and an app’sbaseUrl. Sealed into the attempt. Not withconnectionId.workspaceIdOptional · stringThe workspace the connection goes in (the partner’s id;
defaultnames the default one). Omitted = the account’s default workspace; the empty value is refused.
Response · 200 OK
dataobjectattemptIdstringNames this sign-in: read it with the same credential, and it comes back on
returnUrl.authorizeUrlURLThe provider’s consent page, complete: its
stateis single-use and bound to this attempt. Send the person who should sign in there, and nobody else.expiresAttimeWhen the link stops working (10 minutes after the start).