Skip to Content
Next — You're reading the unreleased preview docs. APIs and pages may change before the next release.Switch to the current docs →
How-To GuidesGoHTTP Request and Response Parameter Mapping (Go)

HTTP Request and Response Parameter Mapping (Go)

Overview

When a Go agent is exposed over HTTP (see golem-add-http-endpoint-go), the platform maps parts of each request onto the agent’s constructor (ID) fields and the method’s input struct fields. This skill covers how path segments, query parameters, headers, and the request body bind to those fields, which Go types each binding accepts, and how return types map to HTTP responses.

Binding is metadata only and declared on the definition: the mount Path on Spec.HTTP and the golem.HTTP(...) option on each method. There is no incoming-request handler in the guest.

All binding and body rules below apply only to user-supplied fields. The host injects golem.Principal fields in ID and method inputs; exclude them from request bodies and path/query/header bindings. Explicit principal bindings are rejected.

Steps

  1. Name fields to match wire names. A path/query/header variable refers to an input field by its schema name: the Go field name in lowerCamelCase with each initialism as one word, so MaxResults is maxResults, SKU is sku, UserID is userId and APIKey is apiKey.
  2. Bind path variables by putting {name} in the mount path (binds ID fields) or an endpoint path suffix (binds method input fields).
  3. Bind query and header values inline (?name={field}) or with golem.Query / golem.Header.
  4. Leave body fields unbound on body-carrying verbs; they arrive as JSON.

Path variables

{var} segments bind by schema name.

  • Mount path {var}s bind the agent’s user-supplied constructor (ID) fields. Every user-supplied ID field must appear as a {var}, excluding auto-injected golem.Principal, so a request selects one instance.
  • Endpoint path {var}s bind the method’s input fields.
type ID struct{ Name string } // bound by {name} in the mount type LookupIn struct { SKU string Detailed bool } var Agent = golem.DefineAgent[ID](golem.Spec{ Name: "CatalogAgent", HTTP: &golem.Mount{Path: "/catalog/{name}"}, // {name} → ID.Name }) // GET /catalog/{name}/items/{sku} — {sku} → LookupIn.SKU var Lookup = Agent.Method[LookupIn, ItemInfo]("lookup", golem.HTTP(golem.GET("/items/{sku}", golem.Query("detailed", "detailed"))))

The trailing catch-all {*rest} captures everything after a prefix and may only appear as the last endpoint segment; it is not allowed in a mount path.

Query parameters

Two equivalent forms. Inline ?name={field} in the path suffix, or golem.Query(wire, field) when the wire name differs from the field:

type SearchIn struct { Query string MaxResults uint64 } // GET /catalog/{name}/search?q=hello&limit=10 var Search = Agent.Method[SearchIn, []Result]("search", golem.HTTP(golem.GET("/search", golem.Query("q", "query"), // ?q= → SearchIn.Query golem.Query("limit", "maxResults"), // ?limit= → SearchIn.MaxResults )))

Inline golem.GET("/search?q={query}&limit={maxResults}") is equivalent when the parameter name matches the field’s wire name.

Header variables

golem.Header(wire, field) maps a request header to an input field:

type DataIn struct { RequestID string Token string } var GetData = Agent.Method[DataIn, Data]("getData", golem.HTTP(golem.GET("/data", golem.Header("X-Request-Id", "requestId"), golem.Header("Authorization", "token"), )))

Header names are matched case-insensitively; binding the same header twice on one endpoint is a definition error.

Supported types for path, query, and header bindings

A value bound from a URL path/query/header string must decode into a scalar Go field. Only these kinds bind:

Go typeBound from
stringused as-is
bool"true" / "false"
int8, int16, int32, int64signed integer
uint8, uint16, uint32, uint64unsigned integer
float32, float64floating-point number

Binding a non-scalar field (struct, slice, map) from path/query/header is a definition error: “cannot bind input field … only scalar fields decode from a URL”. Such fields must arrive in the body instead.

Prefer sized integers throughout. Bare int/uint are rejected at the agent-schema level because their width is platform-dependent (see golem-add-agent-go).

Request body mapping

On body-carrying verbs (POST, PUT, DELETE, PATCH, …), user-supplied input fields not bound to a path/query/header binding are body fields. Ordinary structured fields are populated from the JSON request body:

