Skip to main content

Quota enforcement

Every chargeable action on the agent side is wrapped in a consume call.

Happy path (within quota)

Quota exhausted

The marketplace never half-writes on overage — the transaction rolls back if newUsed > effectiveQuota.

No active subscription

The agent should bounce the user to subscribe, not crash.

Release (fixed metrics only)

Release on a rolling metric returns 422 — rolling counters never decrement.

Idempotency

clientReqId is the agent's responsibility. Always generate a UUID per logical attempt. If the request fails with a network error, retry with the same clientReqId. The marketplace's usage_ledger.clientReqId UNIQUE ensures the second write is a no-op and the response mirrors the first successful state.

// idempotent retry loop on agent side
let attempt = 0;
while (true) {
try {
const r = await fetch(consumeUrl, {
method: "POST",
body: JSON.stringify({ delta: 1, clientReqId }),
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
});
return r;
} catch (e) {
if (++attempt > 3) throw e;
await sleep(300 * attempt);
}
}

Reading current usage

GET /me/agents/:agentId/usage returns all metrics. GET /me/agents/:agentId/usage/:metricSlug returns one.

Both return the same envelope:

type MetricUsageDto = {
metricSlug: string;
displayName: string;
unit: string;
kind: "fixed" | "rolling";
baseQuota: number | null;
addonsBonus: number;
effectiveQuota: number | null; // null = unlimited
used: number;
remaining: number | null; // null = unlimited
resetsAt: string | null; // null for fixed; period end for rolling
};

Use this for dashboard widgets, not for permission checks (a check that uses cached data can let through over-budget calls). Always wrap chargeable actions in consume.

Period reset

When currentPeriodEnd passes and a rolling consume comes in, the handler zeros subscription_usage.used before incrementing. No cron, no separate job.

See also