API Code Examples - Synthreo Builder
Code examples for the Builder API - ready-to-use curl, JavaScript, and Python samples for executing agents, managing users, and handling async job responses.
This guide provides comprehensive code examples for integrating with the Synthreo Builder API across multiple programming languages. Each example demonstrates the complete workflow: authentication, job execution, and status monitoring.
Prerequisites
Section titled “Prerequisites”Before running these examples, ensure you have:
- A Synthreo API key - its key id and secret. See Authentication to create one.
- A cognitive diagram ID for execution
- Node IDs for training operations (if applicable)
Environment Variables:
SYNTHREO_API_KEY_ID=42SYNTHREO_API_KEY_SECRET=sak_your_key_secret_hereSYNTHREO_DIAGRAM_ID=12345SYNTHREO_TRAINING_NODE_ID=your-training-node-uuidComplete Workflow Examples
Section titled “Complete Workflow Examples”Authentication
Section titled “Authentication”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'Synchronous Execution
Section titled “Synchronous Execution”curl -X POST 'https://builder-api.synthreo.ai/CognitiveDiagram/12345/Execute' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Origin: https://builder.synthreo.ai' \ -H 'Authorization: Bearer YOUR_JWT_TOKEN' \ -d '{ "Action": "Execute", "UserSays": "[{\"userSays\":\"Hello, how can you help me?\"}]" }'Asynchronous Job Execution
Section titled “Asynchronous Job Execution”# -i so you can see the status: 202 means poll, 200 means this IS the result.curl -i -X POST 'https://builder-api.synthreo.ai/CognitiveDiagram/12345/ExecuteAsJob' \ -H 'Content-Type: application/json' \ -H 'Accept: */*' \ -H 'Authorization: Bearer YOUR_JWT_TOKEN' \ -d '{ "Action": "Execute", "UserSays": "[{\"userSays\":\"start processing\"}]", "RobotSays": "", "CallerSource": 1 }'
# Only if the POST answered 202: poll the job id from its body (or its Location# header). A 200 above already returned the result and there is nothing to poll.curl -i -X GET 'https://builder-api.synthreo.ai/job/JOB_ID' \ -H 'Accept: */*' \ -H 'Authorization: Bearer YOUR_JWT_TOKEN'Agent Training
Section titled “Agent Training”curl -X PATCH 'https://builder-api.synthreo.ai/CognitiveDiagram/8139/TrainNode' \ -H 'Content-Type: application/json' \ -H 'Accept: */*' \ -H 'Authorization: Bearer YOUR_JWT_TOKEN' \ -d '{ "nodeId": "your-training-node-uuid", "repositoryNodeId": 59, "finishedFlag": false, "logText": "Training started by API user" }'
curl -X GET 'https://builder-api.synthreo.ai/CognitiveDiagram/8139' \ -H 'Accept: */*' \ -H 'Authorization: Bearer YOUR_JWT_TOKEN'Python
Section titled “Python”Complete Implementation
Section titled “Complete Implementation”import requestsimport jsonimport timeimport osfrom typing import Optional, Dict, Any
class SynthreoClient: def __init__(self, key_id: str, key_secret: str, target_app: str = "builder"): self.client_id = f"apikey-{key_id}" self.key_secret = key_secret self.target_app = target_app self.token = None # Access tokens live 15 minutes with no refresh token, so track expiry and # re-exchange the API key before it lapses (jobs can poll for far longer). self.token_expiry = 0.0 self.base_url = 'https://builder-api.synthreo.ai' self.auth_url = 'https://auth.synthreo.ai/connect/token'
def authenticate(self) -> str: """Exchange the API key for a JWT access token (client_credentials grant)""" data = { "grant_type": "client_credentials", "client_id": self.client_id, "client_secret": self.key_secret, "target_app": self.target_app, }
try: response = requests.post(self.auth_url, data=data) response.raise_for_status()
body = response.json() self.token = body['access_token'] # Renew a minute early to absorb clock skew and request latency. self.token_expiry = time.time() + body.get('expires_in', 900) - 60 print('Authentication successful') return self.token
except requests.exceptions.RequestException as e: print(f'Authentication failed: {e}') raise
def get_headers(self) -> Dict[str, str]: """Get headers with a valid authorization token, re-authenticating if expired.""" if not self.token or time.time() >= self.token_expiry: self.authenticate()
return { 'Authorization': f'Bearer {self.token}', 'Content-Type': 'application/json', 'Accept': '*/*' }
def execute_diagram(self, diagram_id: int, message: str, conversation_id: Optional[str] = None) -> Dict[str, Any]: """Execute cognitive diagram synchronously""" url = f'{self.base_url}/CognitiveDiagram/{diagram_id}/Execute'
user_says_data = {"userSays": message} if conversation_id: user_says_data["conversationId"] = conversation_id
# json.dumps, not an f-string: a message containing a quote, a backslash, or a # newline has to be escaped or the API receives invalid JSON. payload = { "Action": "Execute", "UserSays": json.dumps([user_says_data]) }
headers = self.get_headers() headers.update({ 'Accept': 'application/json', 'Origin': 'https://builder.synthreo.ai' })
try: response = requests.post(url, headers=headers, json=payload) response.raise_for_status() return response.json()
except requests.exceptions.RequestException as e: print(f'Diagram execution failed: {e}') raise
def execute_as_job(self, diagram_id: int, message: str = "start") -> Dict[str, Any]: """Start a job. Returns {'job_id', 'result'}: a quick diagram finishes inline and comes back as 'result' with no job id to poll.""" url = f'{self.base_url}/CognitiveDiagram/{diagram_id}/ExecuteAsJob'
payload = { "Action": "Execute", "UserSays": json.dumps([{"userSays": message}]), "RobotSays": "", "CallerSource": 1 }
headers = self.get_headers()
try: response = requests.post(url, headers=headers, json=payload) response.raise_for_status()
data = response.json()
# 202 carries a job to poll; 200 means it already finished and the body IS # the result, with no job id. Handle both or quick jobs crash here. if response.status_code == 202: job = data.get('job') if isinstance(data, dict) else None if not (job and job.get('id')): raise RuntimeError(f'202 response carried no job id: {data}') print(f'Job initiated with ID: {job["id"]}') return {'job_id': job['id'], 'result': None}
print('Job finished immediately; no polling needed') return {'job_id': None, 'result': data}
except requests.exceptions.RequestException as e: print(f'Job initiation failed: {e}') raise
def poll_job_status(self, job_id: str, interval: int = 30, timeout: int = 3600) -> Dict[str, Any]: """Poll job status until completion""" url = f'{self.base_url}/job/{job_id}'
start_time = time.time()
while True: if time.time() - start_time > timeout: raise TimeoutError(f'Job {job_id} timed out after {timeout} seconds')
try: # Fetch headers each iteration so the 15-minute token is refreshed # for polls that run longer than the token's lifetime. # Bound the request too: without a timeout a stalled connection can # hang past the caller's own deadline. remaining = timeout - (time.time() - start_time) response = requests.get( url, headers=self.get_headers(), timeout=(10, min(30, max(1, remaining))), )
if response.status_code == 202: print('Job is still running...') # Never sleep past the deadline. time.sleep(min(interval, max(0, timeout - (time.time() - start_time)))) continue
elif response.status_code == 200: # The body IS the result. An empty body means the id is unknown or # was already read - not success. if not response.content.strip(): raise RuntimeError( f'Job {job_id} is unknown, already read, or expired' ) print('Job completed!') return response.json()
elif response.status_code == 400: # The job ran and failed; the envelope carries the reason. payload = response.json() raise RuntimeError(payload.get('error') or 'Job failed')
else: raise requests.exceptions.HTTPError( f'Unexpected status code: {response.status_code}' )
except requests.exceptions.RequestException as e: print(f'Error polling job status: {e}') raise
def trigger_training(self, diagram_id: int, node_id: str, repository_node_id: int = 59) -> Dict[str, Any]: """Trigger agent training""" url = f'{self.base_url}/CognitiveDiagram/{diagram_id}/TrainNode'
payload = { "nodeId": node_id, "repositoryNodeId": repository_node_id, "finishedFlag": False, "logText": "Training started by API user" }
headers = self.get_headers()
try: response = requests.patch(url, headers=headers, json=payload) response.raise_for_status()
data = response.json() print(f'Training triggered for agent {diagram_id}') return data
except requests.exceptions.RequestException as e: print(f'Training trigger failed: {e}') raise
def monitor_training(self, diagram_id: int, interval: int = 60, timeout: int = 3600) -> Dict[str, Any]: """Monitor training progress""" url = f'{self.base_url}/CognitiveDiagram/{diagram_id}'
start_time = time.time()
while True: if time.time() - start_time > timeout: raise TimeoutError(f'Training timed out after {timeout} seconds')
try: # Refresh headers each iteration so a long-running monitor keeps a # valid 15-minute token. response = requests.get(url, headers=self.get_headers()) response.raise_for_status()
data = response.json() state_id = data.get('stateId')
if state_id == 6: print('Agent is training...') time.sleep(interval) elif state_id == 2: print('Training completed! Agent is ready.') return data else: print(f'Unexpected state ID: {state_id}') return data
except requests.exceptions.RequestException as e: print(f'Error monitoring training: {e}') raise
def parse_response(api_response: Dict[str, Any]) -> str: """Parse API response to extract meaningful output""" if api_response.get('outputData'): try: import json output_data = json.loads(api_response['outputData'])
if isinstance(output_data, list) and output_data: return str(output_data[0]) elif isinstance(output_data, dict): # Try common response field names for field in ['response', 'gpt_response', 'answer', 'result']: if field in output_data: return str(output_data[field]) return json.dumps(output_data, indent=2) else: return str(output_data) except (json.JSONDecodeError, KeyError): return api_response['outputData']
if api_response.get('errorData') and api_response['errorData'] != "[]": import json try: errors = json.loads(api_response['errorData']) return f"Error: {errors[0].get('message', 'Unknown error')}" except json.JSONDecodeError: return f"Error: {api_response['errorData']}"
return "No response generated"
if __name__ == '__main__': # Initialize client client = SynthreoClient( key_id=os.getenv('SYNTHREO_API_KEY_ID'), key_secret=os.getenv('SYNTHREO_API_KEY_SECRET') )
diagram_id = int(os.getenv('SYNTHREO_DIAGRAM_ID'))
try: # Example 1: Synchronous execution response = client.execute_diagram(diagram_id, "Hello, how are you?") ai_response = parse_response(response) print(f"AI Response: {ai_response}")
# Example 2: Asynchronous job execution started = client.execute_as_job(diagram_id, "start processing") job_result = ( started['result'] if started['job_id'] is None else client.poll_job_status(started['job_id']) ) print(f"Job Result: {parse_response(job_result)}")
# Example 3: Training workflow (optional) training_node_id = os.getenv('SYNTHREO_TRAINING_NODE_ID') if training_node_id: client.trigger_training(diagram_id, training_node_id) client.monitor_training(diagram_id)
except Exception as e: print(f"Error: {e}")Node.js
Section titled “Node.js”Complete Implementation
Section titled “Complete Implementation”const axios = require('axios');
class SynthreoClient { constructor(keyId, keySecret, targetApp = 'builder') { this.clientId = `apikey-${keyId}`; this.keySecret = keySecret; this.targetApp = targetApp; this.token = null; // Access tokens live 15 minutes with no refresh token, so track expiry. this.tokenExpiry = 0; this.baseUrl = 'https://builder-api.synthreo.ai'; this.authUrl = 'https://auth.synthreo.ai/connect/token'; }
async authenticate() { const body = new URLSearchParams({ grant_type: 'client_credentials', client_id: this.clientId, client_secret: this.keySecret, target_app: this.targetApp });
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
try { const response = await axios.post(this.authUrl, body, { headers }); this.token = response.data.access_token; // Renew a minute early to absorb clock skew and request latency. this.tokenExpiry = Date.now() + (response.data.expires_in * 1000) - 60000; console.log('Authentication successful'); return this.token; } catch (error) { console.error('Authentication failed:', error.response?.data || error.message); throw error; } }
// Re-exchange the API key when the cached token is missing or about to expire. async ensureToken() { if (!this.token || Date.now() >= this.tokenExpiry) { await this.authenticate(); } }
getHeaders() { return { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', 'Accept': '*/*' }; }
async executeDiagram(diagramId, message, conversationId = null) { const url = `${this.baseUrl}/CognitiveDiagram/${diagramId}/Execute`;
const userSaysData = { userSays: message }; if (conversationId) { userSaysData.conversationId = conversationId; }
const payload = { Action: "Execute", UserSays: JSON.stringify([userSaysData]) };
await this.ensureToken(); const headers = { ...this.getHeaders(), 'Accept': 'application/json', 'Origin': 'https://builder.synthreo.ai' };
try { const response = await axios.post(url, payload, { headers }); return response.data; } catch (error) { console.error('Diagram execution failed:', error.response?.data || error.message); throw error; } }
async executeAsJob(diagramId, message = "start") { const url = `${this.baseUrl}/CognitiveDiagram/${diagramId}/ExecuteAsJob`;
const payload = { Action: "Execute", UserSays: `[{"userSays":"${message}"}]`, RobotSays: "", CallerSource: 1 };
await this.ensureToken(); const headers = this.getHeaders();
try { const response = await axios.post(url, payload, { headers });
// 202 carries a job to poll; 200 means it already finished and the body IS // the result, with no job id. Handle both or quick jobs throw here. if (response.status === 202 && response.data?.job?.id) { const jobId = response.data.job.id; console.log(`Job initiated with ID: ${jobId}`); return { jobId, result: null }; }
console.log('Job finished immediately; no polling needed'); return { jobId: null, result: response.data }; } catch (error) { console.error('Job initiation failed:', error.response?.data || error.message); throw error; } }
async pollJobStatus(jobId, interval = 30000, timeout = 3600000) { const url = `${this.baseUrl}/job/${jobId}`; const startTime = Date.now();
return new Promise((resolve, reject) => { const poll = async () => { if (Date.now() - startTime > timeout) { reject(new Error(`Job ${jobId} timed out after ${timeout/1000} seconds`)); return; }
try { // Refresh the token each poll so long jobs keep a valid 15-minute token. await this.ensureToken(); // A failed job answers 400 with the reason in the body, so do not let // axios turn it into a bare transport error. const response = await axios.get(url, { headers: this.getHeaders(), validateStatus: (status) => [200, 202, 400].includes(status), });
if (response.status === 202) { console.log('Job is still running...'); setTimeout(poll, interval); } else if (response.status === 400) { reject(new Error(response.data?.error || 'Job failed')); } else if (response.status === 200) { // An empty body means the id is unknown or was already read. if (response.data === '' || response.data === null) { reject(new Error(`Job ${jobId} is unknown, already read, or expired`)); return; } console.log('Job completed!'); resolve(response.data); } else { reject(new Error(`Unexpected status code: ${response.status}`)); } } catch (error) { console.error('Error polling job status:', error.response?.data || error.message); reject(error); } };
poll(); }); }
async triggerTraining(diagramId, nodeId, repositoryNodeId = 59) { const url = `${this.baseUrl}/CognitiveDiagram/${diagramId}/TrainNode`;
const payload = { nodeId: nodeId, repositoryNodeId: repositoryNodeId, finishedFlag: false, logText: "Training started by API user" };
await this.ensureToken(); const headers = this.getHeaders();
try { const response = await axios.patch(url, payload, { headers }); console.log(`Training triggered for agent ${diagramId}`); return response.data; } catch (error) { console.error('Training trigger failed:', error.response?.data || error.message); throw error; } }
async monitorTraining(diagramId, interval = 60000, timeout = 3600000) { const url = `${this.baseUrl}/CognitiveDiagram/${diagramId}`; const startTime = Date.now();
return new Promise((resolve, reject) => { const monitor = async () => { if (Date.now() - startTime > timeout) { reject(new Error(`Training timed out after ${timeout/1000} seconds`)); return; }
try { // Refresh the token each cycle so long monitors keep a valid token. await this.ensureToken(); const response = await axios.get(url, { headers: this.getHeaders() }); const stateId = response.data.stateId;
if (stateId === 6) { console.log('Agent is training...'); setTimeout(monitor, interval); } else if (stateId === 2) { console.log('Training completed! Agent is ready.'); resolve(response.data); } else { console.log(`Unexpected state ID: ${stateId}`); resolve(response.data); } } catch (error) { console.error('Error monitoring training:', error.response?.data || error.message); reject(error); } };
monitor(); }); }}
function parseResponse(apiResponse) { if (apiResponse.outputData) { try { const outputData = JSON.parse(apiResponse.outputData);
if (Array.isArray(outputData) && outputData.length > 0) { return String(outputData[0]); } else if (typeof outputData === 'object' && outputData !== null) { // Try common response field names const commonFields = ['response', 'gpt_response', 'answer', 'result']; for (const field of commonFields) { if (outputData[field]) { return String(outputData[field]); } } return JSON.stringify(outputData, null, 2); } else { return String(outputData); } } catch (e) { return apiResponse.outputData; } }
if (apiResponse.errorData && apiResponse.errorData !== "[]") { try { const errors = JSON.parse(apiResponse.errorData); return `Error: ${errors[0]?.message || 'Unknown error'}`; } catch (e) { return `Error: ${apiResponse.errorData}`; } }
return "No response generated";}
// Usage Exampleasync function main() { const client = new SynthreoClient( process.env.SYNTHREO_API_KEY_ID, process.env.SYNTHREO_API_KEY_SECRET );
const diagramId = parseInt(process.env.SYNTHREO_DIAGRAM_ID);
try { // Authenticate await client.authenticate();
// Example 1: Synchronous execution const response = await client.executeDiagram(diagramId, "Hello, how are you?"); const aiResponse = parseResponse(response); console.log(`AI Response: ${aiResponse}`);
// Example 2: Asynchronous job execution const started = await client.executeAsJob(diagramId, "start processing"); const jobResult = started.jobId ? await client.pollJobStatus(started.jobId) : started.result; console.log(`Job Result: ${parseResponse(jobResult)}`);
// Example 3: Training workflow (optional) const trainingNodeId = process.env.SYNTHREO_TRAINING_NODE_ID; if (trainingNodeId) { await client.triggerTraining(diagramId, trainingNodeId); await client.monitorTraining(diagramId); }
} catch (error) { console.error('Error:', error.message); }}
// Run if this file is executed directlyif (require.main === module) { main();}
module.exports = { SynthreoClient, parseResponse };Complete Implementation
Section titled “Complete Implementation”import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.util.concurrent.TimeUnit;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;
public class SynthreoClient { private static final String BASE_URL = "https://builder-api.synthreo.ai"; private static final String AUTH_URL = "https://auth.synthreo.ai/connect/token";
private final String clientId; private final String keySecret; private final String targetApp; private String token; // Access tokens live 15 minutes with no refresh token; track expiry to re-exchange. private long tokenExpiryMillis = 0; private final ObjectMapper objectMapper;
public SynthreoClient(String keyId, String keySecret) { this(keyId, keySecret, "builder"); }
public SynthreoClient(String keyId, String keySecret, String targetApp) { this.clientId = "apikey-" + keyId; this.keySecret = keySecret; this.targetApp = targetApp; this.objectMapper = new ObjectMapper(); }
public String authenticate() throws Exception { String formBody = String.format( "grant_type=client_credentials&client_id=%s&client_secret=%s&target_app=%s", URLEncoder.encode(clientId, StandardCharsets.UTF_8), URLEncoder.encode(keySecret, StandardCharsets.UTF_8), URLEncoder.encode(targetApp, StandardCharsets.UTF_8) );
HttpURLConnection connection = createConnection(AUTH_URL, "POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
try (OutputStream os = connection.getOutputStream()) { os.write(formBody.getBytes(StandardCharsets.UTF_8)); }
int responseCode = connection.getResponseCode(); if (responseCode == 200) { String response = readResponse(connection); JsonNode jsonResponse = objectMapper.readTree(response); this.token = jsonResponse.get("access_token").asText(); long expiresIn = jsonResponse.has("expires_in") ? jsonResponse.get("expires_in").asLong() : 900; // Renew a minute early to absorb clock skew and request latency. this.tokenExpiryMillis = System.currentTimeMillis() + (expiresIn * 1000) - 60000; System.out.println("Authentication successful"); return this.token; } else { throw new RuntimeException("Authentication failed with code: " + responseCode); } }
// Re-exchange the API key when the cached token is missing or about to expire. private void ensureToken() throws Exception { if (this.token == null || System.currentTimeMillis() >= this.tokenExpiryMillis) { authenticate(); } }
public String executeDiagram(int diagramId, String message) throws Exception { return executeDiagram(diagramId, message, null); }
public String executeDiagram(int diagramId, String message, String conversationId) throws Exception { String url = BASE_URL + "/CognitiveDiagram/" + diagramId + "/Execute";
String userSaysData = conversationId != null ? String.format("{\"userSays\":\"%s\",\"conversationId\":\"%s\"}", message, conversationId) : String.format("{\"userSays\":\"%s\"}", message);
String jsonPayload = String.format( "{\"Action\":\"Execute\",\"UserSays\":\"[%s]\"}", userSaysData.replace("\"", "\\\"") );
HttpURLConnection connection = createConnection(url, "POST"); addAuthHeaders(connection); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("Origin", "https://builder.synthreo.ai");
try (OutputStream os = connection.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); }
int responseCode = connection.getResponseCode(); if (responseCode == 200) { return readResponse(connection); } else { throw new RuntimeException("Diagram execution failed with code: " + responseCode); } }
/** Outcome of starting a job: either a pollable jobId, or an inline result. */ public record JobStart(String jobId, String result) {}
public JobStart executeAsJob(int diagramId, String message) throws Exception { String url = BASE_URL + "/CognitiveDiagram/" + diagramId + "/ExecuteAsJob";
String jsonPayload = String.format( "{\"Action\":\"Execute\",\"UserSays\":\"[{\\\"userSays\\\":\\\"%s\\\"}]\",\"RobotSays\":\"\",\"CallerSource\":1}", message );
HttpURLConnection connection = createConnection(url, "POST"); addAuthHeaders(connection);
try (OutputStream os = connection.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); }
// 202 carries a job to poll; 200 means the diagram already finished and the // body IS the result, with no job id. Both are returned to the caller so a // quick job's result is never thrown away. int responseCode = connection.getResponseCode(); String response = readResponse(connection); if (responseCode == 202) { JsonNode job = objectMapper.readTree(response).get("job"); if (job == null || !job.hasNonNull("id")) { throw new RuntimeException("202 response carried no job id: " + response); } String jobId = job.get("id").asText(); System.out.println("Job initiated with ID: " + jobId); return new JobStart(jobId, null); } else if (responseCode == 200) { System.out.println("Job finished immediately; no polling needed"); return new JobStart(null, response); } else { throw new RuntimeException("Job initiation failed with code: " + responseCode); } }
public String pollJobStatus(String jobId, int intervalSeconds, int timeoutSeconds) throws Exception { String url = BASE_URL + "/job/" + jobId; long startTime = System.currentTimeMillis(); long timeoutMs = timeoutSeconds * 1000L;
while (System.currentTimeMillis() - startTime < timeoutMs) { long remainingMs = timeoutMs - (System.currentTimeMillis() - startTime);
HttpURLConnection connection = createConnection(url, "GET"); addAuthHeaders(connection); // Bound the request so a stalled connection cannot outlive the deadline. connection.setConnectTimeout((int) Math.min(10_000L, Math.max(1_000L, remainingMs))); connection.setReadTimeout((int) Math.min(30_000L, Math.max(1_000L, remainingMs)));
int responseCode = connection.getResponseCode();
if (responseCode == 202) { System.out.println("Job is still running..."); // Never sleep past the deadline. long remainingSeconds = Math.max(0L, (timeoutMs - (System.currentTimeMillis() - startTime)) / 1000L); TimeUnit.SECONDS.sleep(Math.min((long) intervalSeconds, remainingSeconds)); continue; } else if (responseCode == 200) { String body = readResponse(connection); // An empty body means the id is unknown or was already read, not success. if (body == null || body.isBlank()) { throw new RuntimeException("Job " + jobId + " is unknown, already read, or expired"); } System.out.println("Job completed!"); return body; } else if (responseCode == 400) { // The job ran and failed; the error is in the body, which for an error // status has to be read from the error stream. String body = readErrorResponse(connection); throw new RuntimeException("Job failed: " + body); } else { throw new RuntimeException("Unexpected response code: " + responseCode); } }
throw new RuntimeException("Job polling timed out after " + timeoutSeconds + " seconds"); }
public String triggerTraining(int diagramId, String nodeId, int repositoryNodeId) throws Exception { String url = BASE_URL + "/CognitiveDiagram/" + diagramId + "/TrainNode";
String jsonPayload = String.format( "{\"nodeId\":\"%s\",\"repositoryNodeId\":%d,\"finishedFlag\":false,\"logText\":\"Training started by API user\"}", nodeId, repositoryNodeId );
HttpURLConnection connection = createConnection(url, "PATCH"); addAuthHeaders(connection);
try (OutputStream os = connection.getOutputStream()) { os.write(jsonPayload.getBytes(StandardCharsets.UTF_8)); }
int responseCode = connection.getResponseCode(); if (responseCode == 200 || responseCode == 204) { System.out.println("Training triggered for agent " + diagramId); return responseCode == 200 ? readResponse(connection) : "Training initiated"; } else { throw new RuntimeException("Training trigger failed with code: " + responseCode); } }
public String monitorTraining(int diagramId, int intervalSeconds, int timeoutSeconds) throws Exception { String url = BASE_URL + "/CognitiveDiagram/" + diagramId; long startTime = System.currentTimeMillis(); long timeoutMs = timeoutSeconds * 1000L;
while (System.currentTimeMillis() - startTime < timeoutMs) { HttpURLConnection connection = createConnection(url, "GET"); addAuthHeaders(connection);
int responseCode = connection.getResponseCode(); if (responseCode == 200) { String response = readResponse(connection); JsonNode jsonResponse = objectMapper.readTree(response); int stateId = jsonResponse.get("stateId").asInt();
if (stateId == 6) { System.out.println("Agent is training..."); TimeUnit.SECONDS.sleep(intervalSeconds); } else if (stateId == 2) { System.out.println("Training completed! Agent is ready."); return response; } else { System.out.println("Unexpected state ID: " + stateId); return response; } } else { throw new RuntimeException("Training monitoring failed with code: " + responseCode); } }
throw new RuntimeException("Training monitoring timed out after " + timeoutSeconds + " seconds"); }
public static String parseResponse(String apiResponse) { try { ObjectMapper mapper = new ObjectMapper(); JsonNode jsonResponse = mapper.readTree(apiResponse);
JsonNode outputData = jsonResponse.get("outputData"); if (outputData != null && !outputData.isNull()) { String outputDataStr = outputData.asText();
try { JsonNode parsedOutput = mapper.readTree(outputDataStr);
if (parsedOutput.isArray() && parsedOutput.size() > 0) { return parsedOutput.get(0).asText(); } else if (parsedOutput.isObject()) { // Try common response field names String[] fields = {"response", "gpt_response", "answer", "result"}; for (String field : fields) { if (parsedOutput.has(field)) { return parsedOutput.get(field).asText(); } } return parsedOutput.toString(); } else { return parsedOutput.asText(); } } catch (Exception e) { return outputDataStr; } }
JsonNode errorData = jsonResponse.get("errorData"); if (errorData != null && !errorData.isNull() && !errorData.asText().equals("[]")) { try { JsonNode errors = mapper.readTree(errorData.asText()); if (errors.isArray() && errors.size() > 0) { return "Error: " + errors.get(0).get("message").asText(); } } catch (Exception e) { return "Error: " + errorData.asText(); } }
return "No response generated";
} catch (Exception e) { return "Failed to parse response: " + e.getMessage(); } }
private HttpURLConnection createConnection(String urlString, String method) throws Exception { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method); connection.setDoOutput(true); return connection; }
private void addAuthHeaders(HttpURLConnection connection) throws Exception { ensureToken(); connection.setRequestProperty("Authorization", "Bearer " + token); connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Accept", "*/*"); }
private String readResponse(HttpURLConnection connection) throws Exception { return readStream(connection.getInputStream()); }
// A 4xx body arrives on the error stream, not the input stream - a failed job's // reason is only readable through here. private String readErrorResponse(HttpURLConnection connection) throws Exception { java.io.InputStream stream = connection.getErrorStream(); return stream == null ? "" : readStream(stream); }
private String readStream(java.io.InputStream stream) throws Exception { try (BufferedReader reader = new BufferedReader( new InputStreamReader(stream, StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } return response.toString(); } }
// Usage Example public static void main(String[] args) { try { // Initialize client with environment variables String keyId = System.getenv("SYNTHREO_API_KEY_ID"); String keySecret = System.getenv("SYNTHREO_API_KEY_SECRET"); int diagramId = Integer.parseInt(System.getenv("SYNTHREO_DIAGRAM_ID"));
SynthreoClient client = new SynthreoClient(keyId, keySecret);
// Authenticate client.authenticate();
// Example 1: Synchronous execution String response = client.executeDiagram(diagramId, "Hello, how are you?"); String aiResponse = parseResponse(response); System.out.println("AI Response: " + aiResponse);
// Example 2: Asynchronous job execution // A null job id means the diagram finished inline; use that result directly. SynthreoClient.JobStart started = client.executeAsJob(diagramId, "start processing"); String jobResult = started.jobId() == null ? started.result() : client.pollJobStatus(started.jobId(), 30, 3600); System.out.println("Job Result: " + parseResponse(jobResult));
// Example 3: Training workflow (optional) String trainingNodeId = System.getenv("SYNTHREO_TRAINING_NODE_ID"); if (trainingNodeId != null && !trainingNodeId.isEmpty()) { client.triggerTraining(diagramId, trainingNodeId, 59); client.monitorTraining(diagramId, 60, 3600); }
} catch (Exception e) { System.err.println("Error: " + e.getMessage()); e.printStackTrace(); } }}C# (.NET)
Section titled “C# (.NET)”Complete Implementation
Section titled “Complete Implementation”using System;using System.Collections.Generic;using System.Net.Http;using System.Net.Http.Headers;using System.Text;using System.Text.Json;using System.Threading.Tasks;using System.Threading;
public class SynthreoClient{ private readonly string _clientId; private readonly string _keySecret; private readonly string _targetApp; private string _token; // Access tokens live 15 minutes with no refresh token; track expiry to re-exchange. private DateTimeOffset _tokenExpiry = DateTimeOffset.MinValue; private readonly HttpClient _httpClient; private const string BaseUrl = "https://builder-api.synthreo.ai"; private const string AuthUrl = "https://auth.synthreo.ai/connect/token";
public SynthreoClient(string keyId, string keySecret, string targetApp = "builder") { _clientId = $"apikey-{keyId}"; _keySecret = keySecret; _targetApp = targetApp; _httpClient = new HttpClient(); }
public async Task<string> AuthenticateAsync() { using var request = new HttpRequestMessage(HttpMethod.Post, AuthUrl) { Content = new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "client_credentials", ["client_id"] = _clientId, ["client_secret"] = _keySecret, ["target_app"] = _targetApp }) };
try { var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseBody);
_token = doc.RootElement.GetProperty("access_token").GetString(); var expiresIn = doc.RootElement.TryGetProperty("expires_in", out var e) ? e.GetInt32() : 900; // Renew a minute early to absorb clock skew and request latency. _tokenExpiry = DateTimeOffset.UtcNow.AddSeconds(expiresIn - 60); Console.WriteLine("Authentication successful"); return _token; } catch (HttpRequestException ex) { throw new Exception($"Authentication failed: {ex.Message}"); } }
// Re-exchange the API key when the cached token is missing or about to expire, then set headers. private async Task EnsureAuthHeadersAsync() { if (string.IsNullOrEmpty(_token) || DateTimeOffset.UtcNow >= _tokenExpiry) { await AuthenticateAsync(); }
_httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token); _httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("*/*")); }
public async Task<string> ExecuteDiagramAsync(int diagramId, string message, string conversationId = null) { await EnsureAuthHeadersAsync(); var url = $"{BaseUrl}/CognitiveDiagram/{diagramId}/Execute";
var userSaysData = new { userSays = message }; var userSaysJson = conversationId != null ? JsonSerializer.Serialize(new { userSays = message, conversationId }) : JsonSerializer.Serialize(userSaysData);
var payload = new { Action = "Execute", UserSays = $"[{userSaysJson}]" };
var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, Encoding.UTF8, "application/json");
// Add specific headers for diagram execution content.Headers.Clear(); content.Headers.Add("Content-Type", "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content }; request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Add("Origin", "https://builder.synthreo.ai");
try { var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } catch (HttpRequestException ex) { throw new Exception($"Diagram execution failed: {ex.Message}"); } }
public async Task<(string JobId, string Result)> ExecuteAsJobAsync(int diagramId, string message = "start") { await EnsureAuthHeadersAsync(); var url = $"{BaseUrl}/CognitiveDiagram/{diagramId}/ExecuteAsJob";
var payload = new { Action = "Execute", UserSays = $"[{{\"userSays\":\"{message}\"}}]", RobotSays = "", CallerSource = 1 };
var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, Encoding.UTF8, "application/json");
try { var response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseBody);
// 202 carries a job to poll; 200 means the diagram already finished and the // body IS the result, with no job id. Both are returned so a quick job's // result is never thrown away. if (response.StatusCode == System.Net.HttpStatusCode.Accepted) { if (!doc.RootElement.TryGetProperty("job", out var job) || job.ValueKind != JsonValueKind.Object || !job.TryGetProperty("id", out var id)) { throw new Exception($"202 response carried no job id: {responseBody}"); } var jobId = id.GetString(); Console.WriteLine($"Job initiated with ID: {jobId}"); return (jobId, null); }
Console.WriteLine("Job finished immediately; no polling needed"); return (null, responseBody); } catch (HttpRequestException ex) { throw new Exception($"Job initiation failed: {ex.Message}"); } }
public async Task<string> PollJobStatusAsync(string jobId, int intervalSeconds = 30, int timeoutSeconds = 3600) { var url = $"{BaseUrl}/job/{jobId}"; var startTime = DateTime.UtcNow; var timeout = TimeSpan.FromSeconds(timeoutSeconds);
while (DateTime.UtcNow - startTime < timeout) { try { // Refresh the token each poll so long jobs keep a valid 15-minute token. await EnsureAuthHeadersAsync(); var response = await _httpClient.GetAsync(url);
if (response.StatusCode == System.Net.HttpStatusCode.Accepted) { Console.WriteLine("Job is still running..."); await Task.Delay(TimeSpan.FromSeconds(intervalSeconds)); continue; } else if (response.IsSuccessStatusCode) { var body = await response.Content.ReadAsStringAsync(); // An empty body means the id is unknown or was already read - the job // is gone, which is not the same as success. if (string.IsNullOrWhiteSpace(body)) { throw new Exception($"Job {jobId} is unknown, already read, or expired"); } Console.WriteLine("Job completed!"); return body; } else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { // The job ran and failed. Read the reason instead of calling // EnsureSuccessStatusCode, which would discard it. var body = await response.Content.ReadAsStringAsync(); throw new Exception($"Job failed: {body}"); } else { throw new Exception($"Unexpected status code: {response.StatusCode}"); } } catch (HttpRequestException ex) { throw new Exception($"Error polling job status: {ex.Message}"); } }
throw new TimeoutException($"Job {jobId} timed out after {timeoutSeconds} seconds"); }
public async Task<string> TriggerTrainingAsync(int diagramId, string nodeId, int repositoryNodeId = 59) { await EnsureAuthHeadersAsync(); var url = $"{BaseUrl}/CognitiveDiagram/{diagramId}/TrainNode";
var payload = new { nodeId = nodeId, repositoryNodeId = repositoryNodeId, finishedFlag = false, logText = "Training started by API user" };
var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, Encoding.UTF8, "application/json");
try { var response = await _httpClient.PatchAsync(url, content); response.EnsureSuccessStatusCode();
Console.WriteLine($"Training triggered for agent {diagramId}"); return await response.Content.ReadAsStringAsync(); } catch (HttpRequestException ex) { throw new Exception($"Training trigger failed: {ex.Message}"); } }
public async Task<string> MonitorTrainingAsync(int diagramId, int intervalSeconds = 60, int timeoutSeconds = 3600) { var url = $"{BaseUrl}/CognitiveDiagram/{diagramId}"; var startTime = DateTime.UtcNow; var timeout = TimeSpan.FromSeconds(timeoutSeconds);
while (DateTime.UtcNow - startTime < timeout) { try { // Refresh the token each cycle so long monitors keep a valid token. await EnsureAuthHeadersAsync(); var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseBody);
var stateId = doc.RootElement.GetProperty("stateId").GetInt32();
if (stateId == 6) { Console.WriteLine("Agent is training..."); await Task.Delay(TimeSpan.FromSeconds(intervalSeconds)); } else if (stateId == 2) { Console.WriteLine("Training completed! Agent is ready."); return responseBody; } else { Console.WriteLine($"Unexpected state ID: {stateId}"); return responseBody; } } catch (HttpRequestException ex) { throw new Exception($"Error monitoring training: {ex.Message}"); } }
throw new TimeoutException($"Training monitoring timed out after {timeoutSeconds} seconds"); }
public static string ParseResponse(string apiResponse) { try { using var doc = JsonDocument.Parse(apiResponse); var root = doc.RootElement;
if (root.TryGetProperty("outputData", out var outputDataElement) && !outputDataElement.ValueEquals("")) { var outputDataStr = outputDataElement.GetString();
try { using var outputDoc = JsonDocument.Parse(outputDataStr); var outputRoot = outputDoc.RootElement;
if (outputRoot.ValueKind == JsonValueKind.Array && outputRoot.GetArrayLength() > 0) { return outputRoot[0].ToString(); } else if (outputRoot.ValueKind == JsonValueKind.Object) { // Try common response field names var fields = new[] { "response", "gpt_response", "answer", "result" }; foreach (var field in fields) { if (outputRoot.TryGetProperty(field, out var fieldElement)) { return fieldElement.GetString(); } } return outputRoot.ToString(); } else { return outputRoot.ToString(); } } catch { return outputDataStr; } }
if (root.TryGetProperty("errorData", out var errorDataElement) && !errorDataElement.ValueEquals("[]")) { var errorDataStr = errorDataElement.GetString(); try { using var errorDoc = JsonDocument.Parse(errorDataStr); var errorRoot = errorDoc.RootElement;
if (errorRoot.ValueKind == JsonValueKind.Array && errorRoot.GetArrayLength() > 0) { var firstError = errorRoot[0]; if (firstError.TryGetProperty("message", out var messageElement)) { return $"Error: {messageElement.GetString()}"; } } } catch { return $"Error: {errorDataStr}"; } }
return "No response generated"; } catch (Exception ex) { return $"Failed to parse response: {ex.Message}"; } }
public void Dispose() { _httpClient?.Dispose(); }}
// Usage Examplepublic class Program{ public static async Task Main(string[] args) { try { // Initialize client with environment variables var keyId = Environment.GetEnvironmentVariable("SYNTHREO_API_KEY_ID"); var keySecret = Environment.GetEnvironmentVariable("SYNTHREO_API_KEY_SECRET"); var diagramId = int.Parse(Environment.GetEnvironmentVariable("SYNTHREO_DIAGRAM_ID"));
using var client = new SynthreoClient(keyId, keySecret);
// Authenticate await client.AuthenticateAsync();
// Example 1: Synchronous execution var response = await client.ExecuteDiagramAsync(diagramId, "Hello, how are you?"); var aiResponse = SynthreoClient.ParseResponse(response); Console.WriteLine($"AI Response: {aiResponse}");
// Example 2: Asynchronous job execution // A null job id means the diagram finished inline; use that result directly. var started = await client.ExecuteAsJobAsync(diagramId, "start processing"); var jobResult = started.JobId == null ? started.Result : await client.PollJobStatusAsync(started.JobId); Console.WriteLine($"Job Result: {SynthreoClient.ParseResponse(jobResult)}");
// Example 3: Training workflow (optional) var trainingNodeId = Environment.GetEnvironmentVariable("SYNTHREO_TRAINING_NODE_ID"); if (!string.IsNullOrEmpty(trainingNodeId)) { await client.TriggerTrainingAsync(diagramId, trainingNodeId); await client.MonitorTrainingAsync(diagramId); } } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } }}Quick Start Examples
Section titled “Quick Start Examples”Simple Synchronous Execution
Section titled “Simple Synchronous Execution”Python
Section titled “Python”from synthreo_client import SynthreoClientimport os
client = SynthreoClient( os.getenv('SYNTHREO_API_KEY_ID'), os.getenv('SYNTHREO_API_KEY_SECRET'))
response = client.execute_diagram(12345, "Hello AI!")print(parse_response(response))Node.js
Section titled “Node.js”const client = new SynthreoClient( process.env.SYNTHREO_API_KEY_ID, process.env.SYNTHREO_API_KEY_SECRET);
async function quickStart() { await client.authenticate(); const response = await client.executeDiagram(12345, "Hello AI!"); console.log(parseResponse(response));}
quickStart();Asynchronous Job with Polling
Section titled “Asynchronous Job with Polling”Python
Section titled “Python”started = client.execute_as_job(12345, "process data")
# A quick diagram finishes inline and has no job id to poll.result = ( started['result'] if started['job_id'] is None else client.poll_job_status(started['job_id'], interval=30, timeout=3600))print(f"Final result: {parse_response(result)}")Node.js
Section titled “Node.js”// Start job and wait for completionconst started = await client.executeAsJob(12345, "process data");// A quick diagram finishes inline and has no job id to poll.const result = started.jobId ? await client.pollJobStatus(started.jobId, 30000, 3600000) : started.result;console.log(`Final result: ${parseResponse(result)}`);Error Handling Examples
Section titled “Error Handling Examples”Python Error Handling
Section titled “Python Error Handling”try: response = client.execute_diagram(diagram_id, message) result = parse_response(response)
if result.startswith("Error:"): print(f"AI Agent Error: {result}") else: print(f"Success: {result}")
except requests.exceptions.HTTPError as e: print(f"HTTP Error: {e}")except requests.exceptions.RequestException as e: print(f"Request Error: {e}")except Exception as e: print(f"Unexpected Error: {e}")Node.js Error Handling
Section titled “Node.js Error Handling”try { const response = await client.executeDiagram(diagramId, message); const result = parseResponse(response);
if (result.startsWith("Error:")) { console.error(`AI Agent Error: ${result}`); } else { console.log(`Success: ${result}`); }} catch (error) { if (error.response) { console.error(`HTTP Error ${error.response.status}: ${error.response.data}`); } else if (error.request) { console.error('Network Error: No response received'); } else { console.error(`Error: ${error.message}`); }}Environment Setup
Section titled “Environment Setup”Python Requirements
Section titled “Python Requirements”requests>=2.25.0python-dotenv>=0.19.0Node.js Dependencies
Section titled “Node.js Dependencies”{ "dependencies": { "axios": "^1.6.0", "dotenv": "^16.3.0" }}Java Dependencies (Maven)
Section titled “Java Dependencies (Maven)”<dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency></dependencies>C# Dependencies
Section titled “C# Dependencies”<PackageReference Include="System.Text.Json" Version="7.0.0" />Best Practices
Section titled “Best Practices”- Always authenticate before making API calls
- Use environment variables for sensitive data
- Implement proper error handling and timeouts
- Choose appropriate polling intervals based on expected job duration
- Parse responses carefully and handle different output formats
- Log important events for debugging and monitoring
- Implement retry logic for transient failures

