Sign a person in for an AI app
This page needs your own installation with an Embed license.
POST/
Says who the person is, once they signed in to your product and pressed Continue.
Needs a partner key: Authorization: Bearer gre_…, from Platform › Partner keys. Call it from your server only.
Parameters
Path
requestIdRequired · stringThe request’s ID: the
gr_requestparameter on the address the person arrived at.
Body
userRequired · objectThe person: your
id,nameand, if you like,email.accountRequired · objectThe account: your
idandname. It must be the request’s.workspaceOptional · object or nullA workspace by your
id. Left out: the default workspace.roleOptional · stringWithout
provision: if sent, it must be their role there.provisionOptional · booleanfalse(the default): they must already be a member.true: made as a login code makes them. A membership it made stays even if the browser never arrives.
What are the rarely used parameters?
turnOffOptional · array of stringsSwitches to turn off for this connection.
After it
The installation shows its own consent page, where the person chooses what the app may do. Once per request; a retry after a success answers OAUTH_REQUEST_DONE.
The connection is checked on every request: it pauses while a switch it needs is off, and ends when the person leaves, drops below this role or is deactivated, when the account is deleted, or when this key is revoked. See Let customers sign in AI apps.
Responses
- 201Created
Send the browser to
continueUrlwith a 303. It works once, for 60 seconds; never log it. A field isn’t valid, as for a login code.
details.fieldnames it.With
provision: true, a new workspace would pass the license’s workspace limit.- 403
FEATURE_OFFA switch AI app sign-in needs is off, or is in
turnOff.details.switch:tokens,mcp,mcpSignIn, ormcpBuildfor a build address. It carriesdetails.continueUrl: send the browser there so the AI app is told. No such request, or it expired. No
continueUrl.account.idisn’t the request’s (details.expected). The request isn’t used up.It was already answered.
- 409
NOT_A_MEMBERWithout
provision, the person isn’t a member of the workspace. It carriesdetails.continueUrl: send the browser there so the AI app is told. - 409
ROLE_DIFFERSWithout
provision,roleisn’t their role there. It carriesdetails.continueUrl: send the browser there so the AI app is told. The account is deleted. It carries
details.continueUrl: send the browser there so the AI app is told.Something failed on the installation’s side. Try again.
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_…
REQUEST_ID="q7Kd2mXr9TfL0aZc3vBn8wYe" # gr_request from the address
URL="$GORUNNER/api/embed/v1/oauth-requests/"
URL="${URL}$REQUEST_ID/complete"
curl -X POST "$URL" \
-H "Authorization: Bearer $GORUNNER_EMBED_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": {
"id": "ada-123",
"name": "Ada Lovelace"
},
"account": {
"id": "acct-bakery",
"name": "Bakery Ltd"
}
}'const GORUNNER = "https://automations.example.com";
const KEY = process.env.GORUNNER_EMBED_KEY; // gre_…
const REQUEST_ID = encodeURIComponent("q7Kd2mXr9TfL0aZc3vBn8wYe");
const res = await fetch(
`${GORUNNER}/api/embed/v1/oauth-requests/` +
`${REQUEST_ID}/complete`,
{
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user: { id: "ada-123", name: "Ada Lovelace" },
account: {
id: "acct-bakery",
name: "Bakery Ltd",
},
}),
},
);
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_…
REQUEST_ID = quote("q7Kd2mXr9TfL0aZc3vBn8wYe", safe="")
res = requests.post(
f"{GORUNNER}/api/embed/v1/oauth-requests/"
f"{REQUEST_ID}/complete",
headers={"Authorization": f"Bearer {KEY}"},
json={
"user": {
"id": "ada-123",
"name": "Ada Lovelace",
},
"account": {
"id": "acct-bakery",
"name": "Bakery Ltd",
},
},
)
data = res.json()["data"]Response · 201 Created, shortened
{
"data": {
"continueUrl": "https://automations.example.com/api/embed/oauth/return?request=q7Kd2mXr9TfL0aZc3vBn8wYe&ticket=…",
"expiresAt": "2026-10-25T09:01:00Z",
"expiresIn": 60,
"account": {
"id": "acct-bakery",
"status": "active",
"created": false
},
"workspace": {
"id": null,
"default": true,
"created": false
},
"user": {
"id": "ada-123",
"created": false
},
"role": "editor"
}
}Every field
Body
userRequired · objectidRequired · stringnameRequired · stringemailOptional · string or nullaccountRequired · objectidRequired · stringnameRequired · stringworkspaceOptional · object or nullidOptional · stringnameOptional · string or nullroleOptional · stringWithout provisioning, optional, and when given it must be the person’s role there. With
provision: true, required.One of
viewer,operator,editor.turnOffOptional · array of stringsprovisionOptional · booleanfalse: the person must already be a member of the workspace. true: made as a login code makes them.
Default
false.
Response · 201 Created
dataobjectcontinueUrlURLexpiresAttimeexpiresInintegeraccountobjectidstringstatusstringOne of
active,suspended.createdbooleanworkspaceobjectidstring or nulldefaultbooleancreatedbooleanuserobjectidstringcreatedbooleanrolestringOne of
viewer,operator,editor.