Quickstart: deploy your first function
A function is the smallest thing you can deploy: one file with one Python function in it, served as an HTTPS endpoint that scales to zero when idle. In this quickstart you'll write it, deploy it with the CLI, call it two ways, and delete it. Total time: a few minutes.
Before you begin
- A platform account and a project — see Create an account.
platformctlbuilt, signed in, and pointed at your project — see Install the CLI.
1. Write the function
A Python function is a file named handler.py that defines handle(event). The event is a dictionary built from the incoming request; whatever dictionary you return becomes the JSON response.
mkdir hello-http && cat > hello-http/handler.py <<'EOF'
def handle(event):
who = event.get("name") or event.get("message") or "world"
return {"greeting": f"Hello, {who}!"}
EOF
That's the whole app. No web framework, no server code, no Dockerfile.
2. Deploy it
platformctl functions deploy ./hello-http --name hello-http
You should see:
packaging ./hello-http...
uploading hello-http (0.3 KiB, framework=function)...
build 7c41d9e2-... accepted
status: -> building
status: building -> deploying
status: deploying -> ready
hello-http is ready at http://hello-http.cai-p-ab12cd.svc.cluster.local
The runtime was auto-detected as Python because the folder contains handler.py. Node.js, Go, and Ruby functions use handler.js, handler.go, and handler.rb, but the CLI cannot deploy them yet — see Runtimes for the API call that can.
3. Call it through the platform
platformctl invoke hello-http "ping"
You should see a blank line and a session id:
(session: 3f6c1f0e-9c1c-4f1a-8f2e-2a0d5f6b7c81)
platformctl invoke wraps your text as {"message": "ping"} before delivering it, which is why the handler reads the message key. The blank line is expected: invoke prints only the response's output field, the way an agent answers, and a function's return value has no output key. To see the {"greeting": "Hello, ping!"} your handler returned, call the function's URL, as in step 4.
4. Call it at its public URL
Every workload has a canonical public address of the form https://<name>-<project-short>.apps.codyhill.dev. The CLI does not print it — platformctl status -o json round-trips only the fields on its own agent struct:
platformctl status hello-http -o json
{
"name": "hello-http",
"framework": "function",
"status": "ready",
"url": "http://hello-http.cai-p-ab12cd.svc.cluster.local",
"image": "localhost:30500/hello-http@sha256:9f3c1a2..."
}
kind, runtime, and public_url are in the API's response but are dropped when the CLI decodes it. Ask the API directly instead:
export CAI_API=http://localhost:8081 # or the endpoint your administrator gave you
curl -s "$CAI_API/v1/agents/hello-http?project=$CAI_PROJECT" \
-H "Authorization: Bearer $CAI_TOKEN" | jq -r .public_url
You should see:
https://hello-http-ab12cd.apps.codyhill.dev
Or read it from the function's page in the console.
Workloads are private by default — cluster-local, reachable only through the platform — so that address serves nothing until you publish the function. Publish it by setting CAI_EXPOSE_EXTERNAL on its env, which rolls a new revision:
curl -s -X PATCH "$CAI_API/v1/agents/hello-http/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-http", "env_updated": true, "env": {"CAI_EXPOSE_EXTERNAL": "true"}}
Once platformctl status hello-http reads ready again, call the function directly — a POSTed JSON body becomes the event dictionary:
export FUNC_URL=https://hello-http-ab12cd.apps.codyhill.dev # your public_url from the curl above
curl -s "$FUNC_URL" -H 'content-type: application/json' -d '{"name":"world"}'
You should see:
{"greeting": "Hello, world!"}
There's also a built-in health check:
curl -s "$FUNC_URL/healthz"
You should see:
{"status": "ok"}
First check that you published it, as above — an unpublished function is cluster-local and its canonical address serves nothing.
The API response for a function (GET /v1/agents/{name}) has two public URL fields: public_url is the canonical address, returned even before the function is published, while external_url appears only once the endpoint is actually reachable over a valid certificate on your install. platformctl status surfaces neither — its output carries only url, the internal cluster address — so query the API or check the console for them. If external_url stays empty after you publish, keep using platformctl invoke and ask your administrator about public endpoints. Alpha honesty: public DNS and TLS are per-install, not guaranteed.
Useful details for later:
- Return
{"statusCode": 404}(plus any other keys) from your handler to control the HTTP status; the default is 200. - A plain
GETto the function calls your handler with an empty{}event. - POST bodies are capped at 8 MiB.
- Functions can also receive events instead of HTTP calls — see HTTP and events.
5. Clean up
platformctl delete hello-http
You should see:
deleted hello-http
Next steps
- Functions overview — when to reach for a function versus an agent or a service.
- Runtimes — Python, Node.js, Go, and Ruby handler contracts.
- HTTP and events — CloudEvents, status codes, and request shapes.
- Quickstart: deploy your first agent — the five-minute agent version of this page.