Errors

When an API call fails, the status code tells you what kind of problem it is, and most errors also carry a JSON body with a readable message. This page lists the errors of every public endpoint, in the order the checks run.

These are errors of the API call itself. What happens when your target URL fails later, when the trigger fires, is described in How triggers fire.

Error format

Most errors come with Content-Type: application/json and a body like this:

{
"_tag": "BadRequest",
"message": "Invalid URL: example.com"
}
Status code_tagMeaning
400Bad RequestBadRequestA header, field or value in the request is invalid.
401UnauthorizedUnauthorizedThe API key doesn't exist, or no credentials were sent.
402Payment RequiredQuotaExceededYour monthly quota is used up. Only /schedule returns it.
404Not FoundNotFoundThe trigger or tag policy doesn't exist.
410GoneGoneThe trigger exists but can no longer be edited or cancelled.
  • _tag always matches the status code, so you can branch on either.
  • message is meant for people and often contains the value that was rejected, such as the URL or the tag. Log it rather than parse it.
  • Only the first failed check is reported. After you fix it, the same request can fail on a later check.

Errors without a JSON body

Some errors are returned before the endpoint's own checks run, so they don't have the JSON body:

Status codeBodyWhen
400Bad RequestEmptyThe request doesn't have the required shape: a required header is missing on /schedule or /cancel, or the body sent to /declare, /bulk or /tag-policies isn't valid JSON or doesn't match the expected fields and types.
404Not FoundEmptyThe path doesn't exist, or the endpoint doesn't accept the method, for example POST /cancel or GET /declare.
415Plain text: Unsupported content-type: <type>/declare, /bulk or /tag-policies received a Content-Type other than application/json.
431EmptyThe request headers are too large. See Size limits.

Good to know

  • Parse the body as JSON only when the response's Content-Type is application/json. The empty responses above have no Content-Type at all.
  • The Full spec shows a JSON body for every 400. The empty-body 400s above don't have one.
  • Requests over the size limits, or ones that aren't valid HTTP, may be rejected before they reach the API, with a response in a different format.
  • A HEAD request to /schedule never gets a response body, so you only see the status code, for errors too.

Handling errors

  • Don't resend a request that got a 4xx without changing it first. A 402 lasts until your quota resets or your limit changes.
  • If you retry a /cancel after a timeout, a 404 or 410 can mean the first attempt already worked. See Cancelling again.

/schedule

Applies to creating triggers and to editing them. The checks run in the order of this table, and the first one that fails is returned:

Status codeMessageWhen
400Bad Request(empty body)ttr-api-keyheader or ttr-urlheader is missing. A key sent only as Authorization: Bearer counts as missing.
400Bad RequestFailed to read request bodyThe request body couldn't be read completely.
400Bad RequestInvalid URL: <ttr-url>ttr-urlheader is empty or isn't an absolute URL. See Target URL.
400Bad RequestMaximum 10 tags allowedttr-tagsheader lists more than 10 tags.
400Bad RequestTag too long (max 50 chars): <tag>A tag is longer than 50 characters.
400Bad RequestInvalid tag (only a-z 0-9 _ - allowed): <tag>A tag contains another character, such as an uppercase letter or a space. See Tags.
401UnauthorizedInvalid api keyttr-api-keyheader is empty or doesn't match any key.
400Bad RequestInvalid date: <date>ttr-scheduled-atheader is empty, or its part before the first | is neither now nor a date. See Date formats.
400Bad RequestUnknown pipe operation: <operation>An operation after a | isn't a lowercase add or subtract with an amount. See Operations.
400Bad RequestInvalid duration: <amount>An amount isn't a whole number followed by s, m, h, d or w.
400Bad RequestInvalid cron expression: <details>The cron(...) expression or its time zone is invalid. For an unknown time zone, <details> may not mention the zone. See Cron syntax.
402Payment RequiredMonthly quota of <limit> triggers exceededYour project has used its monthly quota. See Quota.
404Not FoundTrigger not foundYou sent ttr-trigger-idheader with a date or now, and no one-shot trigger has this ID. This includes the ID of a recurring trigger.
404Not FoundGenerator not foundYou sent ttr-trigger-idheader with cron(...), and no recurring trigger has this ID. This includes the ID of a one-shot trigger or of a single instance.
410GoneJob is no longer in registered stateYou sent ttr-trigger-idheader with a date or now, and the one-shot trigger isn't registered anymore: it's skipped, queued, running, retrying, completed or cancelled.
410GoneGenerator is no longer activeYou sent ttr-trigger-idheader with cron(...), and the recurring trigger was cancelled.

What the order means:

  • An invalid URL or tag is reported even when the API key is wrong (400). An invalid ttr-scheduled-atheader with a wrong key gets 401.
  • Tags are checked one at a time in the order you list them, length first, then characters. So Bad,<a 51-character tag> gets Invalid tag for Bad.
  • Calls rejected with 400, 401 or 402 don't use quota. A call that passes the quota check uses one unit, even if it then fails with 404 or 410.
  • 404 and 410 only come from edits by ttr-trigger-idheader. With a date or now, if you also send ttr-custom-keyheader and it belongs to a registered, skipped or retrying one-shot trigger, that trigger is edited and the ID isn't checked. With cron(...), the ID is always checked. See Sending both identifiers.

/cancel

Only DELETE is accepted. A successful cancel returns 204 with an empty body. The checks run in the order of this table:

