Search Documentation
Search across all documentation pages
Spend Limits

Spend Limits

A spend limit is a per-app monthly cap, in EUR, on transcoding charges. When set, new jobs are rejected once the app’s recorded spend for the current billing period reaches the limit; a warning webhook fires at 80% and a breach webhook at 100%. Unset means unlimited — the default, so existing apps are unaffected.

Setting a limit is not a payment operation. It never charges, authorizes, or reserves funds — it only gates new job admission and fires notification webhooks.

Manage the limit from the app’s Spend Limit settings in the dashboard, or over the API with AppService.UpdateSpendLimit and AppService.GetSpend.

Defaults

StateMeaning
monthly_spend_limit_eur unsetUnlimited (default). Zero behavior change — nothing is aggregated or gated.
monthly_spend_limit_eur = N (> 0)A cap of €N per billing period.

What counts toward spend

Spend is the job’s billed total as computed by the pricing engine, summed over the current billing period:

  • Terminal jobs (completed, failed, canceled, partial) count their actual billed total. A failed job with no billed cost counts zero.
  • In-flight jobs count their recorded probe-time estimate until they reach a terminal state. This includes awaiting_confirmation (delayed-start) jobs — their estimate counts toward spend until they are confirmed, canceled, or otherwise terminal, so cancel abandoned delayed-start jobs to free budget.
  • Both totals already fold in the per-job minimum charge and any additive fees — nothing is re-derived from outputs.
  • Currency is always EUR.

Jobs are attributed to a period by their creation time, not their completion time — a job created in one period keeps counting against that period even if it finishes in the next. Every job-creating path is covered uniformly, including jobs created from an existing input rather than a fresh upload.

Billing period

The billing period follows the app’s billing anchor day in UTC. It starts at 00:00:00 UTC on the anchor day and ends exactly one calendar month later — the same convention as hosting usage.

  • Anchor day 1 (the default) → calendar month, e.g. 2026-07-012026-08-01.
  • Anchor day 15 → 2026-07-152026-08-15.

Spend and threshold state from a previous period never carry into the current one.

Enforcement (a guardrail, not a hard cap)

Enforcement is a create-time check against the spend recorded at that moment — billed totals of terminal jobs plus recorded probe-time estimates of in-flight jobs. It is an advisory read (no row locking). Be clear about what it does and does not promise:

  • Once recorded spend reaches the limit, the next create is rejected. That is the guarantee.
  • Recorded spend can overshoot the limit. A newly admitted job contributes zero recorded cost until the worker probes it and writes an estimate, so a burst of creates just under the limit — or a single large job — can push recorded spend well past it. The overshoot window is the time between a job’s admission and its probe recording an estimate (plus a small gap between the create-time read and the insert commit).
  • In-flight jobs are never killed or blocked. The limit gates only new admissions; work already accepted always runs to completion.
  • Concurrent creates that are all below the limit are all admitted, for the same reason — each contributes zero recorded cost until probed.

Treat the limit as a guardrail against runaway spend, not an exact ceiling.

Once recorded spend reaches the limit, JobService.Create — and every path that funnels through it — is rejected with Connect code resource_exhausted and the machine-readable error code limit_exceeded:

CodeResourceExhausted
  error-code: limit_exceeded
  message: app app_k1l2m3n4o5 has reached its monthly spend limit of €10.00
           (recorded spend €10.00 this billing period); raise
           monthly_spend_limit_eur or wait for the period to reset at
           2026-08-01T00:00:00Z

Switch on the limit_exceeded discriminator, not the message text. To restore admission, raise the limit (or clear it) with UpdateSpendLimit, or wait for the period to reset.

JobService.Confirm is not gated. Confirming a delayed-start job is a state transition, not a new admission, so a job created before the limit was reached can still be confirmed after a breach — its estimate already counts toward period spend, so this changes nothing about the recorded total.

Threshold webhooks

Two events fire once per (app, billing period). The crossing is claimed server-side, so exactly one evaluator emits even under concurrency.

Event typeFires when
app.spend_limit_warningRecorded period spend first reaches 80% of the limit.
app.spend_limit_exceededRecorded period spend first reaches 100% of the limit. A single jump that crosses both thresholds fires both, once each.

Fire-once is not re-armed by a mid-period limit change: raising or lowering the limit after a threshold event has been claimed for the current period does not re-fire it. The claims reset when the next period starts.

Delivery reaches only endpoints subscribed at crossing time. A threshold event is emitted to the webhook endpoints subscribed to it when the crossing is recorded — so subscribe before setting a limit to receive it. Because the crossing is fire-once per period, subscribing afterward does not backfill a missed notification. The claim and its webhook records are written in one transaction, so a crossing is either fully recorded-and-enqueued or not recorded at all (it retries on the next evaluation).

Both events carry the same small notification object inside the standard webhook envelope’s data field (this is a notification payload, not an API resource snapshot):

{
  "app_id": "app_default000",
  "period_start": "2026-07-01",
  "period_end": "2026-08-01",
  "limit_eur": 100.0,
  "spent_eur": 85.5,
  "threshold_pct": 80,
  "currency": "EUR"
}

threshold_pct is 80 for the warning and 100 for the breach. period_start / period_end are RFC 3339 full-dates (UTC). Subscribe to these events like any other on the Webhooks page.

Set or clear the limit

Provide monthly_spend_limit_eur (must be > 0 and finite — NaN/Inf are rejected) to set the cap; omit it to clear the cap back to unlimited. A portal user must be an organization Owner or Admin. An API key may set, change, or clear its own app’s limit — a mismatched app_id is rejected — so a leaked key can lift its own app’s cap; rotate compromised keys promptly.

# Set a €500/month cap
curl -X POST https://api.transcodely.com/transcodely.v1.AppService/UpdateSpendLimit 
  -H "Authorization: Bearer {{API_KEY}}" 
  -H "X-Organization-ID: {{ORG_ID}}" 
  -H "Content-Type: application/json" 
  -d '{"app_id": "app_k1l2m3n4o5", "monthly_spend_limit_eur": 500}'

# Clear the cap (omit the field) — back to unlimited
curl -X POST https://api.transcodely.com/transcodely.v1.AppService/UpdateSpendLimit 
  -H "Authorization: Bearer {{API_KEY}}" 
  -H "X-Organization-ID: {{ORG_ID}}" 
  -H "Content-Type: application/json" 
  -d '{"app_id": "app_k1l2m3n4o5"}'

See Set the spend limit in the API reference for the full request and response shape.

Read current spend

Any organization member can read an app’s current-period spend. warning_triggered and limit_reached report whether the fire-once threshold crossings have been recorded and their notifications enqueued this period — not that any webhook was successfully delivered (a period with no subscribed endpoint still flips these booleans). They follow the claim state, so they stay stable once set.

curl -X POST https://api.transcodely.com/transcodely.v1.AppService/GetSpend 
  -H "Authorization: Bearer {{API_KEY}}" 
  -H "X-Organization-ID: {{ORG_ID}}" 
  -H "Content-Type: application/json" 
  -d '{"app_id": "app_k1l2m3n4o5"}'
{
  "period_start": "2026-07-01T00:00:00Z",
  "period_end": "2026-08-01T00:00:00Z",
  "spent_eur": 85.5,
  "monthly_spend_limit_eur": 100,
  "currency": "EUR",
  "warning_triggered": true,
  "limit_reached": false
}

monthly_spend_limit_eur is omitted from the response when the app is unlimited.