Errors

The two error body shapes the Terac API returns, how to read the error code from either one, and every error code with its HTTP status.

A failed request returns an HTTP status of 400 or above and a JSON body. The body has one of two shapes, depending on where the request was refused:

  • API key errors come from the key check that runs before any operation. They return 401 or 403, with the fields nested under error.
  • Operation errors come from the operation itself: validation, missing resources, state conflicts, server errors, and unknown paths. They return the fields at the top level.

How to read an error

  1. Branch on the HTTP status first. It is reliable whichever shape the body has.
  2. Read code at the top level of the body.
  3. If there is no top-level code, read error.code. Only the API key check returns this shape.
  4. If code is BAD_REQUEST and issues is present, read issues to find which fields were rejected.
const body = await response.json();
const code = body.code ?? body.error?.code;
const message = body.message ?? body.error?.message;

The status alone does not always tell you the shape. A 401 always comes from the key check and is nested, but a 403 can be nested (from the key check) or flat (from an operation). The fallback above handles both.

API key errors

The key check runs before the request reaches any operation. It returns a body with error.code and error.message, and no other fields:

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "API key required. Include Authorization: Bearer <key> header."
  }
}
HTTP statuserror.codeerror.messageCause
401UNAUTHORIZEDAPI key required. Include Authorization: Bearer <key> header.The Authorization header is missing or does not start with Bearer .
401UNAUTHORIZEDInvalid or expired API keyThe key is unknown, disabled, expired, or revoked, or its owner no longer exists. Also sent when the key is rate limited. See Rate limits.
401UNAUTHORIZEDAPI key is not scoped to an organization. Please create a new key.The key is not linked to an organization. Create a new key from your organization settings.
403FORBIDDENThis account is not permitted to use the API.The account that owns the key is banned or deleted.

Rate limits

Each API key is limited to 100 requests in a row. The count starts over only after 60 seconds pass with no accepted request on the key, so a key that never pauses for a full minute is refused after its 100th request, even at fewer than 100 requests a minute.

A refused request receives the same response as an invalid key: 401 with error.code UNAUTHORIZED and the message Invalid or expired API key. The response has no Retry-After header.

If a key that was working starts returning that response, stop sending requests on that key for 60 seconds, then retry once before treating the key as revoked. Refused requests do not extend the wait.

Operation errors

Every error raised after the key check returns the fields at the top level:

{
  "message": "Input validation failed",
  "code": "BAD_REQUEST",
  "issues": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": ["name"],
      "message": "Required"
    }
  ],
  "data": {
    "code": "BAD_REQUEST",
    "httpStatus": 400,
    "path": "createProject"
  }
}
FieldTypePresentDescription
codestringAlwaysAn error code from the table below.
messagestringAlwaysWhat went wrong. For input validation errors it is always Input validation failed, and the detail is in issues.
issuesarrayInput validation errorsOne entry per rejected value. Absent on every other error.
dataobjectAlwaysRepeats code and the HTTP status (httpStatus), and names the operation in path when the request matched one. It can carry other fields. It is not part of the OpenAPI schema, so read the top-level fields instead.

Validation issues

When the request does not match the operation's schema (a missing field, a wrong type, a value outside the allowed range or enum), the response is 400 with code BAD_REQUEST, message Input validation failed, and an issues array. Each issue has:

FieldTypeDescription
patharray of strings and numbersWhere the value is. Object keys are strings and array positions are numbers, so ["tasks", 0, "task_type"] is the task_type of the first task. Query and path parameters appear by name, such as ["limit"].
messagestringWhy the value was rejected, such as Required or Expected number, received nan.
codestringThe kind of problem, such as invalid_type, too_small, too_big, invalid_enum_value, or custom.

Some issue codes carry extra fields that describe the problem, such as expected and received on invalid_type, or minimum on too_small. A number field that is missing or not numeric reports Expected number, received nan, not Required.

Some rules are checked after the schema, such as an unknown filter slug. Those are also 400 BAD_REQUEST, but they have no issues: the message names the problem and, where it can, the valid values.

Error codes

HTTP statuscodeShapeReturned when
400BAD_REQUESTFlatThe request is invalid: it fails the schema (see issues), or breaks a rule the schema cannot express, such as an unknown filter slug or an unsupported filter operator. The message says which.
400PARSE_ERRORFlatThe body is not valid JSON. An empty body sent with Content-Type: application/json is also rejected this way; send {} instead.
401UNAUTHORIZEDNestedThe API key is missing, invalid, expired, revoked, or rate limited. See API key errors.
403FORBIDDENNestedThe account that owns the API key is banned or deleted.
403FORBIDDENFlatThe key's owner lacks the organization permission the operation needs. The message names the missing scope.
404NOT_FOUNDFlatThe resource does not exist or belongs to another organization. Also returned for a path or HTTP method the API does not have, with the message Not found.
409CONFLICTFlatThe action is not allowed in the resource's current state, such as launching an opportunity that is not a draft or approving a submission that is not awaiting review. Also returned when a webhook URL is already subscribed.
412PRECONDITION_FAILEDFlatSomething you can fix must be in place first: the organization balance cannot cover a launch, the opportunity is not ready to launch, or a webhook endpoint failed the confirmation request. The message says what to fix.
415UNSUPPORTED_MEDIA_TYPEFlatA POST or PATCH request without Content-Type: application/json. Send that header and a JSON body on every POST and PATCH, including actions such as launch that take no fields (send {}).
422UNPROCESSABLE_CONTENTFlatThe request refers to a feasibility request that cannot price an opportunity: it is not priced yet, was already used, or was closed. The message says which and what to do.
500INTERNAL_SERVER_ERRORFlatAn unexpected error on Terac's side. The message is usually Unknown error. Retry with backoff. Before retrying a create, list the resources first: the failed attempt may have created one.