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, 403 or 429, 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 or a 429 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.
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.
429RATE_LIMITEDRate limit exceeded. Retry after 42 seconds.The key is rate limited. The number of seconds changes with each response. See Rate limits.

Rate limits

A rate limited key returns 429 with error.code RATE_LIMITED and a Retry-After header that gives the number of seconds to wait. Wait that long, then retry. The key is still valid, so do not rotate or revoke it.

The limit itself is described in Authentication.

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, or revoked. 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.
403FORBIDDENFlatThe operation works only in a sandbox organization, and the API key belongs to one that is not. Creating a test submission is the only such operation.
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_CONTENTFlatA feasibility request cannot price the opportunity (not priced yet, already used, or closed), or Terac cannot create the test submission asked for on that opportunity. The message says which and what to do.
429RATE_LIMITEDNestedThe API key is rate limited. Wait the number of seconds in the Retry-After header, then retry. See Rate limits.
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.