Handling 402 responses
credit_insufficient, event_quota_exceeded — what they mean and how to recover.
Hatched uses HTTP 402 Payment Required for two conditions the caller can
fix by topping up or upgrading:
credit_insufficient— no pool has enough credits for the requested AI job.event_quota_exceeded— the monthly event quota for the plan is exhausted.
Plus 403 plan_feature_locked when a plan doesn't include the requested feature
at all (e.g. Free plan hitting /marketplace/*).
Envelope
All three errors share the canonical envelope:
{
"error": {
"code": "credit_insufficient",
"message": "Not enough credits for this AI job (need 1, have 0).",
"details": {
"required": 1,
"available": 0,
"welcome": 0,
"paid": 0,
"promo": 0,
"upgrade_url": "https://app.hatched.dev/dashboard/billing",
"top_up_url": "https://app.hatched.dev/dashboard/billing?action=top_up"
},
"requestId": "req_abc123"
}
}Do NOT retry
Neither 402 nor 403 are transient. Do not wrap them in exponential backoff. The SDK's built-in retry only kicks in for 429 and upstream 5xx; 402/403 are surfaced to the caller immediately.
Recover
credit_insufficient→ send the user todetails.upgrade_urlordetails.top_up_url(both open the Stripe portal).event_quota_exceeded→ back off untildetails.reset_at(first of next UTC month) or upgrade to a higher plan.plan_feature_locked→ prompt upgrade todetails.required_plan.
SDK recipe
import {
HatchedClient,
CreditInsufficientError,
EventQuotaExceededError,
PlanFeatureLockedError,
} from '@hatched/sdk-js';
const hatched = new HatchedClient({ apiKey: process.env.HATCHED_SECRET_KEY! });
try {
await hatched.events.send({ userId: 'u_1', type: 'lesson_completed' });
} catch (err) {
if (err instanceof EventQuotaExceededError) {
console.warn(
`Event quota exceeded (${err.used}/${err.limit}). Resets ${err.resetAt}.`,
);
redirect(err.upgradeUrl!);
} else if (err instanceof CreditInsufficientError) {
redirect(err.topUpUrl ?? err.upgradeUrl!);
} else if (err instanceof PlanFeatureLockedError) {
showUpgradePrompt(err.requiredPlan, err.upgradeUrl);
} else {
throw err;
}
}Response headers
Every authenticated response includes credit / quota metadata in headers so you can warn the operator before they hit the wall:
X-Credits-Remaining,X-Credits-Welcome-Remaining,X-Credits-Paid-Remaining,X-Credits-Promo-RemainingX-Event-Quota-Limit,X-Event-Quota-Used,X-Event-Quota-Remaining,X-Event-Quota-Reset-AtX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
Webhook signal
When a customer crosses 80% of their monthly event quota we emit one
usage.threshold_reached webhook event (limit_type: 'event_quota'). The
100% boundary is not webhooked — it is hard enforced via 402 and surfaced
in the dashboard banner.