type UpdateIn struct { SKU string // from the path Name string // from the body Count uint64 // from the body } // POST /catalog/{name}/items/{sku} Body: {"name":"Widget","count":"5"} var Update = Agent.Method[UpdateIn, Item]("update", golem.HTTP(golem.POST("/items/{sku}")))

Each ordinary structured body field becomes a top-level key in the JSON body object (keyed by schema name, as elsewhere in the SDK).

⚠️ Important: Ordinary structured bodies use a JSON object with body field names only as keys, even for one field. A DecideIn struct{ Decision string } expects {"decision": "approved"}, not a bare string. A single struct field named Payload also needs wrapping: {"payload": {"name": "Widget", "count": "5"}}, not {"name": "Widget", "count": "5"} (when count is uint64). Do not include path/query/header fields. Unstructured text and binary fields instead consume the raw body; see below.

CLI positional invocation is different: pass one argument per user-supplied input field in declaration order, including fields bound to path/query/header values over HTTP. Skip golem.Principal fields without a principal value or placeholder. Use a Go literal for each value, not one JSON object wrapping the full input. A single struct field is one composite literal without the HTTP payload wrapper. Load golem-invoke-agent-go for CLI examples and caller-only input rules.

GET and HEAD are bodyless: they carry no request body, so they must bind every user-supplied input field from path/query/header, excluding auto-injected golem.Principal. An unbound user-supplied field on a bodyless verb is a definition error.

Return type to HTTP response mapping

Handlers return their output value; the platform maps it to a response. In Go:

Return typeHTTP statusBody
golem.Unit204 No Contentempty
T (any type)200 OKJSON-serialized T
*T200 OK if non-nil, 404 Not Found if nilJSON T or empty
golem.Result[T, E]200 OK on Ok, 500 Internal Server Error on ErrJSON T or JSON E
golem.Result[*T, E]200 OK on Ok with a value, 404 Not Found on Ok(nil), 500 on ErrJSON T, empty, or JSON E
golem.UnstructuredBinary[M]200 OKraw bytes with Content-Type (default application/octet-stream)
golem.UnstructuredText[L]200 OKtext/plain; charset=utf-8 (plus Content-Language when set)
an unstructured value with URL set307 Temporary RedirectLocation = the URL

golem.Unit is the empty result placeholder; *T is Golem’s option<T> (a nil pointer means “absent”, as does golem.Option[T]); golem.Result[Ok, Err] returns the error as an observable value (see golem-add-agent-go on returning failures).

The value inside an option or an Ok follows the same rules as a top-level value. An unstructured value nested in *T, golem.Result[T, E] or golem.Result[*T, E] is sent as the raw body (or a redirect), not as JSON:

// 200 raw bytes / 404 when missing / 500 with a JSON DownloadError var Download = Agent.Method[DownloadIn, golem.Result[*golem.UnstructuredBinary[golem.AnyMimeType], DownloadError]]( "download", golem.HTTP(golem.GET("/files/{id}")))

Data type to JSON mapping

Agent HTTP routes use the platform’s schema JSON renderer, not Go’s encoding/json. Do not assume that SDK canonical JSON or the native public invocation-session JSON envelope is interchangeable with an HTTP body.

Go typeJSON
stringstring
int8, int16, int32, uint8, uint16, uint32number (integer)
int64, uint64decimal string, for example "5"
float32, float64number
boolboolean
[]Tarray
structobject keyed by schema names (MaxResults → maxResults, UserID → userId)
*Tvalue or null
map[K]Varray of key/value pairs, for example [["key","5"]] for map[string]uint64

The native public invocation-session JSON contract instead encodes that map as {"kind":"map","value":{"entries":[[{"kind":"string","value":"key"},{"kind":"u64","value":"5"}]]}}: every key and value is a tagged node too. That wrapper is not the ordinary agent HTTP route representation. Use the contract for the boundary being called; a Go JSON object such as {"key":5} is not either map representation.

Key Constraints

  • A variable can bind an input field from exactly one of path/query/header — binding it more than once is a definition error.
  • Bindable path/query/header fields must be scalar; non-scalars go in the body.
  • Bodyless verbs (GET, HEAD) must bind every user-supplied input field, excluding auto-injected golem.Principal.
  • Field references on the wire use the schema name, the field name in lowerCamelCase with each initialism as one word (SKU → sku, RequestID → requestId).
  • A raw (non-JSON) body is a single body field of type golem.UnstructuredBinary[M] or golem.UnstructuredText[L]; M/L is golem.AnyMimeType/golem.AnyLanguage or a marker type whose MimeTypes()/Languages() lists what is accepted. Any other body is a JSON object keyed by the schema names.
GuideDescription
golem-add-http-endpoint-goThe high-level workflow of mounting an agent and adding routes
golem-add-http-auth-goRequire authentication on endpoints
golem-add-cors-goAllow cross-origin requests
golem-add-agent-goDefine the agent and its input/output types