For AI agents: the complete documentation index is available at /hclapi/llms.txt, the full documentation bundle is available at /hclapi/llms-full.txt, and this page is available as Markdown at /hclapi/docs/concepts/errors.md.

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"
}
FieldTypeDescription
typestringURI reference identifying the problem type.
titlestringShort human-readable summary of the problem type.
statusintHTTP status code.
detailstringHuman-readable explanation specific to this occurrence.
instancestringRequest URL path that generated the error.
stepstringPipeline step where the failure occurred, if applicable.
invalid_paramslistField-level schema validation errors (RFC 9457 extension).

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.