Skip to Content
How-To GuidesRustAdding HTTP Endpoints to a Rust Golem Agent

Adding HTTP Endpoints to a Rust Golem Agent

Overview

Golem agents can be exposed over HTTP using code-first route definitions. This involves:

  1. Adding a mount parameter to #[agent_definition]
  2. Annotating trait methods with #[endpoint(...)]
  3. Adding an httpApi deployment section to golem.yaml (See the golem-configure-api-domain guide)
GuideDescription
golem-http-params-rustPath/query/header variable mapping, body mapping, supported types, response mapping
golem-make-http-request-rustMaking outgoing HTTP requests from agent code, especially when calling other Golem agent endpoints (required for correct JSON body formatting)
golem-add-http-auth-rustEnabling authentication
golem-add-cors-rustConfiguring CORS allowed origins
golem-configure-api-domainSetting up httpApi in golem.yaml, security schemes, domain deployments, and subdomain versus domain choices

Steps

  1. Add mount = "/path/{param}" to #[agent_definition(...)]
  2. Add #[endpoint(get = "/...")] (or post, put, delete) to trait methods
  3. Add httpApi deployment to golem.yaml (see golem-configure-api-domain guide)
  4. Build and deploy

Mount Path

The mount parameter on #[agent_definition] defines the base HTTP path. Path variables in {braces} map to constructor parameters:

use golem_rust::{agent_definition, agent_implementation, endpoint}; #[agent_definition(mount = "/api/tasks/{task_name}")] pub trait TaskAgent { fn new(task_name: String) -> Self; // methods... }

Rules:

  • Path must start with /
  • Every constructor parameter must appear as a {variable} in the mount path (using the parameter name)
  • Every {variable} must match a constructor parameter name
  • Catch-all {*rest} variables are not allowed in mount paths

Endpoint Annotation

The #[endpoint(...)] attribute marks a trait method as an HTTP endpoint. Specify one HTTP method with its path:

#[endpoint(get = "/items")] fn list_items(&self) -> Vec<Item>; #[endpoint(post = "/items")] fn create_item(&mut self, name: String, count: u64) -> Item; #[endpoint(put = "/items/{id}")] fn update_item(&mut self, id: String, name: String) -> Item; #[endpoint(delete = "/items/{id}")] fn delete_item(&mut self, id: String);

Endpoint paths are relative to the mount path. A method can have multiple #[endpoint(...)] attributes to expose it under different routes.

For details on how path variables, query parameters, headers, and request bodies map to method parameters, See the golem-http-params-rust guide.

Durable Stream Route Customization

For a method with stream inputs or outputs, add durable_streams(...) inside #[endpoint(...)] to customize its Durable Streams HTTP surface:

#[endpoint( post = "/events", durable_streams( input("input", name = "uploads"), output( "$result", name = "events", content_type = "application/vnd.example.events" ), allow_external_writes = true, allow_stream_delete = false, allow_invocation_delete = false, max_concurrent_readers_per_stream = 8, max_append_requests_per_second_per_stream = 25, ) )] fn events(&mut self, input: AgentStream<u8>) -> AgentStream<u8>;
  • input and output select canonical top-level stream slots. $result is the only implicit result selector.
  • name changes the public URL and OpenAPI name; the canonical name is no longer accepted in the public URL.
  • content_type is allowed only for a direct AgentStream<u8> slot and must be a concrete non-text, non-JSON MIME type without parameters or wildcards. JSON-shaped streams stay application/json; do not use text/plain for AgentStream<String>. SSE still uses text/event-stream and base64 data.
  • The three allow_* options default to true. Setting one to false removes that protocol operation and produces 405 with an exact Allow header.
  • The live-reader limit is 1 through 16 and applies to long-poll and SSE. The append limit must be positive and cannot be set when external writes are disabled. A route-local limit rejection is 429 with Retry-After: 1.
  • Limits are maintained per route and worker-service node, not as a cluster-wide quota.

Phantom Agents

Set phantom_agent = true to create a new agent instance for each HTTP request, enabling fully parallel processing:

#[agent_definition(mount = "/gateway/{name}", phantom_agent = true)] pub trait GatewayAgent { fn new(name: String) -> Self; // Each HTTP request gets its own agent instance }

Custom Types

All types used in endpoint parameters and return values must derive Schema:

use golem_rust::Schema; #[derive(Clone, Schema)] pub struct Task { pub id: String, pub title: String, pub done: bool, }

Return Type to HTTP Response Mapping

Golem maps method return types to HTTP status codes and response bodies according to the table below. This mapping is currently not configurable.

Return TypeHTTP StatusResponse Body
() (unit / no return)204 No Contentempty
T (any type)200 OKJSON-serialized T
Option<T>200 OK if Some, 404 Not Found if NoneJSON T or empty
Result<T, E>200 OK if Ok, 500 Internal Server Error if ErrJSON T or JSON E
Result<(), E>204 No Content if Ok, 500 if Errempty or JSON E
Result<T, ()>200 OK if Ok, 500 if ErrJSON T or empty
UnstructuredBinary<M>200 OKRaw binary with Content-Type

Complete Example

use golem_rust::{agent_definition, agent_implementation, endpoint, Schema}; #[derive(Clone, Schema)] pub struct Task { pub id: String, pub title: String, pub done: bool, } #[derive(Schema)] pub struct ErrorResponse { pub error: String, } #[agent_definition(mount = "/task-agents/{name}")] pub trait TaskAgent { fn new(name: String) -> Self; #[endpoint(get = "/tasks")] fn get_tasks(&self) -> Vec<Task>; #[endpoint(post = "/tasks")] fn create_task(&mut self, title: String) -> Task; #[endpoint(get = "/tasks/{id}")] fn get_task(&self, id: String) -> Option<Task>; #[endpoint(post = "/tasks/{id}/complete")] fn complete_task(&mut self, id: String) -> Result<Task, ErrorResponse>; } struct TaskAgentImpl { name: String, tasks: Vec<Task>, } #[agent_implementation] impl TaskAgent for TaskAgentImpl { fn new(name: String) -> Self { Self { name, tasks: vec![] } } fn get_tasks(&self) -> Vec<Task> { self.tasks.clone() } fn create_task(&mut self, title: String) -> Task { let task = Task { id: format!("{}", self.tasks.len() + 1), title, done: false, }; self.tasks.push(task.clone()); task } fn get_task(&self, id: String) -> Option<Task> { self.tasks.iter().find(|t| t.id == id).cloned() } fn complete_task(&mut self, id: String) -> Result<Task, ErrorResponse> { match self.tasks.iter_mut().find(|t| t.id == id) { Some(task) => { task.done = true; Ok(task.clone()) } None => Err(ErrorResponse { error: "not found".to_string() }), } } }
# golem.yaml (add to existing file) httpApi: deployments: local: - subdomain: my-app # resolves to my-app.localhost:9006 by default agents: TaskAgent: {}

Key Constraints

  • A mount path is required on #[agent_definition] before any #[endpoint] attributes can be used
  • All constructor parameters must be provided via mount path variables
  • Path/query/header variable names must exactly match method parameter names
  • Catch-all path variables {*name} can only appear as the last path segment
  • The endpoint path must start with /
  • Exactly one HTTP method must be specified per #[endpoint] attribute
  • All custom types used in parameters or return values must derive Schema