Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error reference

Two vocabularies of refusal. The control plane answers HTTP requests with RFC 7807 problem bodies, each carrying a stable machine code. The request path refuses admission with a DenyReason, which never involves I/O and always charges zero units; how a service turns one into an HTTP response is its own choice.

Codes and reasons are contracts: tollgate-client's HTTP transport maps problem codes back to domain errors, and deny-reason labels name exported counters. An embedder can match on them.

The problem body

Every tollgate-server error is application/problem+json, built in crates/tollgate-server/src/error.rs from the Problem type in crates/tollgate-store/src/wire.rs:

fieldpresentmeaning
statusalwaysthe HTTP status, repeated
codealwaysthe stable machine code below; classify by this and status
titlealwaysa human-readable summary. Never backend text: storage failures always read backend unavailable
generationrevoked-principal onlythe tombstone's generation
balance_exhaustionbalance-exhausted, when the allocator attests it{"period_end": …}: when the period that could refund the account ends, or null without a schedule
balance_shortfallinsufficient-balance, when the allocator attests it{"remaining": …, "period_end": …}: funding still held in other leases
error_idevery 5xx, and usage-refused32 lowercase hex digits naming the matching tollgate::diagnostics warning; see backend failures and diagnostics

A 401 also carries WWW-Authenticate: Bearer realm="tollgate-control".

There is no type member: the code is the problem type. Requests to a path or method the router does not serve get axum's plain 404 or 405 with an empty body, not a problem.

Problem codes

Every code the server can return. "Retry" means resending the same request. The HTTP API reference lists which routes return which codes.

Authentication and request shape

codestatusmeaningretry
authentication-required401no credential, a malformed or duplicate Authorization header, a credential that does not verify or has expired, a client certificate no longer trusted, or a certificate and bearer naming different identitiesafter the credential is fixed or rotated
scope-forbidden403the credential verifies but maps to no identity, or to a role the route does not admit; or a provisioner sent an argument outside its scopeno — change the role map, the credential or the request
invalid-id400a path identifier is not exactly 32 lowercase hexadecimal digitsno — fix the path
invalid-json400, 415 or 422the body is not JSON (400), is not sent as application/json (415), or does not match the endpoint's type, including a missing required field or an unknown field where refused (422). The status is axum's rejection statusno — fix the body
invalid-query400query parameters are malformed or unknownno — fix the query
invalid-limit422a credential page limit outside 1–4096no — fix the limit
batch-too-large413the body exceeds the endpoint's limit: 2 MiB for usage ingest and other routes, 4 MiB for snapshot publication. Title: "request body exceeds this endpoint's limit"; only usage ingest appends the usage-batch event capnever unchanged — reduce the body or split a usage batch

Leases

tollgate-client maps each of these to an AllocateError variant of the same name. See LeaseAllocator.

codestatusmeaningretry
unknown-account404no such account, including when listing its credentials; an existing account with no matching keys returns an empty pageno — fix the id
account-inactive409the account exists but is not in a state that may spendafter its status changes
insufficient-balance409no grant is possible now. With balance_shortfall, the ledger attests how much funding remains, all of it held in other leasesyes, polling: settlement, lease release or a top-up can restore balance
balance-exhausted409the ledger confirms no funding remains, including in leasesafter a deposit, or after balance_exhaustion.period_end for a scheduled account
invalid-ttl422the lease TTL is not one unambiguous positive durationno — fix the TTL
unknown-lease404no such leaseno — acquire a new lease
fenced409the fencing token does not match the leaseno — the capability is not this caller's
lease-not-active409the lease was already released, expired or reclaimedno — acquire a new lease
invalid-release422the release claims more unspent units than the lease can still hold: a client accounting faultno — investigate the caller

Snapshots and principals

codestatusmeaningretry
unknown-principal404no snapshot was ever published for this principalafter one is published
revoked-principal410the principal's snapshot was withdrawn; generation is the tombstone'sonly after a republish at a higher generation
enumeration-unsupported501the backend cannot list principals. Not an empty catalogueno — track a configured principal set
invalid-snapshot-limits422the snapshot's limits cannot be enforced as publishedno — fix the snapshot
invalid-credential-binding422the snapshot's key_id names a different credential, principal or account than the one being publishedno — fix the snapshot
snapshot-status-mismatch409the snapshot's status contradicts the account ledgerno — change status through the status route
snapshot-capacity-class-mismatch409the snapshot's capacity class contradicts the account ledgerno — change it through the capacity-class route

Accounts and credentials

codestatusmeaningretry
account-exists409the account is already createdno — creation already succeeded
account-closed409the account is Closed, which no status or class change leavesno
account-not-provisioned403a provisioner addressed an account an operator createdno — an operator administers it
operator-hold403a provisioner tried to activate an account whose current status an operator setno — only an operator lifts the hold
zero-deposit400a deposit of zero unitsno — deposit a positive amount
balance-overflow422a deposit exceeds the backend's unit domain or would overflow the top-up balance or lifetime deposited total; nothing changesnever
unknown-credential404no such credential for this account, including another account's keyno
credential-exists409this key_id is already recordedno — the first issuance succeeded; its secret is not disclosed again
active-key-limit409the account already holds max_active_keys live credentialsafter revoking one
credential-retired409the credential is revoked and can never be bound to a policy againno — issue a new credential
issuance-unsupported501this server has no credential issuer configuredno — configure one; see credential issuer
issuer-misconfigured500the configured issuer minted a secret that is not presentable text; nothing was storedno — fix the issuer
entropy-unavailable503the issuer could not obtain entropyyes, with backoff

