Developer End-to-End: Workspace, Function, Flow
This guide starts from a running Falcone platform and a tenant and workspace provided by its operator. 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. Use the IDs returned by your running platform; some runtimes return UUID-backed IDs. Do not hand-write ID prefixes.
What you need
Ask your platform operator for the console and identity hostnames, a tenant ID (also the tenant realm), tenant slug, workspace ID you may use, and a tenant user's username and password. The account needs permission to deploy functions and manage and run flows in that workspace. You also need curl and jq locally. Replace every placeholder below with the supplied value:
export API="https://<console hostname>"
export IAM="https://<identity hostname>"
export TENANT_ID="<tenant ID and realm>"
export TENANT_SLUG="<tenant slug>"
export WORKSPACE_ID="<workspace ID>"
export OWNER_USERNAME="<tenant username>"
export OWNER_PASSWORD="<tenant user password>"Use the console hostname for /v1/... requests: its API proxy forwards them to the gateway. The operator must configure issuer trust for the identity hostname and have Knative Serving available before you deploy a function. See the installation guide and the public surface configuration.
1. Get a tenant-scoped token
The tenant realm name is TENANT_ID, and the public app client is derived from the tenant slug.
export TENANT_TOKEN="$(
curl -sS -X POST \
"$IAM/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
)"Check that the token was issued:
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 "$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 "$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 "$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 "$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 "$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 "$API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID/validate" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq
curl -sS -X POST "$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 "$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 "$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 when you have finished:
curl -sS -X DELETE "$API/v1/functions/actions/$FUNCTION_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jq
curl -sS -X DELETE "$API/v1/flows/workspaces/$WORKSPACE_ID/flows/$FLOW_ID" \
-H "authorization: Bearer $TENANT_TOKEN" \
| jqLocal cluster (operator)
This section applies to a cluster you operate, such as the kind quickstart. Creating the tenant, workspace and tenant user is operator work; complete those quickstart steps first. The public-hostname walkthrough above requires the operator to expose the console and identity endpoints and configure issuer trust for that identity hostname. See the installation guide. The quickstart's local hostnames alone do not establish ingress or issuer/JWKS trust; making them work is a separate deployment task.
For local diagnosis through direct service endpoints, use the following operator-only setup. These endpoints bypass the public-hostname path above.
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:8082The local identity endpoint is http://127.0.0.1:8081. The quickstart assigns tenant and workspace IDs from its creation responses:
export TENANT_ID="$(jq -r '.tenantId' /tmp/falcone-tenant.json)"
export WORKSPACE_ID="$(jq -r '.workspaceId' /tmp/falcone-workspace.json)"