API errors

The HTTP API wraps expected calculator service failures in CalculatorApiErrorEnvelope. The error field is the canonical CalculatorServiceError union from the calculator package.

Error model

json
{
  "error": {
    "_tag": "CalculatorInputDecodeError",
    "calculatorId": "au.pay.take-home",
    "message": "Invalid facts for au.pay.take-home",
    "issues": [
      {
        "_tag": "CalculatorInputIssue",
        "message": "Expected GrossPay",
        "path": ["grossPay"]
      }
    ]
  }
}

Expected failures stay in the service error channel. The current public union includes calculator input decode failures, unsupported calculator errors, unsupported calculator context errors and calculation errors. Those request failures use HTTP 400. A busy calculation server returns HTTP 503 with CalculatorCapacityExceeded. An operation that exceeds five seconds returns HTTP 504 with CalculatorOperationTimedOut. Both include a fixed code, message and retry: "try-again-manually". Display the message and let the caller choose when to retry; do not automatically repeat calculations.

The server shares eight active calculation places across HTTP and RPC. Each calculation in a batch takes a place. This budget does not claim that a JavaScript timer can forcibly stop synchronous CPU work. Current per-client rate admission in the native API adds 60 calculations per minute per checked connection address across HTTP, RPC and Website forms. Every batch calculation counts separately; metadata reads do not count. A rejected calculation returns HTTP 429 with CalculatorRateLimited and Retry-After: 60. Missing/invalid connection identity or a failed limiter returns HTTP 503 with CalculatorAdmissionUnavailable. Both contain fixed safe guidance and manual retry fields. These native limits are approximate and local to a Cloudflare location, rather than an exact worldwide quota. The standalone Bun server keeps its existing work policy.

Handle an error response

ts
import { CalculatorApiErrorEnvelope } from "@taxkit/api-http";
import { Effect, Schema } from "effect";
import type { HttpClientResponse } from "effect/http/HttpClientResponse";

// A response from an HTTP request is untrusted until its owning Schema checks it.
export const readCalculatorError = (response: HttpClientResponse) =>
  response.json.pipe(
    Effect.flatMap(Schema.decodeUnknownEffect(CalculatorApiErrorEnvelope))
  );

Call readCalculatorError with the failed calculation response from an Effect HTTP client. The returned body.error has the owning calculator error type. If the response does not match that contract, decoding returns a Schema error.

For typed HTTP-client calls, endpoint error bodies are already checked; use the error channel shown in Build a browser app. Do not maintain another error-envelope type or force an unchecked JSON value into one with a cast.

Add help

Append ?help=errors when you want descriptor-backed help for invalid input:

txt
POST /api/v1/calculators/au.pay.take-home/calculate?help=errors

The help data comes from canonical fact descriptors. Do not maintain a separate application copy of calculator fields.

Public documentation errors

The documentation routes return their own error JSON. Read _tag and message directly; these replies do not use CalculatorApiErrorEnvelope.

Status_tagMessageWhat to do
404DocsPageUnavailableThe documentation page was not found.Choose a page address from navigation.
503DocsSearchUnavailableDocumentation search is temporarily unavailable.Try your search again later.

Both the page JSON and Markdown routes return the same missing-page error. A missing or invalid path or term returns an empty HTTP 400 reply. Check that you supplied the required query and used the limits in Endpoints.