Docs / Guides
Rate limits
The per-project request limits on the Tend API, the headers that report them, and patterns for staying under them.
Last updated April 22, 2026
#Overview
Tend limits how many API requests a project may make per minute. This protects the platform for everyone and, occasionally, protects you from a loop you did not mean to write. The limit is measured per project and per environment, using a sliding one-minute window. Requests made with tnd_dev_ keys and tnd_live_ keys are counted separately.
Rate limits apply to calls to the Tend API. They do not apply to Tend calling your handlers. Delivery of scheduled runs and webhooks to your endpoints is governed by your concurrency limit, described in the Concurrency limits and queues guide, and by the 15-second webhook timeout.
#Limits by plan
| Plan | Requests per minute | Typical fit |
|---|---|---|
| Hobby | 60 | Side projects, prototypes, a handful of schedules |
| Pro | 600 | Production apps creating jobs from request handlers |
| Scale | 3,000 | High-volume producers, fan-out workloads |
| Enterprise | 10,000 (negotiable) | Sustained bulk enqueueing, dedicated pools |
#Response headers
Every API response includes headers describing your current standing, so you can throttle proactively instead of discovering the limit by hitting it.
| Header | Meaning |
|---|---|
RateLimit-Limit | Requests allowed per minute for this project and environment. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window has fully replenished. |
Retry-After | Only on 429. Seconds to wait before retrying. |
HTTP/1.1 200 OK
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 41
Content-Type: application/json#When you exceed the limit
Once the limit is exceeded, the API responds with 429 rate_limit_exceeded and a Retry-After header giving the number of seconds to wait. The request was not processed, so retrying is safe. Requests that create jobs should still carry an Idempotency-Key header, since a client that times out and retries can otherwise create duplicates.
{
"error": {
"code": "rate_limit_exceeded",
"message": "Request rate limit of 600 per minute exceeded. Retry after 12 seconds.",
"request_id": "req_01JA2N6QW3T8YB5XE9DKM7RF4C"
}
}Do not confuse this with 429 quota_exceeded, which applies only to Hobby projects that have used all 10,000 monthly runs. The two share a status code and mean different things: one clears in seconds, the other clears when the billing month rolls over or you upgrade. Branch on the code field, never on the status alone.
# Respect Retry-After when a 429 comes back
curl -i -X POST https://api.tendcomputer.com/v2/jobs \
-H "Authorization: Bearer $TEND_API_KEY" \
-H "Idempotency-Key: welcome-email-usr_4471" \
-H "Content-Type: application/json" \
-d '{"run_at": "2026-04-23T09:00:00Z", "target": "https://app.example-shop.dev/hooks/welcome", "payload": {"user": "usr_4471"}}'
# HTTP/1.1 429 Too Many Requests
# Retry-After: 12import time
from tend import Tend, RateLimitError
client = Tend(api_key=os.environ["TEND_API_KEY"], max_retries=0)
def create_with_retry(**params):
for attempt in range(5):
try:
return client.jobs.create(**params)
except RateLimitError as e:
time.sleep(e.retry_after or 2 ** attempt)
raise RuntimeError("still rate limited after 5 attempts")import { Tend, RateLimitError } from "@tend/sdk";
const client = new Tend({ apiKey: process.env.TEND_API_KEY, maxRetries: 0 });
async function createWithRetry(params) {
for (let attempt = 0; attempt < 5; attempt++) {
try {
return await client.jobs.create(params);
} catch (err) {
if (!(err instanceof RateLimitError)) throw err;
const waitMs = (err.retryAfter ?? 2 ** attempt) * 1000;
await new Promise((r) => setTimeout(r, waitMs));
}
}
throw new Error("still rate limited after 5 attempts");
}#SDK behavior
The official SDKs (Python tend 2.7.1, Node.js @tend/sdk 2.9.0, Go tend-go 2.4.2) handle 429 for you by default. They read Retry-After, wait, and retry up to two times before surfacing an error. Set max_retries (Python), maxRetries (Node.js) or the equivalent client option in Go to 0 if you prefer to manage backoff yourself, as in the examples above.
SDK retries respect Retry-After exactly. If you write your own client, add jitter on top: when a hundred workers all wait exactly 12 seconds and retry together, you have re-created the burst you were trying to escape.
#Staying under the limit
Most projects that hit the limit do so by making many small requests where a few larger ones would do. The following patterns account for nearly all avoidable usage.
- Use schedules instead of self-rescheduling jobs. A job that creates its own successor on every run spends a request per run. A cron schedule spends none.
- Prefer webhook results over polling. Registering a signed webhook delivery costs nothing per result. Polling
GET /jobs/{id}every few seconds costs a request each time. - Batch creation.
POST /jobs/batchaccepts up to 100 jobs per request and counts as one request. - List with the maximum page size. Pass
limit=200rather than paging 50 at a time when you genuinely need everything. - Read the headers. If
RateLimit-Remainingdrops below 10 percent ofRateLimit-Limit, slow down before the429arrives.
curl -X POST https://api.tendcomputer.com/v2/jobs/batch \
-H "Authorization: Bearer $TEND_API_KEY" \
-H "Idempotency-Key: digest-batch-2026-04-22-a" \
-H "Content-Type: application/json" \
-d '{"jobs": [
{"run_at": "2026-04-23T08:00:00Z", "target": "https://app.example-shop.dev/hooks/digest", "payload": {"user": "usr_101"}},
{"run_at": "2026-04-23T08:00:00Z", "target": "https://app.example-shop.dev/hooks/digest", "payload": {"user": "usr_102"}}
]}'jobs = [
{
"run_at": "2026-04-23T08:00:00Z",
"target": "https://app.example-shop.dev/hooks/digest",
"payload": {"user": f"usr_{i}"},
}
for i in range(101, 201)
]
result = client.jobs.create_batch(jobs, idempotency_key="digest-batch-2026-04-22-a")
print(len(result.jobs)) # 100const jobs = Array.from({ length: 100 }, (_, i) => ({
runAt: "2026-04-23T08:00:00Z",
target: "https://app.example-shop.dev/hooks/digest",
payload: { user: `usr_${101 + i}` },
}));
const result = await client.jobs.createBatch(jobs, {
idempotencyKey: "digest-batch-2026-04-22-a",
});
console.log(result.jobs.length); // 100#Requesting higher limits
If you are running into the ceiling for your plan with well-behaved traffic, the first option is to move up a plan: Pro allows 600 requests per minute and Scale allows 3,000. Beyond that, Enterprise projects start at 10,000 requests per minute with negotiated limits and dedicated concurrency pools. Contact your support engineer or write to sales@tendcomputer.com with your project ID and a description of your traffic shape, including peak requests per second and how long the peaks last.