Functions quickstart
In about five minutes you will write a one-file Python function, deploy it with one command, call it three ways (CLI, public URL with GET, public URL with POST), read its logs, and delete it. Cost while you follow along: effectively zero — a function that nobody is calling scales to zero and uses no compute.
Before you begin
- You have the
platformctlCLI installed. See install the CLI. - You are signed in (
platformctl login) and have a default project set (platformctl config set-project my-project). - The examples below use
ab12cdas the project "short" — the six-character ID that appears in your public URLs. Yours will differ;platformctl projects listshows it.
The platform is in alpha and has no public API hostname yet. platformctl reaches the API through the $CAI_API environment variable, or by opening a port-forward to the cluster automatically when you have a KUBECONFIG. Your function can also get a real internet-facing HTTPS address, but only after you publish it in step 4 — and public DNS and TLS are configured per install, so ask your administrator if the address never answers.
Step 1: Write the function
Create a directory with a single file, handler.py. The function echoes back whatever message it receives:
mkdir hello-fn
cat > hello-fn/handler.py <<'EOF'
def handle(event):
print(f"got event: {event}")
return {"echo": event.get("message", "")}
EOF
That is the whole app. handle(event) takes a dictionary in and returns a dictionary out; the platform turns the return value into a JSON HTTP response. The print line goes to the function's logs, which you will read in step 5.
Step 2: Deploy it
platformctl functions deploy ./hello-fn --name hello-fn
You should see:
packaging ./hello-fn...
uploading hello-fn (0.2 KiB, framework=function)...
build b-1a2b3c accepted
status: -> building
status: building -> deploying
status: deploying -> ready
hello-fn is ready at http://hello-fn.cai-p-ab12cd.svc.cluster.local
The deploy is asynchronous: the platform accepts your code (that is the build ... accepted line), builds a container image from it, and starts it. The CLI polls every 2 seconds, for up to 5 minutes, and prints each status change. The runtime was auto-detected as Python because the directory contains handler.py.
Step 3: Invoke it from the CLI
platformctl invoke hello-fn "ping"
You should see a blank line and a session id:
(session: 3f6c1f0e-9c1c-4f1a-8f2e-2a0d5f6b7c81)
platformctl invoke sends your text to the function as event["message"] — that is why the handler reads that key. The blank line is expected: invoke prints only the response's output field, which is how an agent answers, and a function's return value has no output key. -o json does not help either — it prints the same reduced {session_id, response, tool_calls} shape, still with no function output. To see the {"echo": "ping"} your handler actually returned, call its URL, as in step 4.
Step 4: Call the public URL
Start with the CLI:
platformctl status hello-fn -o json
You should see:
{
"name": "hello-fn",
"framework": "function",
"status": "ready",
"url": "http://hello-fn.cai-p-ab12cd.svc.cluster.local",
"image": "localhost:30500/hello-fn@sha256:9f3c1a2...",
"created_at": "2026-08-12T14:02:11Z",
"updated_at": "2026-08-12T14:03:04Z"
}
That url is the internal cluster address, not the public one. platformctl status decodes the API response into a struct that has no field for the public URL, so it cannot show you one. Ask the API directly instead (or read it from the function's page in the console):
export CAI_API=http://localhost:8081 # or the endpoint your administrator gave you
export CAI_PROJECT=my-project # id, slug, or short — platformctl projects list shows all three
curl -s "$CAI_API/v1/agents/hello-fn?project=$CAI_PROJECT" \
-H "Authorization: Bearer $CAI_TOKEN" | jq -r .public_url
You should see:
https://hello-fn-ab12cd.apps.codyhill.dev
$CAI_TOKEN is an API key or a session token — see API authentication for where to get one.
That address is the one your function would have, but nothing serves it yet: workloads are private by default, reachable only through the platform. Publish the function by setting CAI_EXPOSE_EXTERNAL on its env, which rolls a new revision:
curl -s -X PATCH "$CAI_API/v1/agents/hello-fn/env?project=$CAI_PROJECT" \
-H "Authorization: Bearer $CAI_TOKEN" \
-H 'content-type: application/json' \
-d '{"set": {"CAI_EXPOSE_EXTERNAL": "true"}}'
You should see:
{"agent": "hello-fn", "env_updated": true, "env": {"CAI_EXPOSE_EXTERNAL": "true"}}
Wait for platformctl status hello-fn to read ready again, then copy the public URL into an environment variable and call it. A GET request reaches your handler with an empty event:
export FN_URL="https://hello-fn-ab12cd.apps.codyhill.dev" # your public_url from above
curl -s "$FN_URL"
You should see:
{"echo": ""}
A POST with a JSON body delivers that body to your handler as the event:
curl -s -X POST "$FN_URL" \
-H 'Content-Type: application/json' \
-d '{"message": "hello from the internet"}'
You should see:
{"echo": "hello from the internet"}
The platform also answers a health check for you — your handler is never called for this path:
curl -s "$FN_URL/healthz"
You should see:
{"status": "ok"}
Leave the function alone for a couple of minutes, then call it again. The first request takes a few seconds longer — that is a cold start: the platform had stopped your idle function and is starting a fresh copy. See autoscaling and scale to zero for how the timing works.
Step 5: Read the logs
A scaled-to-zero function has no running copy to tail, so use --history, which reads persisted log lines that survive scale-to-zero:
platformctl logs hello-fn --history
You should see lines like:
2026-08-12T14:03:05Z stdout got event: {'message': 'ping'}
2026-08-12T14:05:41Z stdout got event: {}
2026-08-12T14:06:02Z stdout got event: {'message': 'hello from the internet'}
Those are the print calls from your handler — one per request you made. (Plain platformctl logs hello-fn tails the live copy instead, which only works while one is running.)
Clean up
One command removes the function and releases its URL:
platformctl delete hello-fn
You should see:
deleted hello-fn
Next steps
- Runtimes — write the same function in Node.js, Go, or Ruby, and control the HTTP status code.
- HTTP and events — the full request contract, including CloudEvents.
- Troubleshooting — what the real error messages mean.