Errors
hclapi returns RFC 9457 Problem Details for every error, whether raised at ingress, during schema validation, or during pipeline execution.
{
"type": "urn:hclapi:error:bad-request",
"title": "Invalid Request Payload",
"status": 400,
"detail": "invalid JSON payload: syntax error at line 1, column 9",
"instance": "/api/v1/transform",
"step": "ingress"
}
Returning custom errors from Go steps
Native go steps can return a hclapi.Problem to halt the pipeline and emit a specific HTTP status code directly to the client.
step.Problem
The fastest way to return an error is using the step.Problem helper. The engine automatically binds the step's name, derives the standard title (http.StatusText), and builds the canonical type URN:
engine.RegisterStep("auth.verify_key", func(ctx context.Context, step *hclapi.Step) (any, error) {
apiKey := step.Request.Header("X-API-Key")
if apiKey == "" {
// Automatically emits HTTP 401 Unauthorized with canonical URN
return nil, step.Problem(http.StatusUnauthorized, "Missing or invalid 'X-API-Key' header")
}
return map[string]any{"authenticated": true}, nil
})
hclapi.NewProblem
If you are outside a step or prefer a package-level function:
return nil, hclapi.NewProblem(http.StatusNotFound, "Customer record not found")
3. Bare struct literal with auto-derivation
You can return a hclapi.Problem struct literal with only Status and Detail. The engine automatically infers Title, Type, Step, and Instance:
return nil, hclapi.Problem{
Status: http.StatusForbidden,
Detail: "User does not have permission to delete this project",
}
RFC 9457 Extension Members
RFC 9457 Section 3.2 allows problem details to be extended with custom members. Any key-value pairs placed in Extensions are automatically flattened into the root JSON object:
engine.RegisterStep("billing.charge", func(ctx context.Context, step *hclapi.Step) (any, error) {
p := step.Problem(http.StatusPaymentRequired, "Insufficient account balance")
p.Extensions = map[string]any{
"error_code": "CARD_DECLINED",
"current_balance": 14.50,
"required_amount": 50.00,
"currency": "USD",
}
return nil, p
})
Serialized output to the client:
{
"type": "urn:hclapi:error:payment-required",
"title": "Payment Required",
"status": 402,
"detail": "Insufficient account balance",
"instance": "/api/v1/checkout",
"step": "billing.charge",
"error_code": "CARD_DECLINED",
"current_balance": 14.5,
"required_amount": 50,
"currency": "USD"
}
Overriding error documentation URLs
It's possible to override the default urn:hclapi:error: prefix by configuring problem.type_prefix in the server {} block:
server {
problem {
type_prefix = "https://docs.mycompany.com/errors/"
}
}
This transforms urn:hclapi:error:payment-required into https://docs.mycompany.com/errors/payment-required.