Status codeMessageWhen
400Bad Request(empty body)ttr-api-keyheader is missing. A key sent only as Authorization: Bearer counts as missing.
400Bad RequestMust provide either ttr-trigger-id or ttr-custom-key headerNeither identifier was sent, or the one you sent is empty.
400Bad RequestProvide either ttr-trigger-id or ttr-custom-key, not bothBoth identifiers were sent.
401UnauthorizedInvalid api keyttr-api-keyheader is empty or doesn't match any key.
410GoneGenerator already cancelledBy ID only: the recurring trigger with this ID is already cancelled.
404Not FoundJob not foundNothing in your project matches. By ID: no one-shot or recurring trigger has this ID. By custom key: no active recurring trigger and no uncancelled one-shot trigger has this key. The message is the same for recurring triggers.
410GoneJob is no longer cancellable (status=<status>)The one-shot trigger is queued, running or completed, or, by ID, already cancelled.

Good to know

  • The identifiers are checked before the API key, so a request without an identifier gets 400 even with a wrong key.
  • /cancel doesn't use quota and never returns 402.

/declare

The checks run in the order of this table:

Status codeMessageWhen
415Unsupported content-type: <type> (plain text)The Content-Type isn't application/json. curl's -d sends application/x-www-form-urlencoded unless you set it. A request without a Content-Type is read as JSON.
400Bad Request(empty body)The body isn't valid JSON or doesn't match the format: it's empty or not an object, tag, items or an item's url is missing, or a field has the wrong type, such as "runMissed": "true", or is null (only an item's body may be null).
401UnauthorizedInvalid api keyThe key in ttr-api-keyheader or Authorization: Bearer doesn't exist.
401UnauthorizedUnauthorizedNo API key, an empty one, or an Authorization header that isn't Bearer, and no dashboard session.
400Bad RequestDuplicate customKey in items: <key>Two items have the same customKey.
400Bad RequestItem <key>: duplicate tag "<tag>" in tagsAn item lists the same tag twice.
400Bad RequestItem <key>: <error>An item's scheduledAt can't be read. <error> is one of the ttr-scheduled-atheader messages of /schedule, for example Item (no customKey): Invalid date: tomorrow.

Good to know

  • The Content-Type and the body format are checked before your API key, so a malformed request gets 415 or 400 even without a valid key.
  • Items are checked in request order, before anything is written, and only the first error is returned. <key> is (no customKey) for items without a key.
  • /declare never returns 402, 404 or 410. See Declare a set of triggers for what happens to the items.

/bulk

/bulk returns the same errors as /declare, with these differences:

Status codeMessageWhen
400Bad Request(empty body)Instead of a missing tag or items: an upsert has no url, a cancels entry isn't a string, {"id": ...}, {"customKey": ...} or {"tag": ...}, or upserts or cancels isn't a list. Both lists are optional, so {} is valid.
400Bad RequestDuplicate customKey in upserts: <key>Two upserts have the same customKey.
400Bad RequestUpsert <key>: duplicate tag "<tag>" in tagsAn upsert lists the same tag twice.
400Bad RequestUpsert <key>: <error>An upsert's scheduledAt can't be read, for example Upsert daily-report: Invalid cron expression: ....

Cancel targets that match nothing, or only triggers that can't be cancelled anymore, are skipped without an error, so /bulk never returns 404 or 410. It never returns 402 either. See Bulk apply.

/tag-policies

The checks run in the order of this table:

Status codeMessageWhen
415Unsupported content-type: <type> (plain text)POST and PUT: the Content-Type isn't application/json.
400Bad Request(empty body)POST and PUT: the body isn't valid JSON or doesn't match the format: tag is missing on POST, a field has the wrong type (such as "maxConcurrent": "2"), a retryStrategy or retryOn value is unknown, or a throughput window is malformed (such as 1w).
401UnauthorizedInvalid api keyThe key in ttr-api-keyheader or Authorization: Bearer doesn't exist.
401UnauthorizedUnauthorizedNo API key, an empty one, or an Authorization header that isn't Bearer, and no dashboard session.
404Not FoundTag policy not foundPUT or DELETE: no policy in your project has this id.
400Bad RequestSee belowPOST and PUT: a value is out of range.

The 400 validation messages, in the order they are checked:

  • Invalid tag (only a-z 0-9 _ - allowed, max 50 chars)
  • At least one of throughput, concurrency, timeout, or retry strategy must be specified (POST only)
  • requestTimeoutSeconds must be between 1 and 3600
  • maxConcurrent must be ≥ 1
  • At most 8 throughput windows per policy
  • Throughput rate for '<window>' must be a finite number ≥ 0
  • Effective window for '<window>' at rate <rate> exceeds the 30-day maximum, followed by (rates below 1 stretch the window by 1/rate) when the rate is above 0 and below 1
  • retryMaxAttempts must be ≥ 1 (or null for unlimited)
  • retryInitialDelaySeconds is required (≥0) when retryStrategy is 'fixed' or 'exponential'
  • retryMaxDelaySeconds must be ≥ 0
  • retryInitialDelaySeconds must be ≤ 86400 (1 day)
  • retryMaxDelaySeconds must be ≤ 86400 (1 day)

On PUT, the retry messages are checked against the policy as it would be after the update, so changing retryStrategy alone can fail if the stored policy has no initial delay. See Validation messages for the rules behind each message.

TimeTriggers — Schedule HTTP requests at any time.