HTTP Ingresses
HTTP ingresses expose reactor components as HTTP endpoints, allowing external systems to call component methods via standard HTTP requests.
Configuration
An HTTP ingress is configured through the properties field when creating an ingress with type: http. The properties define how HTTP requests map to component parameters.
Basic Structure
uri: /resources/ingresses/my-ingressspec: type: http componentId: my-component.hreactor properties: route: api/my-endpoint httpMethod: post isPublic: false headers: {} queryParams: {} body: mode: structuredRoute Configuration
Route
The route property defines the URL path where the ingress will be accessible:
properties: route: api/orders/createThe ingress will be callable at /ingress/api/orders/create.
Route Parameters
Routes can include dynamic parameters using the {paramName} syntax:
properties: route: api/orders/{orderId}/statusRoute parameters are automatically extracted and passed to the component:
curl -X GET https://your-instance.hantera.io/ingress/api/orders/12345/status# orderId parameter will be "12345"Multiple route parameters are supported:
properties: route: api/warehouses/{warehouseId}/products/{productId}HTTP Method
The httpMethod property defines which HTTP method the ingress accepts:
properties: httpMethod: post # get, post, put, patch, or deleteAvailable methods:
get- HTTP GET requestspost- HTTP POST requestsput- HTTP PUT requestspatch- HTTP PATCH requestsdelete- HTTP DELETE requests
Requests using other HTTP methods will receive a 405 Method Not Allowed response.
Parameter Mapping
HTTP ingresses map various parts of the HTTP request to component parameters. The mapping happens in a specific order, with later mappings potentially overriding earlier ones.
Parameter Resolution Order
- Route parameters - Extracted from the URL path
- Headers - Mapped HTTP headers
- Query parameters - URL query string values
- Body - Request body content
This order allows intentional parameter overloading. For example, a query parameter can override a route parameter if needed.
Headers
Map HTTP headers to component parameters:
properties: headers: userId: X-User-Id apiKey: X-API-KeyThe component will receive userId and apiKey parameters with values from the X-User-Id and X-API-Key headers.
If multiple values exist for a header, they are joined with semicolons.
Query Parameters
Map URL query string parameters to component parameters:
properties: queryParams: pageSize: limit pageNumber: offsetExample request:
curl "https://your-instance.hantera.io/ingress/api/products?limit=50&offset=0"# Component receives: pageSize=50, pageNumber=0If multiple values exist for a query parameter, they are joined with semicolons.
Body Handling
The body configuration determines how the HTTP request body is processed.
Structured Mode
In structured mode, the request body must be JSON and is parsed into component parameters:
properties: body: mode: structuredWithout a parameter name, each JSON property becomes a component parameter:
curl -X POST https://your-instance.hantera.io/ingress/api/orders \ -H "Content-Type: application/json" \ -d '{ "customerId": "CUST001", "items": [{"sku": "PROD001", "quantity": 2}] }'# Component receives: customerId="CUST001", items=[...]With a parameter name, the entire JSON body becomes one parameter:
properties: body: mode: structured parameter: orderDatacurl -X POST https://your-instance.hantera.io/ingress/api/orders \ -H "Content-Type: application/json" \ -d '{ "customerId": "CUST001", "items": [{"sku": "PROD001", "quantity": 2}] }'# Component receives: orderData={customerId: "CUST001", items: [...]}Structured mode requires Content-Type: application/json. Other content types will result in a 400 Bad Request.
Raw Mode
In raw mode, the request body is passed as a stream to the component:
properties: body: mode: raw parameter: fileDataThis is useful for:
- Large structured data imports (XML, CSV)
- Binary data processing
- File uploads
- Custom content types
The component receives the raw stream as a byte array ([byte]) that can be parsed as needed.
import 'xml'
param xmlData: [byte]
// Parse XML stream and extract product datalet products = xmlData |> xml.readXml |> xml.whereElement('product') select product => { skuNumber = product |> xml.whereElement('sku') |> xml.getText name = product |> xml.whereElement('name') |> xml.getText price = product |> xml.whereElement('price') |> xml.getText }
// Create SKUs by sending messages to the sku actorlet results = products select p => messageActor( 'sku' 'create' [{ type = 'create' body = { skuNumber = p.skuNumber commands = [{ type = 'setDynamicFields' fields = { name = p.name price = p.price } }] } }])
from resultsComplete Example: SKU Search API
Here’s a comprehensive example showing various HTTP ingress features:
# Component that searches SKUsuri: /resources/components/sku-search.hreactorspec: codeFile: sku-search.hreactor
---# HTTP ingress with query paramsuri: /resources/ingresses/api/skus/searchspec: type: http componentId: sku-search.hreactor acl: - skus:read properties: route: api/skus/search httpMethod: get queryParams: searchTerm: q sortBy: sortComponent code:
param searchTerm: textparam sortBy: text
let sort = sortBy match nothing |> 'skuNumber' |> sortBy
from query skus(skuNumber) phrase searchTerm orderBy $'{sort} asc'Example request:
curl -X GET \ "https://your-instance.hantera.io/ingress/api/skus/search?q=laptop&sort=skuNumber" \ -H "Authorization: Bearer YOUR_TOKEN"The component receives:
searchTerm: “laptop” (from query)sortBy: “skuNumber” (from query)
The query uses cursor-based pagination automatically, so results are returned lazily as the caller iterates through them.
Access Control
Calling HTTP Ingresses
To call an HTTP ingress, a session must have the appropriate permission based on the ingress resource ID:
ingresses[/<ingressId>]:httpImportant: The permission uses the ingress resource ID (the uri when creating the ingress), NOT the HTTP route. These can be different.
Example:
uri: /resources/ingresses/api/skus/search # This is the ingressIdspec: type: http properties: route: search/skus # This is the HTTP route (can be different!)Permissions needed:
- To call this ingress:
ingresses/api/skus/search:http(uses the ingressId) - To manage this ingress:
ingresses:readoringresses:write
HTTP endpoint:
- The ingress is called at:
/ingress/search/skus(uses the route property)
The permission grants the ability to call the ingress, not to view or modify its configuration.
Note: Public ingresses (see below) bypass this authentication requirement entirely.
Public Ingresses
Mark an ingress as public to allow unauthenticated access:
uri: /resources/ingresses/webhooks/stripespec: type: http componentId: stripe-webhook.hreactor properties: route: webhooks/stripe httpMethod: post isPublic: true body: mode: raw parameter: payloadPublic ingresses:
- Don’t require authentication
- Have a 10 MB maximum payload size
- Rate limiting applied by the platform
- Should validate input carefully in component code
- Are ideal for webhooks and public APIs
Response Handling
Component return values are automatically converted to HTTP responses:
JSON Response
Most component return values are serialized as JSON:
from { status = 'success' data = ['item1', 'item2']}Response:
HTTP/1.1 200 OKContent-Type: application/json
{ "status": "success", "data": ["item1", "item2"]}Stream Response
Components can return streams for file downloads or large responses:
param fileId: text
from files.read($'documents/{fileId}')The stream’s MIME type is automatically set as the Content-Type header, and the content is streamed to the client.
Error Response
Return a record matching the Error type to indicate failure. HTTP ingresses automatically convert records with this structure to HTTP error responses:
param orderId: text | nothing
from orderId match nothing |> { error = { code = 'MISSING_PARAMETER' message = 'Order ID is required' } } id |> processOrderInternal(id)Error type structure: { error: { code: text, message: text | nothing } }
The HTTP response will be:
HTTP/1.1 400 Bad RequestContent-Type: application/json
{ "error": { "code": "MISSING_PARAMETER", "message": "Order ID is required" }}Best Practices
Validation
Always validate input parameters, especially for public ingresses:
param email: text | nothingparam name: text | nothing
from { email, name } match { email: text, name: text } |> createUser(email, name) |> { error = { code = 'INVALID_INPUT' message = 'Email and name are required' } }Parameter Naming
Use clear, consistent parameter names:
- ✅
customerId,orderId,productSku - ❌
id,val,x
HTTP Methods
Use appropriate HTTP methods:
- GET: Retrieve data (no side effects)
- POST: Create new resources
- PUT: Update/replace entire resources
- PATCH: Partial updates
- DELETE: Remove resources
Error Handling
Provide meaningful error messages:
param productId: text
let product = query skus(skuNumber) first
from product match nothing |> { error = { code = 'NOT_FOUND' message = $'SKU not found' } } p |> pRoute Design
- Use hierarchical routes:
api/orders/{orderId}/items - Use plural nouns for collections:
api/products, notapi/product - Keep routes simple and predictable
- Version your API if needed:
api/v1/orders
Security Considerations
Authentication
Non-public ingresses require authentication via Bearer token:
curl -H "Authorization: Bearer YOUR_TOKEN" \ https://your-instance.hantera.io/ingress/api/ordersAuthorization
Use ingress ACLs to control what operations the component can perform:
spec: acl: - orders:read - customers:readInput Validation
Always validate and sanitize input, especially from public ingresses:
param payload: anyparam signature: text
// Validate webhook signaturelet isValid = validateSignature(payload, signature)
from isValid match false |> { error = { code = 'UNAUTHORIZED' message = 'Invalid signature' } } true |> processWebhook(payload)See Also
- Ingresses Overview - Core ingress concepts
- Components - Creating reactor components
- API Reference - Complete API documentation