Make Custom HTTP APIs for your Golem App
Golem's Gateway service not only exposes the low-level REST API for invocations but also provides a way to expose the agent methods as HTTP APIs.
Future versions will support other protocols such as MCP, gRPC, or GraphQL.
HTTP API definition
The HTTP APIs are defined in the application manifest (golem.yaml) httpApi section. Every template provided by Golem contains a sample API definition that can be used as a starting point.
The httpApi section consists of two main parts:
- definitions contains the versioned API definitions describing routes and their handlers
- deployments specifies which sets of these definitions are exposed on which hosts
Definitions
Each API definition has a name and a version; multiple versions of the same API can be deployed simultaneously if needed, for example for backward compatibility.
The primary part of an API definition is the list of routes. Each route matches an incoming HTTP request and performs a specific action, most commonly invoking an agent method.
The following example defines an API called counter-api with version 0.0.1:
httpApi:
definitions:
counter-api:
version: '0.0.1'
routes:
# - method: GET
# ...Routes calling agents
The most common type of route is one that invokes an agent method. A route consists of an HTTP method and path, with optional variables in it, and a Rib script. The script's job is invoking an agent's method by taking information from the request to assemble the agent ID and the method's parameters, and constructing an HTTP response from the method's response. This allows a lot of flexibility in mapping the agent to an HTTP API.
The routes field is a list of routes, each consisting of:
method: one of GET, POST, DELETE, PUT, PATCH, HEAD, CONNECT, OPTIONS, TRACEpath: the path part of the URL, which may contain variables in{}bracesbindings: describes how to handle the request, for example by invoking an agent method
The bindings field has several subfields:
type: default: indicates that this route invokes an agent method; other types are documented belowcomponentName: the name of the component containing the agent to invokeresponse: the Rib script that handles the request- optionally
idempotencyKey: a Rib script that computes an idempotency key for the request - optionally
invocationContext: a Rib script that computes an invocation context for the request
To learn more about the Rib scripting language, see the Rib page.
Response script
The response script has access to the request object, which allows access to details of the HTTP request:
request.path.xyzrefers to a path variable namedxyzrequest.headers.xyzrefers to a header namedxyzrequest.bodyrefers to the JSON request body - it can be inspected by accessing its fields such asrequest.body.exampleFieldetc.request.authwhen authentication is configured (see below)request.request_id.valueis a string containing a UUID that is unique for each incoming request. It can be used with ephemeral agents to provide a unique identity for each request by defining arequestIdstring parameter for the ephemeral agent's constructor, and passing this value from Rib.
The script can construct an agent reference for any of the agent types defined in the component identified by componentName by using the agent type name and providing the necessary constructor parameters. For example if there is an agent type counter-agent with a single string constructor parameter, the following Rib snippets are constructing an instance of it:
let agent = counter-agent("test");or
let agent = counter-agent(request.path.user);This agent variable then can be used to invoke an agent method, for example:
let new-value = agent.increment();The script must return an object with at least a status and a body field:
{ status: 200u64, body: "Hello World" }Additionally, this response object can also have a headers field, which is a map of header names to values.
For example, it is possible to modify the response content type by adding a Content-Type header:
{ status: 200u64, body: "Hello World", headers: { Content-Type: "text/plain" } }The next example reads a custom header from the requests, and uses it as another custom header that is added to the response:
let customer-id: string = request.headers.X-Customer-ID;
{
status: 200u64,
body: "The customer id header was ${customer-id}",
headers: {
Content-Type: "text/plain",
X-Request-Customer-ID: customer-id
}
}Example
The following example defines an increment endpoint for the example used on the Quickstart page:
httpApi:
definitions:
counter-api:
version: '0.0.2'
routes:
- method: POST
path: /{name}/increment
binding:
type: default
componentName: "example:counter"
response: |
let name: string = request.path.name;
let agent = counter-agent(name);
let new-value = agent.increment();
{ status: 200u64, body: { result: "incremented ${name}, new value is ${new-value}" } }Note that when the content type header is not specified, the body is automatically serialized as JSON and the Content-Type: application/json header is added to the response.
Non-JSON request or response bodies
Currently Rib only supports JSON request bodies. The request.body field is always a parsed JSON object, allowing access to its fields. Arbitrary payload can be only sent as part of the JSON object, for example by encoding it as a base64 string.
The response body's content type can be customized by adding a Content-Type header to the response object, and it also depends on the request's Accept header, if any. Depending on the accept and content type headers, different values returned in the response object's body field have different outcomes:
- By default, if no
AcceptorContent-Typeheader is present, the resulting content type isapplication/json, and thebodyfield can be any structured value, that is going to be serialized as JSON. - One exception to the above rule is if the value returned as the response body is an array of bytes. In this case the content type is set to
application/octet-streamand the byte array is sent as-is in the response body. - If there is an explicit
Content-Typeheader, or if theAcceptheader in the request requires a specific content type, then the following rules apply:- If the content type is
application/json, then thebodyfield can be any structured value, that is going to be serialized as JSON. - If the content type is
text/plain, then thebodyfield must be a string, which is sent as-is in the response body. - If the content type is anything else, then the
bodyfield must be an array of bytes, which is sent as-is in the response body.
- If the content type is
Optional idempotency key script
In Golem every request is associated with an idempotency key. If there is no special idempotency key script provided, then this key is either taken from the Idempotency-Key HTTP header, if any, or otherwise a random key is generated for each request.
The idempotencyKey field of a binding can define a Rib script returning a string value. The following example uses a custom header instead of the Idempotency-Key one for extracting the idempotency key:
request.headers.X-Custom-Idempotency-KeyOptional invocation context script
Every invocation in Golem has access to an invocation context. This invocation context has a span ID and a set of key-value pairs associated with it. Golem adds several keys by default to the invocation context, but by providing a Rib script in the binding's invocationContext field, additional key-value pairs can be added to the invocation context of the agent method invocation based on the request.
The result of this Rib script must be an object of which each field is going to be added to the invocation context.
Authentication
Golem API gateway has built-in authentication support to identify users using OpenID protocol. See the API authentication page for details.
When authentication is configured for a route, the request object in the Rib script has an additional auth field, which contains the claims from the authenticated user's ID token. For example, if the authentication provider provides an email claim, it can be accessed as request.auth.email.
In the HTTP API route definitions, authentication is enabled by adding a security field to the route (next to the method, path and binding fields) referring to the preconfigured security scheme:
httpApi:
definitions:
counter-api:
version: '0.0.2'
routes:
- method: POST
path: /{name}/increment
security: my-security
binding:
type: default
componentName: "example:counter"
response: |
# ...CORS
Setting up CORS for a route in API Gateway is done by specifying an extra route for the same path with OPTIONS method and using the cors-preflight binding type.
Optionally a response field can be added to the binding, customizing the CORS headers:
httpApi:
definitions:
counter-api:
version: '0.0.2'
routes:
- method: POST
path: /{name}/increment
binding:
type: default
componentName: "example:counter"
response: |
# ...
- method: OPTIONS
path: /{name}/increment
binding:
type: cors-preflight
response: | # this field is optional
{
Access-Control-Allow-Origin: "*",
Access-Control-Allow-Methods: "GET, POST, PUT, DELETE, OPTIONS",
Access-Control-Allow-Headers: "Content-Type, Authorization",
Access-Control-Expose-Headers: "Content-Length, X-Requested-With",
Access-Control-Allow-Credentials: true,
Access-Control-Max-Age: 86400u64
}Deployments
A deployment simply associates a set of API definitions with a host, for example to deploy the example API definition when running the local Golem server, the following deployment can be used:
httpApi:
definitions:
# ...
deployments:
local:
- host: localhost:9006
definitions:
- counter-apiDeploying the API
Once the API definition and deployments are added to the golem.yaml file, the API can be deployed using the following command:
golem api deployThe interactive shell will force you to update the version of the API definition to not lose track of the changes,
and keep a note that you can have advanced management of the APIs using the golem api definition and golem api deploy commands
allowing you to switch back to a previous API if needed.
Note that the api deploy command also deploys the underlying components if needed.
Constraints
Once an API is deployed using a specific component-version, the agent methods used in the Rib script of the deployed API definition must be kept backward compatible. This is checked and enforced in the deploy command.
If there are conflicts the api deploy command will fail with a conflict report message,
which details especially the missing functions and conflicting functions, where conflicting functions
detail about existing parameter types, new parameter types, existing return type, and new return type.
Exporting as OpenAPI specification
The API definitions can also be exported as OpenAPI specifications, which can be used for generating client code or as documentation.
Use the golem api definition export CLI command to export the OpenAPI specification of a specific API definition:
golem api definition export --id counter-api --version 0.0.1Troubleshooting
See the troubleshooting page for some common issues and their solutions.