Polling
For long-running, asynchronous operations — a job you kick off and then wait on — the SDKs give
you two complementary surfaces: a generated poll<Method>() companion beside each operation
your spec marks pollable, and a generic waiter for any custom predicate.
import { Client } from 'glotto-sdk';
const client = new Client({ token: '<token>' });
const result = await client.pets.pollGetPet('string');regenerated + byte-diffed in CI a0f8af4b462c
The generated poll<Method>() companion
When the spec marks an operation as pollable, the engine emits a companion beside it that
re-fetches on an interval until the resource reaches its terminal state, then returns the typed
result. It honors a deadline and (where idiomatic) an AbortSignal / context so a poll loop
never runs unbounded.
Because the companion owns the fetch, it sees the HTTP response — so it also honors the server’s
Retry-After cadence hint between attempts, clamped to your maxDelayMs so a large hint can
never stall a wait past the budget you set. That is the one thing the generic waiter below cannot
do: you hand it a predicate over an already-decoded value, so the headers are gone by the time it
runs. If your API advertises a poll cadence, the companion is the surface that follows it.
The companion is named for the method it waits on, spelled the way that language spells names —
a method key get emits pollGet in TypeScript, React Native, Swift and Dart; PollGet in Go and
C#, whose methods are exported or PascalCased; poll_get in Python, Ruby, Rust, PHP and Elixir; and
get_job likewise emits pollGetJob / PollGetJob / poll_get_job. It is never a bare poll().
Its return follows the language’s own convention too: TypeScript resolves the typed result and
throws on timeout, while Elixir returns {:ok, result} / {:error, :timeout} like every other call
in that SDK. It ships in every SDK we generate — the generic waiter below remains available for
any predicate the spec does not model.
A few per-language shapes worth knowing:
- Swift —
async throws, taking a trailingpoll: WaitForOptions = WaitForOptions()after the usual request options, and waiting withTask.sleep, so cancelling the surrounding task cancels the wait (there is noAbortSignalto pass). - Java — an overload pair beside the operation:
client.jobs().pollGet(id)for the defaults, orpollGet(id, intervalMs, maxAttempts, timeoutMs, maxDelayMs)to set the budget — plus apollGetAsynctwin returning aCompletableFuture, matching the async twin every other Java method carries.
The generic waiter
For anything the spec doesn’t model, every SDK also exports a generic waiter —
waitFor(predicate, options) (TypeScript), wait_until(fetch, predicate, …) / await_until
(Python), WaitUntil(ctx, fetch, predicate, …opts) (Go), and the equivalent elsewhere. You supply
the predicate; it polls with the configured intervalMs,
timeoutMs, and maxAttempts, returning when the predicate is satisfied and raising a typed
timeout error (PollingTimeoutError) when it isn’t.
Tuning
Both surfaces take the same knobs — poll interval, overall timeout, and max attempts —
so you can bound how long a wait runs. Exceeding the timeout or attempt cap raises a typed error
rather than hanging. In Go the overall budget is your context.WithTimeout rather than a
timeoutMs option; in the C#, Java, Kotlin, PHP, and Ruby SDKs timeoutMs is opt-in — passing it
adds a wall-clock deadline on top of the attempt cap, and leaving it off keeps the wait
attempt-bounded.
Backing off between polls
By default the wait polls on a fixed interval. Set maxDelayMs (max_delay_ms in the
snake_case SDKs, WithPollMaxDelay in Go) to switch to capped exponential backoff: the delay
starts at intervalMs and doubles each attempt up to the cap —
min(maxDelayMs, intervalMs * 2^(attempt-1)). The schedule is deterministic (no jitter), so a
wait is reproducible run-to-run. Python and Go poll with this backoff curve out of the box; for
the generic waiter everywhere else it turns on when you pass the cap. The companion always has
a cap — it defaults maxDelayMs to 30 seconds even where the generic waiter leaves it off — because
the cap is also what bounds a Retry-After hint, so there is always something for a large hint to
be clamped against.