Errors & retries
The SDK throws a small, typed hierarchy so you can handle failure precisely. Every
error extends EdaError.
The hierarchy
| Class | HTTP | Thrown when | Typical handling |
|---|---|---|---|
EdaDeniedError | — | You used a guard/protect wrapper and the action was blocked or denied. | Tell the model/user why (.reason). |
EdaAuthError | 401 | API key missing, malformed, or revoked. | Fix the key; don’t retry. |
EdaQuotaError | 402 | Plan quota exceeded / payment required. | Surface upgrade path; back off. |
EdaForbiddenError | 403 | Key valid but not allowed for this resource. | Check scopes/workspace. |
EdaRateLimitError | 429 | Too many requests. | Retried automatically with backoff; respect retryAfter. |
EdaTimeoutError | — | Request exceeded timeoutMs. | Retried (idempotent); then failMode applies. |
EdaNetworkError | — | DNS/socket/transport failure. | Retried; then failMode applies. |
EdaPendingTimeoutError | — | checkAndWait/waitForApproval timed out before a human decided. | Return “still pending”; resume later via getDecision. |
EdaConfigError | — | Misconfiguration (e.g. no API key at all). | Fix at startup; not retryable. |
EdaApiError | 4xx/5xx | Any other API error. | Inspect .status / .code. |
import {
EdaDeniedError,
EdaAuthError,
EdaQuotaError,
EdaRateLimitError,
EdaPendingTimeoutError,
} from "@eda-holding-inc/sdk";
try {
await refund({ amount: 900_00 });
} catch (err) {
if (err instanceof EdaDeniedError) return replyToUser(err.reason);
if (err instanceof EdaPendingTimeoutError) return replyToUser("Awaiting approval — I'll follow up.");
if (err instanceof EdaAuthError) return alertOncall("Eda key rejected");
throw err;
}check() itself does not throw on a blocked/denied decision — it returns a
Decision you branch on. The EdaDeniedError only comes from the
guard/protect wrappers and adapters, which throw so a denied tool call
short-circuits your agent loop.
Fail mode
When a check can’t complete (network, timeout, 5xx after retries), the client
applies your failMode:
"closed"(default) — the decision resolves to not approved; the action does not run. Safe by default."open"— the decision resolves to approved; availability wins.
const eda = new Eda({ apiKey: process.env.EDA_API_KEY!, failMode: "closed" });Retries
Idempotent calls (check, getDecision, listActions, usage, health) retry
automatically on 429, timeouts, and transient 5xx/network errors, using
exponential backoff with jitter. Configure or disable it:
const eda = new Eda({
apiKey: process.env.EDA_API_KEY!,
retry: {
retries: 3, // attempts after the first
minDelayMs: 200,
maxDelayMs: 3_000,
factor: 2, // exponential base
},
});
// or turn it off entirely
const strict = new Eda({ apiKey: process.env.EDA_API_KEY!, retry: false });EdaRateLimitError carries the server’s retryAfter (seconds) when present; the
retrier honors it.
Observability
eda.stats() returns client-side counters (checks, approved, blocked,
pending, errors, cacheHits) you can push to your metrics pipeline to watch how
often agents are being gated — and how often Eda is in the hot path.