docs/guide/integration.md
Astron Agent can run workflows for another application through its published workflow API. Your application stays responsible for its own UI and business logic, while Astron Agent provides the workflow, model, knowledge, tool, and RPA orchestration behind an HTTP/SSE boundary.
This is a service integration, not an in-process library integration. The supported consumer boundary is the public workflow API exposed by Astron Agent's gateway. Internal Python and Java modules are implementation details and are not published as stable SDK packages.
Use the workflow API when you want to:
If you only want to evaluate or operate the full platform, start with Quick Start or Deployment instead.
Your application
│ POST /workflow/v1/chat/completions
│ Authorization: Bearer <application-key>:<application-secret>
▼
Astron Agent gateway (Nginx)
│ validates the application and forwards its App ID
▼
Published workflow
└─ models · knowledge · tools/MCP · RPA
Call the gateway address shown after publishing. Do not call core-workflow:7880, /internal/gateway/auth/**, or other container-only endpoints directly: those routes are internal, and direct calls bypass the supported gateway boundary.
Keep the API Secret in a server-side secret store. Do not put it in browser code, mobile packages, source control, screenshots, or client-visible logs.
The public endpoint is:
POST <ASTRON_BASE_URL>/workflow/v1/chat/completions
Use the exact Service URL displayed by the console when it differs from the path above. Authentication uses the application credentials in one header:
Authorization: Bearer <application-key>:<application-secret>
Content-Type: application/json
A minimal request is:
{
"flow_id": "<FLOW_ID>",
"uid": "user-123",
"stream": true,
"parameters": {
"query": "Summarize this support request"
}
}
flow_id and parameters are required. The available keys inside parameters come from the workflow's Start node, so replace query with the inputs defined by your published workflow.
Optional request fields:
| Field | Type | Purpose |
|---|---|---|
uid | string | Your end-user identifier (up to 40 characters). |
stream | boolean | true for SSE streaming; false for one JSON response. Defaults to true. |
chat_id | string | Your conversation identifier (up to 128 characters). Reuse it when continuing a conversation. |
history | array | Earlier messages as `{ "role": "user" |
ext | object | Integration-specific metadata forwarded with the request. |
version | string | Published workflow version when your environment requires one. |
export ASTRON_BASE_URL="https://astron.example.com"
export ASTRON_API_KEY="replace-me"
export ASTRON_API_SECRET="replace-me"
export ASTRON_FLOW_ID="replace-me"
curl --no-buffer \
--request POST "$ASTRON_BASE_URL/workflow/v1/chat/completions" \
--header "Authorization: Bearer $ASTRON_API_KEY:$ASTRON_API_SECRET" \
--header "Content-Type: application/json" \
--data "{
\"flow_id\": \"$ASTRON_FLOW_ID\",
\"uid\": \"user-123\",
\"chat_id\": \"conversation-456\",
\"stream\": true,
\"parameters\": {
\"query\": \"Summarize this support request\"
}
}"
This example uses only the Python standard library. Run it in a backend service, not in browser-delivered code.
import json
import os
import urllib.request
base_url = os.environ["ASTRON_BASE_URL"].rstrip("/")
credential = f'{os.environ["ASTRON_API_KEY"]}:{os.environ["ASTRON_API_SECRET"]}'
payload = {
"flow_id": os.environ["ASTRON_FLOW_ID"],
"uid": "user-123",
"chat_id": "conversation-456",
"stream": True,
"parameters": {"query": "Summarize this support request"},
}
request = urllib.request.Request(
f"{base_url}/workflow/v1/chat/completions",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {credential}",
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=1800) as response:
for raw_line in response:
line = raw_line.decode("utf-8").strip()
if not line.startswith("data:"):
continue
event = json.loads(line.removeprefix("data:").strip())
if event.get("code") != 0:
raise RuntimeError(event.get("message", "workflow failed"))
choice = (event.get("choices") or [{}])[0]
delta = choice.get("delta") or {}
if delta.get("content"):
print(delta["content"], end="", flush=True)
finish_reason = choice.get("finish_reason")
if finish_reason == "stop":
break
if finish_reason == "interrupt":
print("\nWorkflow paused and requires a reply.")
break
const baseUrl = process.env.ASTRON_BASE_URL.replace(/\/$/, "");
const authorization = `Bearer ${process.env.ASTRON_API_KEY}:${process.env.ASTRON_API_SECRET}`;
const response = await fetch(`${baseUrl}/workflow/v1/chat/completions`, {
method: "POST",
headers: {
Authorization: authorization,
"Content-Type": "application/json",
Accept: "text/event-stream"
},
body: JSON.stringify({
flow_id: process.env.ASTRON_FLOW_ID,
uid: "user-123",
chat_id: "conversation-456",
stream: true,
parameters: { query: "Summarize this support request" }
})
});
if (!response.ok || !response.body) {
throw new Error(`Astron request failed: ${response.status} ${await response.text()}`);
}
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
let finished = false;
while (!finished) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
const frames = buffer.split("\n\n");
buffer = frames.pop() ?? "";
for (const frame of frames) {
const dataLine = frame.split("\n").find((line) => line.startsWith("data:"));
if (!dataLine) continue;
const event = JSON.parse(dataLine.slice(5).trim());
if (event.code !== 0) throw new Error(event.message || "workflow failed");
const choice = event.choices?.[0];
if (choice?.delta?.content) process.stdout.write(choice.delta.content);
if (choice?.finish_reason === "interrupt") {
console.log("\nWorkflow paused and requires a reply.");
finished = true;
}
if (choice?.finish_reason === "stop") finished = true;
}
}
With stream: true, the response media type is text/event-stream. Each frame contains a data: line whose value is JSON. Useful fields include:
{
"code": 0,
"message": "Success",
"id": "request-or-session-id",
"choices": [
{
"delta": {
"role": "assistant",
"content": "incremental output",
"reasoning_content": ""
},
"finish_reason": null
}
],
"workflow_step": {
"seq": 3,
"progress": 0.5
}
}
Consumer rules:
choices[0].delta.content when it is non-empty;code != 0 as a workflow error even if the HTTP connection was established successfully;finish_reason: "ping";finish_reason: "stop";finish_reason: "interrupt" only if your workflow contains an interactive pause;workflow_step field as a stable business contract.Set stream to false to receive one JSON response. The response uses the same top-level shape; inspect code, message, choices, and usage rather than assuming a plain text body.
If a frame has finish_reason: "interrupt", save event_data.event_id. After collecting the user's answer, call:
POST <ASTRON_BASE_URL>/workflow/v1/resume
Use the same Authorization header:
curl --no-buffer \
--request POST "$ASTRON_BASE_URL/workflow/v1/resume" \
--header "Authorization: Bearer $ASTRON_API_KEY:$ASTRON_API_SECRET" \
--header "Content-Type: application/json" \
--data '{
"event_id": "<EVENT_ID>",
"event_type": "resume",
"content": "The user reply"
}'
The resume response follows the mode of the interrupted request. Event IDs are runtime state: resume promptly and handle an expired or already-resumed event as an error.
uid and chat_id from your system; do not put secrets or sensitive personal data in either value.parameters as untrusted input and constrain it before passing values to tools, databases, or RPA actions.id is useful for troubleshooting. Log it with the status and duration, but redact credentials and sensitive prompts.The following can be useful when contributing to Astron Agent itself, but external applications should not depend on them as stable APIs:
core/agent, core/workflow, or core/common;X-Consumer-Username yourself;/internal/gateway/auth/** or unpublished /workflow/v1/** routes;For repository-level extension work, see Project Modules. For reusable workflow definitions, see Workflow Examples.
| Symptom | Check |
|---|---|
401 or a missing/malformed credential error | Header must be exactly Authorization: Bearer <application-key>:<application-secret>. Confirm neither value is empty. |
Failed to get application | Confirm the API Key and Secret belong to the application selected at publication time. |
| Workflow not found or not authorized | Use the Flow ID shown for the published API, not the App ID or an editor-only ID. |
| Parameter validation error | Send a parameters object whose keys and value types match the Start node. |
| Output arrives all at once | Disable response buffering in every reverse proxy and use an SSE-capable HTTP client. |
| Stream stays open without text | Heartbeat frames are normal while a long-running node executes; enforce your own overall deadline. |
| Works inside Docker but not externally | Call the published gateway host and exposed port, then check DNS, firewall, TLS, and proxy routing. |
See the FAQ, Configuration Reference, and Deployment Guide for additional operational guidance.