Reliability
SigWise has two ways to get a verdict, and they fail differently. Pick the one that matches how much a late or missing answer costs you.
| Asynchronous (default) | Inline ("wait": true) | |
|---|---|---|
| Your request returns | 202 as soon as the events are recorded | 200 with the verdict, after the analysis |
| If the analyzer is slow or down | The analysis is retried in the background with exponential backoff. Your request is not affected | Your request waits, then fails |
| Best for | Scores that build over time, alerts, search | A decision you must make before something is published or paid |
Inline checks: decide what a failure means
An inline check puts the analyzer in your request path, so decide in advance what your product does when it does not answer. This is a product decision with no universal answer:
| Situation | Usual choice | Why |
|---|---|---|
| Publishing a message or listing | Fail open: publish, then score again in the background | A short outage should not stop your users from talking |
| Releasing a payout, changing a bank detail, sending an invite to many users | Fail closed: hold for review | The cost of letting a bad action through is higher than a delay |
| First message from a brand-new account | Hold until the verdict arrives, with a short budget | Most of the risk sits here |
Give the call a budget shorter than the server's limit (the server gives up on any request after 30 seconds), and treat running out of budget as a failure:
type Decision = "publish" | "hold" | "block";
const withBudget = <T>(p: Promise<T>, ms: number) =>
Promise.race([
p,
new Promise<T>((_, reject) => setTimeout(() => reject(new Error("budget")), ms)),
]);
async function checkMessage(userId: string, text: string): Promise<Decision> {
try {
const verdict = await withBudget(
sigwise.events.ingest(userId, {
wait: true,
signals: ["is_scammer"],
events: [{ type: "message", content: text }],
}),
3000,
);
const p = "answers" in verdict ? (verdict.answers[0]?.noul ?? 0) : 0;
if (p >= 0.9) return "block";
if (p >= 0.6) return "hold";
return "publish";
} catch {
// The events may or may not have been recorded. Publish now (fail open)
// and let the background analysis and your rules catch it afterwards.
return "publish";
}
}def check_message(user_id: str, text: str) -> str:
try:
verdict = sigwise.events.ingest(
user_id,
wait=True,
signals=["is_scammer"],
events=[{"type": "message", "content": text}],
)
answers = verdict.get("answers") or []
p = answers[0].get("noul", 0) if answers else 0
if p >= 0.9:
return "block"
if p >= 0.6:
return "hold"
return "publish"
except Exception:
# Fail open. Set a request timeout on the client (see your SDK's
# README) so this does not wait for the server's 30 seconds.
return "publish"Choose the thresholds from your own test, not from this example. See Test SigWise on your own data.
What each failure looks like
| Response | Meaning | What to do |
|---|---|---|
200 with analyzed: false | No signal matched, so nothing was scored | Treat as no verdict. The events are recorded |
402 | The prepaid balance is empty | Top up. Decide whether you fail open meanwhile |
429 | Too many requests or too many inline analyses at once | Wait for Retry-After. Fail open or hold, as above |
502 | The analysis provider failed | Fail open or closed, as above |
503 analyzer_busy | The provider is throttling the request | After Retry-After, ask for an analysis again or read the answers later |
| Timeout on your side | You do not know whether the events were recorded | Retry with the same Idempotency-Key |
Whether the events were recorded on a 502 or 503 depends on your retention policy. With the default, they were, and you should not send them again. With after_analysis, inline requests are never written, so nothing was recorded and you should resend. Sending the same Idempotency-Key makes a resend safe either way. See Errors and retries.
Asynchronous analyses
If an analysis fails, it is retried with exponential backoff, capped at five minutes between attempts, up to a fixed number of attempts, and then marked failed. If the provider is throttling, the analysis is put back until the time the provider asked for, without using up an attempt.
An empty balance is not retried, because retrying cannot succeed until you top up. The analysis is failed straight away. After you top up, the next event for the object schedules a new analysis, or you can request one with POST /v1/objects/{id}/analyze (API reference).
Analysis providers
SigWise can run on more than one analysis provider (see Sub-processors), and the provider for an account is set by SigWise. There is no automatic failover from one provider to another for a single request today. If a provider is having a long outage, ask for your account to be moved to the other one.
Measure latency yourself
Inline latency is dominated by the provider's response, which varies with how much history an object has and how many signals you score. Do not rely on a single number. Every inline response includes latency_ms, so log it for a week of real traffic and look at the 50th and 99th percentiles before you pick your budget. Scoring only the signals you need with signals keeps it lower.
Health endpoints
The API exposes GET /healthz (the process is up) and GET /readyz (the database is reachable). Use /readyz for load balancer checks.