REFERENCE
Errors & rate limits
The API uses conventional HTTP status codes and returns a structured error body. Build for failure: retry transient errors with backoff, and surface the rest clearly.
Error shape
{
"error": {
"type": "rate_limit_error",
"code": "too_many_requests",
"message": "You have exceeded your requests-per-minute limit.",
"request_id": "req_a1b2c3"
}
}Always log request_id, it lets us trace a specific call if you contact support.
Status codes
| Status | Meaning | What to do |
|---|---|---|
400 | Invalid request | Fix the payload; do not retry unchanged. |
401 | Bad or revoked key | Check the API key. |
402 | Insufficient credits | Top up the wallet (see below). Do not retry unchanged. |
403 | Not permitted | Key lacks scope for this action. |
404 | Not found | Check the resource or endpoint. |
429 | Rate limited | Back off and retry (see below). |
500 / 503 | Server error / overloaded | Retry with backoff. |
Insufficient credits (402)
Metered T1 calls debit your wallet. When it's empty the API returns 402 with the failing balance and a URL to top up:
{
"error": "insufficient_credits",
"balance": 0,
"topUpUrl": "https://platform.taskclan.com/billing/recharge",
"requestId": "req_a1b2c3"
}The SDK wraps this as a TaskclanError whose type is "insufficient_credits" and whose balance and topUpUrl are populated. The recommended catch, safe across realms and bundlers (no instanceof required):
import { TaskclanError } from "@taskclan/sdk";
try {
await taskclan.run({ goal: "…" });
} catch (e) {
if (TaskclanError.isInsufficientCredits(e)) {
// e.balance, e.topUpUrl, e.requestId are all populated.
// Prompt the operator to top up, then retry when the wallet is funded.
return promptTopUp(e.topUpUrl);
}
throw e;
}In a browser app, redirect to e.topUpUrl (or open it in a new tab) so the user lands on the recharge page. In a headless worker, page the operator — a 402 will not clear itself on retry.
GET /api/t1/credits/balance).Rate limits
Limits are applied per key. Every response includes headers so you can pace requests:
| Header | Meaning |
|---|---|
x-ratelimit-limit | Requests allowed in the window |
x-ratelimit-remaining | Requests left in the window |
x-ratelimit-reset | Seconds until the window resets |
Retrying with backoff
Retry 429, 500 and 503 with exponential backoff and jitter. The SDK does this for you by default; here is the shape:
async function withRetry(fn, tries = 4) {
for (let i = 0; i < tries; i++) {
try {
return await fn();
} catch (err) {
if (!err.retryable || i === tries - 1) throw err;
const wait = Math.min(1000 * 2 ** i, 8000) + Math.random() * 250;
await new Promise((r) => setTimeout(r, wait));
}
}
}Idempotency-Key header on writes so a retried request is not executed twice.