Skip to main content

HTTP and events

This page is the full request/response contract for functions: what your handler's event contains for each kind of request, how your return value becomes the HTTP response, and how event deliveries in the CloudEvents format behave differently from plain HTTP calls.

How a request becomes an event

Every function sits behind a platform-provided web server (the shim). It maps requests to your handler like this:

RequestWhat your handler gets
GET /healthzNothing — the shim answers 200 with {"status": "ok"} itself. This is the platform's health check.
Any other GETAn empty {} event.
POST with a JSON bodyThe parsed JSON body as the event.
POST that is a CloudEventThe event data — see CloudEvents below.

Your handler sees only the JSON body. It does not receive the URL path, the query string, or the request headers. If you need routing across multiple paths, deploy separate functions, or use a serverless container service where you control the whole web server.

The 8 MiB event cap

A POST body may be at most 8 MiB. A larger body is rejected before your handler runs, with status 413:

{"error": "body exceeds 8388608 bytes"}

The Go runtime answers the same 413 with a different body:

{"error": "body too large or unreadable"}

On the Python runtime, a request whose Content-Length header is not a number is rejected with status 400:

{"error": "invalid Content-Length"}

The Node.js, Go, and Ruby shims never parse the Content-Length header — they measure the body as it arrives or after reading it, and only reject it with 413 when it is too large.

(Deploying your function's code is a different limit — up to 100 MiB through the CLI. See limits.)

How your return value becomes the response

  • The whole return value is serialized as a JSON response body.
  • The status defaults to 200. Include a "statusCode" key to change it — {"statusCode": 404, "error": "no such record"} produces an HTTP 404 whose body is that JSON. The runtimes page shows this convention in each language.
  • If your handler throws an exception, the platform catches it and answers 500 (so callers get a real response instead of a dropped connection):
{"error": "handler raised: division by zero"}

The text after handler raised: is your language's own error message — check the function's logs for the full stack trace.

Invoking through the platform

Besides calling the function's URL directly, you can invoke it through the platform's shared invoke path — the same one agents use:

platformctl invoke my-function "some text"

The CLI (and POST /v1/agents/{name}/invoke underneath it) delivers your text to the handler as event["message"]. See invoking agents for the endpoint details.

The invoke path is open by default

Deploying and managing functions always requires authentication, but calling one — the data plane — is open by default in this alpha. A platform operator can require authentication for invocations by setting INVOKE_AUTH_REQUIRED=true on the control plane. See API authentication.

CloudEvents delivery

A CloudEvent is a small industry-standard envelope for event data — "what happened, where, and when" — used by event brokers to deliver messages to subscribers. When the platform's eventing system (or anything else) delivers a CloudEvent to your function, the shim treats it differently from a plain HTTP call.

A request counts as a CloudEvent if either:

  • it carries a Ce-Id header (binary mode — the event data is the JSON body as-is), or
  • its content type is application/cloudevents+json (structured mode — the whole envelope is the body, and your handler receives just its data field).

Three rules change for CloudEvents:

  1. The response is always an empty 204. That is the acknowledgment ("ACK") the event broker expects. Your handler's return value is discarded — the handler runs for its side effects (writing to a database, logging, calling another service), not for its response.
  2. Handler exceptions return 400, not 500. A 400 tells the broker the failure is permanent for this event, so it stops redelivering. (A 5xx would make the broker retry an event that will fail identically every time — an infinite retry storm.) The body is the same shape: {"error": "handler raised: ..."}.
  3. Nothing else changes: the 8 MiB cap still applies, and the event still reaches the same handle(event) function.

Try it with curl

Binary mode — the Ce-Id header marks it as a CloudEvent:

curl -i -X POST "$FN_URL" \
-H 'Ce-Id: 1234' \
-H 'Ce-Specversion: 1.0' \
-H 'Ce-Type: demo.event' \
-H 'Ce-Source: docs-example' \
-H 'Content-Type: application/json' \
-d '{"message": "an event happened"}'

You should see (headers abridged):

HTTP/1.1 204 No Content

Structured mode — the envelope is the body, and the handler receives only the data field (here, {"message": "an event happened"}):

curl -i -X POST "$FN_URL" \
-H 'Content-Type: application/cloudevents+json' \
-d '{"specversion": "1.0", "id": "1234", "type": "demo.event",
"source": "docs-example", "data": {"message": "an event happened"}}'

You should see:

HTTP/1.1 204 No Content

To confirm the handler ran, check the logs: platformctl logs my-function --history.

What about schedules and other triggers?

Functions respond to HTTP and CloudEvents. Trigger objects for schedules (cron), object-storage activity, and Pub/Sub topics live on the Serverless surface, not on functions. For a worked example of wiring events to functions, see the event-driven functions tutorial; for the messaging service itself, see Pub/Sub.

Summary

SituationStatusBody
GET /healthz200{"status": "ok"} (shim answers)
GET anything elseyour statusCode (default 200)your return value as JSON
POST with JSON bodyyour statusCode (default 200)your return value as JSON
POST body over 8 MiB413{"error": "body exceeds 8388608 bytes"} (Go runtime: {"error": "body too large or unreadable"})
Bad Content-Length (Python runtime only)400{"error": "invalid Content-Length"}
Handler exception (plain HTTP)500{"error": "handler raised: ..."}
CloudEvent delivered successfully204empty — return value discarded
Handler exception (CloudEvent)400{"error": "handler raised: ..."} — stops redelivery