Skip to main content

Using the Tower API

Everything you do in the Tower dashboard is backed by the same public REST API, so anything you can do by hand you can automate.

Base URL

All endpoints live under the versioned base URL:

https://api.tower.dev/v1

Authentication

Authenticate every request with an API key in the X-API-Key header. Keys are prefixed with sk-. See API keys for how to create one and how to scope it down.

curl 'https://api.tower.dev/v1/apps' \
-H 'X-API-Key: sk-...'

API reference and OpenAPI spec

The full, always-current endpoint reference — request/response schemas, required scopes, and status codes — is generated from Tower's OpenAPI description and lives under API reference.

The raw OpenAPI spec that powers those pages is downloadable at /tower-api-v1.yaml. Point your own tooling (client generators, curl/Postman imports, contract tests) at it.

Ordering writes with a sequential nonce

When more than one client (or more than one instance of the same automation) synchronizes the same Tower app concurrently, requests can arrive out of order. A slow request carrying older state can land after a newer one and clobber it — a classic lost update. Tower lets you prevent this with an optional, account-scoped sequential nonce (a monotonic "fence").

You opt in per request by sending a sequence token in a header. Tower keeps one counter per account and only applies a write whose token advances that counter; stale or out-of-order writes are rejected with 412 Precondition Failed and the newer state is preserved.

The header contract

Both the request and the successful response use the same header:

X-Tower-Request-Number: <positive integer>
  • On the request it is the sequence number you are advancing the account to.
  • On a 200/201 response it echoes the sequence Tower accepted.
  • On a 412 response it reports the account's current authoritative sequence, so you can resync and decide whether to retry.

The header is optional. A request without it is not fenced (see Enforcement for the exception).

Fencing is currently available on these mutating endpoints, which all share the same per-account counter:

  • Deploy app (POST /v1/apps/{name}/deploy)
  • Update app (PUT /v1/apps/{name})
  • Update schedule (PUT /v1/schedules/{idOrName})

Because the counter is shared across endpoints, a token that updates an app also advances the fence seen by a subsequent schedule update for the same account.

The acceptance window

A token t is accepted only when it lands in the half-open window above the current value c:

c < t <= c + MAX_JUMP
  • It must strictly advance the counter — a stale or equal token loses.
  • It may not jump more than MAX_JUMP ahead of the current value. MAX_JUMP defaults to 1000 and is configurable per deployment. This bound also applies to the very first write for an account (the counter starts at 0), and it caps the damage a buggy client can do: a bad token can never brick the account by rocketing the counter to a huge value.

A token outside the valid syntactic range is rejected before any state is read, with 422 Unprocessable Entity. Valid tokens are positive integers no greater than 2^52.

An accepted write advances the counter atomically, so under a concurrent race exactly one of two equal tokens wins and the other is rejected.

The hybrid client model

Fencing is opt-in per request, so fenced and unfenced clients can coexist against the same account:

  • Clients that care about ordering (for example, a CI pipeline that deploys from monotonically increasing build numbers, or a controller that reconciles state) send X-Tower-Request-Number and get strict ordering.
  • Clients that don't send the header keep today's last-write-wins behavior.

A natural token source is a monotonic counter you already have: a CI build number, a commit sequence, or a millisecond timestamp — anything that only increases and advances by no more than MAX_JUMP between successive writes.

Rejection and retry

When Tower rejects a fenced write it returns 412 Precondition Failed and sets X-Tower-Request-Number to the current authoritative sequence. That tells you a newer change has already been applied. The correct response is usually not to blindly retry with a higher number: a 412 means your write was based on stale state. Re-read the current state, and only retry if your change is still relevant, using a token above the sequence Tower just reported.

Enforcement

By default the fence is advisory: it only orders requests that carry a token, and a request without one still applies. An account can be switched to mandatory fencing (via the enforce-fencing feature flag). When enforcement is on, every mutating write to a fenced endpoint must carry a token — a fenceless request is rejected with 412, and the response header reports the current sequence so the client can start sending tokens from the right place.

Enforcement gives an unconditional guarantee that no un-ordered write can slip through, at the cost of requiring every writer for that account to participate.