Usage and availability

codestatusmeaningretry
usage-refused422the store examined the usage batch and will refuse it again unchanged, for example an accounting total that cannot absorb its unitsnever unchanged
credential-source-unavailable503the credential feed could not produce a valid pageyes, with backoff
storage503the backend could not answer, or refused an operation for a reason it does not classify. A mutation's outcome is unknownyes, with backoff; reconcile a mutation against its audit receipt

tollgate-client treats an ingest answer of 401, 403, 408, 429 or any 5xx as retryable, and every other 4xx as a refusal it must not replay; see usage accounting.

Deny reasons

DenyReason, in crates/tollgate-core/src/deny.rs, is every reason admission can refuse a request. Each carries a Retry classification from DenyReason::retry, so every embedder gives the same advice for the same refusal:

  • Transient — the same request can become admissible when capacity or freshness recovers, without a funding change.
  • AfterInFlight — retry once a concurrent admission decision finishes publishing. The retry may then report transient capacity.
  • Never — the same request cannot become admissible under the current policy and funding. New funding, a new budget period or a policy change can change that.

label is DenyReason::name, the metric label the reason is counted under (see the metrics reference).

The last column is the status and problem code that examples/pricing-api answers with, in its deny_response. That mapping is the example's own choice, not part of Tollgate's contract; another embedder may choose differently. It is recorded here because it is a worked answer to the question each reason poses.

reasonlabelmeaningRetrypricing-api
UnknownPrincipalunknown_principalno snapshot is installed for the principal, including a principal recently confirmed unknownNever401 unknown-principal
AccountSuspendedaccount_suspendedthe account is administratively suspendedNever403 forbidden
AccountClosedaccount_closedthe account is closed; terminalNever403 forbidden
SnapshotExpiredsnapshot_expiredthe installed snapshot's validity window lapsed and no replacement arrivedTransient503 policy-stale
MissingPermissionmissing_permissionthe snapshot does not grant the operation's permission bitsNever403 forbidden
RequestTooLarge { max_items }request_too_largethe item count exceeds the account's batch capNever413 batch-too-large
UnpricedOperationunpriced_operationthe operation has no price in the account's cost tableNever422 unpriceable
RateLimitedrate_limitedthe account's weighted rate limiter has no capacity for this request's weight nowTransient429 rate-limited
RequestRateLimitedrequest_rate_limitedthe account's request-count bucket has no token nowTransient429 request-rate-limited
ConcurrencyLimitedconcurrency_limitedan account or principal in-flight ceiling is saturatedTransient429 concurrency-limited
UnpriceableUnderLimits { weight, burst_units }unpriceable_under_limitsthe quote exceeds the account's whole burst capacity, so no wait can admit it: a misconfigured scheduleNever422 unpriceable-under-limits
LeaseUnavailablelease_unavailableno lease is installed for the account: cold start, or lostTransient503 quota-unavailable
LeaseExpiredlease_expiredthe local lease's validity lapsed and refill has not replaced itTransient503 quota-unavailable
LeaseExhausted { remaining }lease_exhaustedthe local lease cannot cover the quoteTransient429 quota-exhausted
OverageCapExhausted { spent, overage_cap }overage_cap_exhaustedan elastic request cannot fit inside the per-instance overage cap even if every refundable reservation releases. Does not prove central exhaustionTransient503 overage-cap-exhausted
CostOverflowcost_overflowcost arithmetic overflowed; the quote is refused rather than wrappedNever422 unpriceable
AccountingBackpressureaccounting_backpressurethe usage queue is full; admitting would drop billing events or blockTransient503 accounting-busy
OverageCapTemporarilyExhausted { spent, overage_cap }overage_cap_temporarily_exhaustedpending overage reservations occupy the cap, and can return it without a funding changeTransient503 overage-cap-temporarily-exhausted
OverageCommitInProgress { spent, overage_cap }overage_commit_in_progressa reservation is publishing its move from pending to committed overageAfterInFlight503 overage-commit-in-progress
EmptyWorkloadempty_workloadthe staged request carried no priceable workNever422 empty-workload
FundingExpiredAtStartfunding_expired_at_startthe funding reserved at admission expired before execution started; staged lifecycle onlyTransient503 funding-expired-at-start
CapacityUnavailablecapacity_unavailablethis instance has no execution capacity to start the request; says nothing about the accountTransient503 capacity-unavailable
BalanceExhaustedbalance_exhaustedthe allocator confirmed the account's funding is exhaustedNever402 balance-exhausted
BalanceInsufficient { remaining }balance_insufficientthe allocator confirmed the account's remaining funding, counting units in leases, is below this quote. Smaller quotes are unaffectedNever402 balance-insufficient

The payload fields in braces are for the caller's response; they are not part of the label, so a label never mints a time series per value. The example renders DenyReason's Display text as the problem title and sends no Retry-After header: no reason carries a retry instant.

The concepts page introduces refusals, and embedding Tollgate states which parts of pricing-api are contract.