Developer End-to-End: Workspace, Function, Flow
This guide starts from the tenant and workspace created by the kind quickstart. It shows how to treat a workspace as a project/stage boundary, deploy a function, invoke it, and call that function from a Flow.
The current public model has no standalone stage resource. A stage is represented by the workspace environment field. The generated OpenAPI contract allows dev, sandbox, staging, prod, and preview.
NOTE
The generated OpenAPI schemas use ten_... and wrk_... identifier patterns. The current local control-plane runtime used by the kind path returns UUID-backed tenant and workspace IDs. Use the IDs returned by your running platform; do not hand-write ID prefixes.
Prerequisites
Complete the kind quickstart, including these exported values:
export TENANT_ID="$(jq -r '.tenantId' /tmp/falcone-tenant.json)"
export WORKSPACE_ID="$(jq -r '.workspaceId' /tmp/falcone-workspace.json)"Functions and hosted MCP servers are Knative Services at runtime. For the kind path, install the repo-provided Knative Serving + Kourier manifests before deploying a function:
kubectl apply -f deploy/kind/knative/serving-crds.yaml
kubectl apply -f deploy/kind/knative/serving-core.yaml
kubectl apply -f deploy/kind/knative/kourier.yaml
kubectl -n knative-serving wait --for=condition=Available deploy --all --timeout=10m
kubectl -n kourier-system wait --for=condition=Available deploy --all --timeout=10m
kubectl api-resources | grep serving.knative.devExpected result: services.serving.knative.dev appears in the API resource list.
Keep the quickstart control-plane and Keycloak port-forwards running:
kubectl -n falcone port-forward svc/falcone-control-plane 8080:8080
kubectl -n falcone port-forward svc/falcone-keycloak 8081:8080Add a third port-forward for the executor runtime. Flows, data APIs, events, realtime, MCP, and the executor-local function routes are served there:
kubectl -n falcone port-forward svc/falcone-control-plane-executor 8082:8080Set endpoint variables:
export CONTROL_API=http://127.0.0.1:8080
export EXECUTOR_API=http://127.0.0.1:80821. Get a tenant-scoped token
The quickstart created a tenant owner named acme-owner in the tenant realm. The tenant realm name is the TENANT_ID, and the public app client is derived from the tenant slug.
export TENANT_SLUG=acme-quickstart
export OWNER_USERNAME=acme-owner
export OWNER_PASSWORD=Falcone-quickstart-ChangeMe-1
export TENANT_TOKEN="$(
curl -sS -X POST \
"http://127.0.0.1:8081/realms/${TENANT_ID}/protocol/openid-connect/token" \
-H 'content-type: application/x-www-form-urlencoded' \
-d grant_type=password \
-d client_id="${TENANT_SLUG}-app" \
-d username="$OWNER_USERNAME" \
--data-urlencode "password=${OWNER_PASSWORD}" \
-d scope=openid | jq -r .access_token
)"
test "$TENANT_TOKEN" != "null" && test -n "$TENANT_TOKEN"Expected result: the final test command exits with status 0 and prints nothing.
Verify the workspace/stage:
curl -sS "$CONTROL_API/v1/workspaces/$WORKSPACE_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq '{workspaceId, tenantId, slug, environment, state}'Expected output shape:
{
"workspaceId": "...",
"tenantId": "...",
"slug": "acme-dev",
"environment": "dev",
"state": "active"
}2. Deploy a function
The function runtime expects Node.js source that defines main(params). The generated OpenAPI route for governed functions is POST /v1/functions/actions.
cat > /tmp/falcone-hello-function.json <<'JSON'
{
"tenantId": "",
"workspaceId": "",
"actionName": "hello-orders",
"source": {
"kind": "inline_code",
"language": "javascript",
"inlineCode": "function main(params, context) { return { greeting: `hello ${params.name || 'Falcone'}`, workspaceId: context.workspaceId }; }",
"entryFile": "index.js"
},
"execution": {
"runtime": "nodejs:20",
"entrypoint": "main",
"parameters": {},
"environment": {},
"limits": {
"timeoutSeconds": 60,
"memoryMb": 256
},
"webAction": {
"enabled": false,
"requireAuthentication": true,
"rawHttpResponse": false
}
},
"activationPolicy": {
"logsAccess": "workspace_developers",
"resultAccess": "workspace_developers",
"rerunPolicy": "manual_only",
"retentionHours": 24
}
}
JSON
jq --arg tenant "$TENANT_ID" --arg workspace "$WORKSPACE_ID" \
'.tenantId=$tenant | .workspaceId=$workspace' \
/tmp/falcone-hello-function.json > /tmp/falcone-hello-function.resolved.json
curl -sS -X POST "$CONTROL_API/v1/functions/actions" \
-H "authorization: Bearer $TENANT_TOKEN" \
-H 'content-type: application/json' \
-H "idempotency-key: fn-hello-$(date +%s)" \
-d @/tmp/falcone-hello-function.resolved.json \
| tee /tmp/falcone-function-create.json
export FUNCTION_ID="$(jq -r '.resourceId' /tmp/falcone-function-create.json)"Expected output shape:
{
"resourceId": "...",
"status": "accepted",
"acceptedAt": "..."
}If this returns FN_DEPLOY_FAILED with a serving.knative.dev API error, Knative Serving is not available in the cluster or the control-plane service account cannot create Knative Services.
3. Invoke the function
curl -sS -X POST "$CONTROL_API/v1/functions/actions/$FUNCTION_ID/invocations" \
-H "authorization: Bearer $TENANT_TOKEN" \
-H 'content-type: application/json' \
-H "idempotency-key: invoke-hello-$(date +%s)" \
-d '{
"parameters": { "name": "Falcone" },
"triggerContext": { "kind": "direct" },
"responseMode": "wait_for_result",
"idempotencyScope": "request"
}' \
| tee /tmp/falcone-function-invoke.json
export ACTIVATION_ID="$(jq -r '.invocationId' /tmp/falcone-function-invoke.json)"Expected output shape:
{
"invocationId": "...",
"resourceId": "...",
"status": "completed",
"acceptedAt": "..."
}Fetch the result:
curl -sS "$CONTROL_API/v1/functions/actions/$FUNCTION_ID/activations/$ACTIVATION_ID/result" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq '{activationId, status, result}'Expected output shape:
{
"activationId": "...",
"status": "succeeded",
"result": {
"greeting": "hello Falcone",
"workspaceId": "..."
}
}4. Create a Flow that calls the function
Flows are workspace-scoped and use the DSL documented in the Workflow DSL Reference. This minimal definition has one functions.invoke task.
cat > /tmp/falcone-hello-flow.json <<'JSON'
{
"name": "hello-order-flow",
"definition": {
"apiVersion": "v1.0",
"name": "hello-order-flow",
"description": "Call the hello-orders function.",
"nodes": [
{
"id": "callFunction",
"type": "task",
"taskType": "functions.invoke",
"input": {
"actionId": "",
"params": {
"name": "Flow"
}
}
}
]
}
}
JSON
jq --arg action "$FUNCTION_ID" \
'.definition.nodes[0].input.actionId=$action' \
/tmp/falcone-hello-flow.json > /tmp/falcone-hello-flow.resolved.json
curl -sS -X POST "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows" \
-H "authorization: Bearer $TENANT_TOKEN" \
-H 'content-type: application/json' \
-d @/tmp/falcone-hello-flow.resolved.json \
| tee /tmp/falcone-flow-create.json
export FLOW_ID="$(jq -r '.flowId' /tmp/falcone-flow-create.json)"Expected output shape:
{
"flowId": "...",
"name": "hello-order-flow",
"status": "draft",
"dslApiVersion": "v1.0"
}Validate and publish version 1:
curl -sS -X POST "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID/validate" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq
curl -sS -X POST "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID/versions" \
-H "authorization: Bearer $TENANT_TOKEN" \
| tee /tmp/falcone-flow-version.jsonExpected validation response:
{
"valid": true
}Expected publish response shape:
{
"flowId": "...",
"version": 1,
"createdAt": "..."
}5. Run the Flow
curl -sS -X POST "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID/executions" \
-H "authorization: Bearer $TENANT_TOKEN" \
-H 'content-type: application/json' \
-d '{ "version": 1, "input": { "orderId": "ord-1001" } }' \
| tee /tmp/falcone-flow-execution.json
export EXECUTION_ID="$(jq -r '.executionId' /tmp/falcone-flow-execution.json)"Expected output shape:
{
"executionId": "...",
"workflowId": "...",
"version": 1,
"status": "Running"
}Check the run:
curl -sS "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID/executions/$EXECUTION_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jqYou have now used one workspace as a project/stage, deployed a Knative-backed function, invoked it, and created a Flow that calls it.
Cleanup
Delete the function and flow resources before tearing down the quickstart cluster:
curl -sS -X DELETE "$CONTROL_API/v1/functions/actions/$FUNCTION_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq
curl -sS -X DELETE "$EXECUTOR_API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq