Handling outages
What should happen when your code calls check() and Agreely cannot be reached?
Agreely's answer is deny, by default, everywhere. This guide explains why,
and how to make a narrow, audited exception when you genuinely need one.
Fail-closed is the default
When Agreely is unreachable (a 503, a timeout, or a network error):
check()returnsfalse(deny);checkDetailed()throwsAgreelyUnavailableError;- the CLI exits
4(distinct from a deny's10).
A real 200 deny is never affected by any of this. An outage is not a denial,
but the safe default for an outage is to behave as if denied, so an
infrastructure problem can never silently grant access to data.
Distinguish outage from deny
Use checkDetailed() (or the CLI exit code) when you need to tell the two apart. A deny is an authoritative answer you must honor; an outage is a transient failure you may retry, alert on, or, for a chosen category, degrade. check() collapses both to false on purpose, so the simplest integration is always safe.
The fail-open exception
Some categories are low-risk enough that denying them during an outage is worse than allowing them (for example, a non-sensitive analytics signal). The SDKs let you fail open for such categories, but only through independent gates, so it can never happen by accident and is always audited.
TypeScript: three gates
const agreely = new Agreely({
apiKey,
degradeOnOutage: {
mode: "fail-open", // gate 1a: the explicit word
categories: ["Browsing/usage"], // gate 1b: ONLY these may ever degrade
maxOutageWindow: "5m", // refuse to degrade past this
onDegrade: (ctx) => audit.log(ctx) // MANDATORY, without it, the constructor throws
},
});
// gate 2: the call must ALSO opt in. Effective only because the category is
// allow-listed. Without the config, this per-call opt-in still denies.
await agreely.check("cust_8812", "Browsing/usage", "Analytics", { onOutage: "allow" });
// gate 3 (operator): break-glass, for an active incident.
agreely.breakGlass.engage({ reason: "incident-4471", ttl: "30m", scope: ["Browsing/usage"] });
PHP: two gates
PHP ships the same config allow-list + per-call opt-in. It omits break-glass in v1, because a request-scoped runtime cannot hold a fleet-wide "engaged" state honestly (see the PHP SDK).
$agreely = new Agreely([
'apiKey' => $key,
'degradeOnOutage' => [
'mode' => 'fail-open',
'categories' => ['Browsing/usage'],
'maxOutageWindow' => '5m',
'onDegrade' => fn ($ctx) => $audit->log($ctx), // MANDATORY
],
]);
$agreely->check('cust_8812', 'Browsing/usage', 'Analytics', ['onOutage' => 'allow']);
The guardrails
What fail-open will not let you do
- No silent degrade.
onDegradeis mandatory; without an audit sink the constructor throws. Every degraded allow emits an evidence record, and every break-glass-authorized allow emits its own event. - No unbounded window. Both
ttlandmaxOutageWindoware capped at 24h; over the cap throwsAgreelyConfigError. - No allow-cache. There is no cache of allow decisions; fail-open is computed per call during an outage, never replayed from a stored allow.
- No accidental opt-in. A per-call
onOutage: "allow"with no matching allow-listed category has no effect (the check still denies) and logs a one-time warning.
Tuning 429s, not outages
A rate limit (429) is not an outage: it is a signal to slow down. Two
constructor options tune it, without ever touching the fail-closed policy.
maxRetries (default 0) adds extra attempts on a 429 only and only for
idempotent calls (GET and check); a mutating call never retries.
respectRetryAfter (default true) honors the server's Retry-After header. An
outage, by contrast, is never silently retried: it denies (or throws
AgreelyUnavailableError) and leaves the decision to you.
Recommendation
Keep the default. Fail-closed is correct for anything that touches sensitive data
or carries a compliance obligation, which is most of what you will gate. Reach
for fail-open only for a specific, low-risk category where availability genuinely
outweighs strict enforcement, and wire its onDegrade sink into the same audit
trail you would show a regulator.
Next
- TypeScript SDK and PHP SDK degrade reference.
- Core concepts on why the check is fail-closed by design.