HTTP Request and Response Parameter Mapping (Effect)
Effect Golem endpoints declare request bindings with the Http namespace and describe every
method parameter and result with Effect Schema. The Golem host performs the HTTP parsing and
response rendering; handlers receive typed values and return Effect values rather than reading
or constructing raw HTTP requests and responses.
Load golem-add-http-endpoint-effect when the agent still needs a mount, endpoint declarations,
an implementation import in src/main.ts, or an httpApi deployment.
Path Variables
Place constructor identity fields in the mount path and method fields in endpoint paths. Variable
names must exactly match the corresponding id or input keys, including
TypeScript casing:
import { Schema } from "effect";
import { defineAgent, Http, method } from "@golemcloud/effect-golem";
export const TaskAgent = defineAgent({
name: "TaskAgent",
mode: "durable",
id: {
taskName: Schema.String,
},
http: Http.mount("/api/tasks/{taskName}"),
methods: {
getItem: method({
input: { itemId: Schema.String },
success: Item,
http: [Http.get("/items/{itemId}")],
}),
},
});Every agent id field must appear in the mount path. This Effect SDK has no API for binding a mount header to a agent id field. Mount paths also cannot contain query parameters or catch-all variables.
An endpoint catch-all captures the remaining path and must be its final segment:
serveFile: method({
input: { path: Schema.String },
success: FileContent,
http: [Http.get("/files/{*path}")],
}),
// GET .../files/docs/readme.md supplies "docs/readme.md" as path.Query Parameters
Declare query bindings in the endpoint path with key={parameterName}. The HTTP key may differ
from the TypeScript parameter name:
search: method({
input: {
query: Schema.String,
minPrice: Schema.NullOr(Schema.Number),
inStockOnly: Schema.NullOr(Schema.Boolean),
},
success: Schema.Array(Product),
http: [
Http.get(
"/products/search?q={query}&min-price={minPrice}&in-stock-only={inStockOnly}",
),
],
}),For query and header parameters, Schema.NullOr(T) represents an optional value: an omitted value
is supplied as null. Keep path variables non-optional because a matching path always contains
the segment.
Header Variables
The endpoint options map each HTTP header name to one method parameter name:
submitReport: method({
input: {
tenantId: Schema.String,
report: Report,
},
success: Receipt,
http: [
Http.post("/reports", {
headers: { "X-Tenant": "tenantId" } as const,
}),
],
}),Here tenantId comes from X-Tenant, while the unbound report parameter comes from the JSON
body field named report. Header names are case-insensitive for duplicate detection. Query keys
and all parameter names are case-sensitive.
A parameter may be bound only once within an endpoint. Do not bind the same parameter from two path positions, two query keys, two headers, or a mixture of those sources.
Schemas Allowed in Path, Query, and Headers
Route-bound values must use a string-bindable schema:
| Effect Schema | Parsed value |
|---|---|
Schema.String | string |
Schema.Number | JavaScript number / WIT f64 |
Schema.Boolean | boolean |
Schema.BigInt | bigint / WIT s64 |
Schema.Literal(...) or a union of scalar literals | validated literal value |
| scalar template literal, refinement, brand, or transformation | decoded scalar value |
For a string enum, use the Effect v4 union form:
const SortOrder = Schema.Union([
Schema.Literal("ascending"),
Schema.Literal("descending"),
]);Wrap a scalar with Schema.NullOr(...) only for an optional query or header. Structs, tuples,
records, unstructured values, multimodal values, nested collections, and non-empty arrays are not
route-bindable.
Query and header bindings additionally accept Schema.Array(T) when T is one of the supported
primitive, literal/string-enum, template-literal, refinement, brand, or transformation schemas
listed above. The host collects repeated query instances and repeated raw header fields in arrival
order:
collectionBindings: method({
input: {
tags: Schema.Array(Schema.String),
scores: Schema.Array(Schema.Number),
},
success: Schema.Struct({
tags: Schema.Array(Schema.String),
scores: Schema.Array(Schema.Number),
}),
http: [
Http.get("/bindings/collections?tag={tags}", {
headers: { "X-Score": "scores" } as const,
}),
],
}),For example, ?tag=a&tag=b binds tags as ["a", "b"]; three raw X-Score fields bind three
numbers. A single comma-separated header line is one value and is not split into collection items.
Path variables remain scalar-only.
JSON Body Mapping
Use Http.post, Http.put, Http.del, Http.patch, or another bodyful helper when parameters
must come from a body. Every parameter not bound to a path, query key, or header becomes a
top-level field in the structured JSON body, using the exact TypeScript parameter name.
Auto-injected inputs are excluded:
updateItem: method({
input: {
id: Schema.String,
name: Schema.String,
count: Schema.Number,
},
success: Item,
http: [Http.put("/items/{id}")],
}),The request body is:
{ "name": "Widget", "count": 5 }The structured JSON body is an object keyed by body parameter name. A single unbound parameter
decision: Schema.String expects { "decision": "approved" }, not the bare JSON string
"approved". Likewise, input: { item: Item } expects { "item": { ... } }; the fields of the
Item struct are not flattened into the top-level body.
Unstructured text and binary inputs instead consume raw bodies, without a JSON wrapper.
CLI invocation uses a different rule: pass one native
TypeScript literal per user-supplied input field in declaration order, including fields bound to
path/query/header values over HTTP. Do not wrap CLI values in a named input object. Load
golem-invoke-agent-effect for examples and caller-only input rules. Skip host-injected
Principal fields without a principal value or placeholder.
Http.get and Http.head are bodyless, so every parameter of those methods must be bound from
the path, query, or headers. Prefer the standard verb helpers over Http.custom(...) for standard
methods so the SDK can apply its bodyless validation.
Structured Data and JSON
Use schemas that the SDK can lower to WIT:
| Effect Schema | JSON representation |
|---|---|
Schema.String | string |
Schema.Number | number |
Schema.Boolean | boolean |
Schema.Array(T) | array |
Schema.Struct({ ... }) | object with the declared field names |
Schema.Tuple([...]) | array |
Schema.NullOr(T) | value or null |
| union of string literals | string enum value |
| tagged union | tagged variant object |
Use Schema.Struct for fixed object fields. Open-ended Schema.Record(...) index signatures are
not supported by the pinned Effect SDK’s WIT schema compiler. Keep transformations and refinements
serializable through their encoded schemas.
HTTP Response Mapping
Declare the HTTP outcome through the method’s success and optional error schemas:
| Method contract and handler result | HTTP response |
|---|---|
success: Schema.Void, return Effect.void | 204, empty body |
success: T, return Effect<T> | 200, JSON T |
success: UnstructuredText(...), return inline text | 200, plain text |
success: UnstructuredBinary(...), return inline bytes | 200, raw bytes |
unstructured success, return { _tag: "url", val } | 307 redirect to val |
success: Schema.NullOr(T), return T | 200, JSON T |
success: Schema.NullOr(T), return null | 404, empty body |
success: Unstructured.Option(element), return Option.some(ref) | 200, raw body |
success: Unstructured.Option(element), return Option.none() | 404, empty body |
declare error: E, return Effect.fail(E) | 500, JSON E |
A declared error: E wraps any success in result<_, E>: Schema.NullOr(T) or
Unstructured.Option(...) with error: E gives 200 / 404 / 500 (the result<option<T>, E>
mapping).
Use Schema.NullOr(T) for ordinary not-found behavior. Use a declared error schema and
Effect.fail(...) for expected typed failures. Unhandled defects such as Effect.die(...) fail
the invocation without retry. Do not use defects to select an HTTP status or to repeat completed calls.
The SDK does not expose a raw response builder or per-error status mapping. Do not return an
Effect Platform HttpServerResponse or invent response-header/status helpers.
For plain text or binary output, set method success to
Unstructured.UnstructuredText(...) or Unstructured.UnstructuredBinary(...) and return an
inline TextReferenceValue ({ _tag: "inline", val: string, languageCode? }) or
BinaryReferenceValue ({ _tag: "inline", val: Uint8Array, mimeType? }). Inline text uses
text/plain plus its optional languageCode as Content-Language; inline binary uses its
mimeType (default application/octet-stream). Wrap the element in
Unstructured.Option(...) for an optional raw success, and declare error: E for a typed error;
the value inside some / ok is still sent as the raw body, not as JSON:
const download = method({
input: { id: Schema.String },
// 200 raw bytes / 404 for Option.none() / 500 with JSON `E` for Effect.fail(e)
success: Unstructured.Option(Unstructured.UnstructuredBinary()),
error: Schema.Struct({ _tag: Schema.Literal("Pending") }),
http: [Http.get("/files/{id}")],
});Load golem-add-http-endpoint-effect for complete value and handler examples.
Malformed Bound Input
The host rejects malformed route-bound input before invoking the handler. The response is 400 Bad Request, has a JSON content type, and has this stable shape:
{ "code": "REQUEST_VALUE_PARSING_FAILED", "errors": ["descriptive message"] }The stable codes distinguish common cases:
| Case | Code |
|---|---|
| Missing required query or header value | REQUEST_MISSING_VALUE |
| Malformed scalar or unparsable collection item | REQUEST_VALUE_PARSING_FAILED |
| More than one value for a scalar binding | REQUEST_TOO_MANY_VALUES |
Do not duplicate this parsing in the handler or replace these host responses with custom response construction.
Unstructured Request Bodies
The Effect SDK exposes top-level unstructured method parameters under the Unstructured
namespace:
import { Effect, Schema } from "effect";
import { Http, method, Unstructured } from "@golemcloud/effect-golem";
const upload = method({
input: {
payload: Unstructured.UnstructuredBinary({
restrictions: [{ mimeType: "image/png" }],
}),
},
success: Schema.Number,
http: [Http.post("/upload")],
});
// In the implementation:
const uploadHandler = ({
payload,
}: {
payload: Unstructured.BinaryReferenceValue;
}) =>
Effect.succeed(payload._tag === "inline" ? payload.val.byteLength : -1);Use Unstructured.UnstructuredText({ restrictions: [{ languageCode: "en" }] }) for a restricted
text input, or omit restrictions to accept any declared text language or binary MIME type. An
unstructured value cannot bind to path, query, or headers; keep it as the only body parameter.
method.success accepts an ordinary Effect Schema, one top-level UnstructuredBinary(...) /
UnstructuredText(...) element specification, or Unstructured.Option(element) (success only,
not an input). Any of them combines with a typed error. Unstructured values cannot be nested
inside Schema.Struct or combined into a multimodal success value.
Authenticated Principal
Read the authenticated caller from the normal Effect Context service:
import { Effect } from "effect";
import { Principal } from "@golemcloud/effect-golem";
const currentCaller = Effect.gen(function* () {
return yield* Principal.Principal;
});Enable authentication with Http.mount(path, { auth: true }) or an endpoint auth option, then
read Principal.Principal inside the handler for the current invocation. Load
golem-add-http-auth-effect for deployment security configuration and principal variants.
Explicit PrincipalSchema method input fields are host-injected, not HTTP body fields.
SDK clients omit them from the named input object, and CLI callers skip them in declaration
order without a principal value or placeholder. The host supplies them to handlers.
Key Constraints
- Import Effect APIs from
effectand Golem APIs from@golemcloud/effect-golem. - Match every placeholder and header target to an exact
inputoridkey. - Use
Schema.NullOr(...)andnullfor optional query/header values and 404 success results; useUnstructured.Option(...)andOption.none()for an optional raw success. - Keep GET/HEAD parameters fully bound. Ordinary structured bodies use named JSON fields; unstructured text and binary inputs use raw bodies.
- Return Effects from handlers; do not use plain values,
asynchandlers, or raw HTTP middleware. - Do not use
@golemcloud/golem-ts-sdkdecorators or invent APIs from another language SDK.