Skip to content
synthreo.ai

API Authentication - Synthreo Builder

Authentication guide for the Builder API - create an API key, exchange it for a Bearer access token with the OAuth 2.0 client credentials grant, and call the API in multi-tenant environments.

The Synthreo Builder API authenticates with OAuth 2.0. You create an API key in the Synthreo web app, then exchange that key for a short-lived JWT access token using the standard client_credentials grant. The access token is then sent in the Authorization header of every Builder API request.

Authenticating is a two-step process:

  1. Once: create an API key in the Synthreo web app and note its key id and secret.
  2. Per token: exchange the key at the token endpoint for an access token, then send that token as a Bearer credential.
ValueWhere it comes from
client_idapikey-{your key id} - for example, key id 42 gives apikey-42
client_secretThe key secret (sak_...), shown once when you create the key
Token endpointhttps://auth.synthreo.ai/connect/token
Access token lifetime15 minutes (no refresh token - request a new one when it expires)

API keys are managed per user, in the Synthreo web app:

  1. Sign in to Threo (https://threo.synthreo.ai).
  2. Open the account menu (your initials, top right) and choose Profile.
  3. Select the API Keys tab.
  4. Click Add key, enter a Label (for example, CI pipeline), optionally set an expiry date, and click Create key.
  5. Copy the secret now. It starts with sak_ and is shown only once - it can never be retrieved again. If you lose it, revoke the key and create a new one.

A key you create is scoped to your user and customer (tenant). The token it mints carries your own account’s roles and permissions.

Your OAuth client_id is your key id with an apikey- prefix:

client_id = apikey-{key id}

The key id is the numeric identifier of the key you created (for example, key id 42 gives client_id apikey-42). The client_id numeric part must match your key exactly - an incorrect id is rejected as invalid_client.

Your client_id is shown directly in the Synthreo web app: in the reveal dialog when you create a key (next to the secret), and in the Client ID column of the API Keys list.

Step 3 - Exchange the Key for an Access Token

Section titled “Step 3 - Exchange the Key for an Access Token”

Send a POST request to the token endpoint using the OAuth 2.0 client credentials grant. The request is form-encoded (application/x-www-form-urlencoded), not JSON.

POST https://auth.synthreo.ai/connect/token

ParameterRequiredDescription
grant_typeYesAlways client_credentials
client_idYesapikey-{your key id} (for example, apikey-42)
client_secretYesYour key secret (the sak_... value shown once at creation)
target_appYesThe application the token is for: builder, threo, or tenant. Use builder for the Builder API.
account_idNoScopes the token to a specific account when your identity can access more than one. Omit to use your default account.
Terminal window
curl -X POST 'https://auth.synthreo.ai/connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=apikey-42' \
--data-urlencode 'client_secret=sak_your_key_secret_here' \
--data-urlencode 'target_app=builder'
{
"access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
"token_type": "Bearer",
"expires_in": 900
}
FieldTypeDescription
access_tokenstringThe JWT access token to send on Builder API requests
token_typestringAlways Bearer
expires_inintegerToken lifetime in seconds (900 = 15 minutes)

There is no refresh_token - the client credentials grant is not refreshable. When a token expires, request a new one by repeating this exchange.

StatuserrorCommon cause
401 Unauthorizedinvalid_clientWrong, revoked, or expired key; or a client_id whose numeric id doesn’t match the key
400 Bad Requestinvalid_requestMissing target_app, an unknown target_app, or a scope parameter was sent
403 Forbiddenaccess_deniedThe key’s user has no access to the requested target_app / account_id

Include the access token in the Authorization header of every request, prefixed with Bearer:

Authorization: Bearer YOUR_ACCESS_TOKEN
Terminal window
curl -X POST 'https://builder-api.synthreo.ai/CognitiveDiagram/12345/Execute' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"Action":"Execute","UserSays":"[{\"userSays\":\"Hello!\"}]"}'

Access tokens expire after 15 minutes. There is no refresh token; renew by exchanging your API key again. A typical client caches the token and re-requests it shortly before expiry (or on a 401).

from datetime import datetime, timedelta, timezone
import requests
class SynthreoApiClient:
TOKEN_URL = "https://auth.synthreo.ai/connect/token"
def __init__(self, key_id, key_secret, target_app="builder", account_id=None):
self.client_id = f"apikey-{key_id}"
self.key_secret = key_secret
self.target_app = target_app
self.account_id = account_id
self.token = None
self.token_expiry = None
def _token_expired(self):
if not self.token or not self.token_expiry:
return True
# Refresh a minute early to absorb clock skew and request latency.
return datetime.now(timezone.utc) > (self.token_expiry - timedelta(minutes=1))
def ensure_valid_token(self):
if self._token_expired():
self.authenticate()
def authenticate(self):
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.key_secret,
"target_app": self.target_app,
}
if self.account_id is not None:
data["account_id"] = self.account_id
resp = requests.post(self.TOKEN_URL, data=data, timeout=30)
resp.raise_for_status()
body = resp.json()
self.token = body["access_token"]
self.token_expiry = datetime.now(timezone.utc) + timedelta(seconds=body["expires_in"])
def make_request(self, url, method="GET", headers=None, **kwargs):
self.ensure_valid_token()
req_headers = {"Authorization": f"Bearer {self.token}", **(headers or {})}
resp = requests.request(method, url, headers=req_headers, **kwargs)
if resp.status_code == 401:
# The token may have been revoked or expired early. Re-exchange the key
# ONCE and retry. authenticate() raises for an invalid/revoked key, so a
# genuinely bad credential fails fast instead of looping.
self.authenticate()
req_headers["Authorization"] = f"Bearer {self.token}"
resp = requests.request(method, url, headers=req_headers, **kwargs)
return resp

A single Synthreo identity can have access to more than one account, across multiple customers (tenants) and application instances. By default the token targets your primary account. To scope a token to a specific account, pass its account_id in the token request:

Terminal window
curl -X POST 'https://auth.synthreo.ai/connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=apikey-42' \
--data-urlencode 'client_secret=sak_your_key_secret_here' \
--data-urlencode 'target_app=builder' \
--data-urlencode 'account_id=1234'

The same API key can also target other Synthreo apps by changing target_app (threo, tenant), subject to your account’s access. If you’re unsure which account or app to use, contact your Synthreo representative.

  • Treat the key secret like a password. Store it in environment variables or a secret manager - never in client-side code or version control.
  • Never expose the secret in a browser or mobile app. The client credentials grant is for server-to-server use.
  • Do not log tokens or secrets.
  • Rotate keys by creating a new key and revoking the old one from the API Keys tab.
  • Revoke immediately if a key is ever exposed. Revocation takes effect at once; existing tokens still expire within 15 minutes.
  • Use HTTPS exclusively for all API communication.

Related pages: