Embedding the Graphyn Server
The server module ships as a standalone Ktor application and as an installable Ktor plugin (install(Graphyn)), so you can embed workflow execution directly into an existing Ktor server.
Quick start
Add the :server module to your project and call install(Graphyn) in your application module:
// build.gradle.kts
dependencies {
implementation(projects.server)
}
// Application.kt
fun Application.module() {
// Your existing plugins …
install(Authentication) { /* … */ }
// Mount Graphyn under /graphyn — all workflow routes land here.
install(Graphyn) {
routePrefix = "/graphyn"
requireApiKey = false // your auth already handles it
}
routing {
get("/health") { call.respondText("ok") }
}
}
Configuration reference
| Property | Type | Default | Description |
|---|---|---|---|
routePrefix |
String |
"" |
URL prefix for all Graphyn routes. "/graphyn" mounts execution at /graphyn/execute. |
requireApiKey |
Boolean |
true |
Install [GraphynAuthPlugin] to enforce Bearer-token auth. Set false when your gateway handles auth. |
apiKey |
String? |
null |
Explicit API key. null falls back to the GRAPHYN_API_KEY env var. |
store |
WorkflowStore |
FileWorkflowStore() |
Persistence back-end for saved workflows. |
extraPlugins |
List<GraphynPlugin> |
[] |
Additional node plugins on top of the built-in runtime (see Custom node plugins). |
Routes
After installation, Graphyn mounts the following routes (relative to routePrefix):
Execution
| Method | Path | Description |
|---|---|---|
POST |
/validate |
Validate a workflow. Returns List<ValidationError> (empty = valid). |
POST |
/execute |
Run synchronously. Returns WorkflowExecutionResult. |
POST |
/executions |
Start async run. Returns { runId } with 202 Accepted. |
GET |
/executions/{id}/events |
SSE stream of ExecutionStreamMessage frames until terminal frame. |
Workflow CRUD
| Method | Path | Description |
|---|---|---|
GET |
/workflows |
List saved workflows (List<WorkflowMeta>). |
GET |
/workflows/{id} |
Load a WorkflowDefinition (404 when not found). |
POST |
/workflows |
Save / upsert a workflow. Returns WorkflowMeta with 201 Created. |
DELETE |
/workflows/{id} |
Delete a workflow. Returns 204 No Content. |
Authentication
By default requireApiKey = true. Set GRAPHYN_API_KEY in the environment to activate enforcement:
GRAPHYN_API_KEY=my-secret ./gradlew :server:run
Clients pass the key as a Bearer token:
Authorization: Bearer my-secret
GET / (the health-check) is always exempt. All other routes return 401 Unauthorized without a valid token.
To supply the key programmatically (useful in tests or multi-tenant setups):
install(Graphyn) {
requireApiKey = true
apiKey = resolveKeyFromVault() // your key store
}
Set requireApiKey = false when your API gateway or reverse proxy already handles auth upstream.
Custom node plugins
GraphynRuntime.runtimePlugins (io, text, control, json, list-ops, preview) are always installed.
Add your own domain nodes with extraPlugins:
// 1. Define a plugin
object MyPlugin : GraphynPlugin {
override fun install(registrar: DefaultGraphynPluginRegistry) {
registrar.registerSpec(NodeSpec(
type = "my.transform",
label = "My Transform",
inputs = listOf(PortSpec("input", WorkflowType.StringType)),
outputs = listOf(PortSpec("output", WorkflowType.StringType)),
))
registrar.registerExecutor("my.transform") { inputs ->
val text = (inputs["input"] as? WorkflowValue.StringValue)?.value ?: ""
mapOf("output" to WorkflowValue.StringValue(text.uppercase()))
}
}
}
// 2. Install it
install(Graphyn) {
plugins(MyPlugin) // convenience vararg setter
// or: extraPlugins = listOf(MyPlugin, AnotherPlugin)
}
Any workflow containing my.transform nodes can now be executed and validated server-side.
For media workflows, install MediaCorePlugin and MediaAiPlugin and ensure FFmpeg is on the server's PATH:
install(Graphyn) {
plugins(MediaCorePlugin, MediaAiPlugin)
}
Custom persistence
The default FileWorkflowStore writes JSON files in the working directory. Replace it with your own implementation of WorkflowStore:
class PostgresWorkflowStore(private val db: Database) : WorkflowStore {
override suspend fun list(): List<WorkflowMeta> = TODO()
override suspend fun load(id: String): WorkflowDefinition? = TODO()
override suspend fun save(workflow: WorkflowDefinition): WorkflowMeta = TODO()
override suspend fun delete(id: String) = TODO()
}
install(Graphyn) {
store = PostgresWorkflowStore(db)
}
Standalone server
If you don't have an existing Ktor app, run the bundled server directly:
./gradlew :server:run
# or build a fat JAR:
./gradlew :server:buildFatJar
java -jar server/build/libs/server-all.jar
The standalone server binds to 0.0.0.0:8080 and uses install(Graphyn) with all defaults.
Concurrent run limit
The server caps concurrent async workflow executions at 10 by default. When the limit is reached, POST /executions returns 503 Service Unavailable. The limit is currently not configurable via GraphynKtorConfig; for higher throughput deploy multiple instances behind a load balancer.
SSE streaming example (JavaScript)
const { runId } = await fetch('/graphyn/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer my-secret' },
body: JSON.stringify(workflow),
}).then(r => r.json());
const es = new EventSource(`/graphyn/executions/${runId}/events`);
es.addEventListener('event', e => console.log('node done', JSON.parse(e.data)));
es.addEventListener('completed', e => { console.log('done', JSON.parse(e.data)); es.close(); });
es.addEventListener('failed', e => { console.error('failed', JSON.parse(e.data)); es.close(); });