On this page

API REFERENCE

Decide

POST/v1/decide

Score one message against your rules and get back a verdict: allow, flag or block, with the winning rule as the reason. One synchronous call, under a second round trip.

View as .mdJSON Schema
ROUND TRIP< 1 s
TIMEOUT2 s → 503
RULESup to 16
IDEMPOTENCY24 h

Quickstart

  1. 1

    Get your test key

    Every new workspace starts with a dk_test_ key, shown once at sign-up. Lost it? Create another in API keys. Test keys run the same model and never count toward your allowance.

  2. 2

    Send one message and your rules

    Copy the example request. Set SIGILLINE_API_KEY and SIGILLINE_API_URL in your environment.

  3. 3

    Act on the verdict

    allowDeliver the message.

    flagDeliver or hold it, and queue it for a person to review.

    blockDon't deliver it. Show reason.name to your moderators.

Authentication

Send your key as a bearer token. Keys belong to one project. Call the API from your server, never from a browser or app.

dk_live_…Production. Each successful call counts as one decision.
dk_test_…Same model, same response. Never counts. usage.decisions is 0.

A missing or unknown key returns 401 unauthenticated. A revoked key returns 403 forbidden.

Headers

Authorizationrequired

Bearer dk_live_… or dk_test_….

Content-Typerequired

application/json

Idempotency-Keyoptional

Up to 255 printable ASCII characters. Replays the stored response for 24 hours without billing again.

X-Request-Idoptional

Your id for the call, echoed back. Generated when absent. Quote it when you contact support.

The API rejects anything over a limit with 400. It never truncates.

schema_versionstringrequired

The contract version you built against. Use "2026-10-18".

taskstringrequired

"moderation". Other task names are reserved and return 400 today.

messagestringrequired

The message to decide on. Up to 2,000 characters. Not stored unless history is on.

channel"public" | "private"required

Where it was sent. Rules scoped to the other channel are skipped, not scored.

sender_role"viewer" | "performer" | "mod"required

Who sent it.

rulesobject[]required

1 to 16 rules. Order matters: it is the final tie-break.

rules[].keystringrequired

Your stable id: 1–64 letters, digits, _ or -. Unique within the request.

rules[].namestringrequired

Up to 64 characters. Returned as reason.name.

rules[].descriptionstringrequired

Up to 240 characters. What the rule catches, in plain words. This is what the model reads.

rules[].action"allow" | "flag" | "block"required

What a match means.

rules[].thresholdnumberoptional

0 to 1. A score at or above it matches. Defaults to 0.5.

rules[].examplesstring[]optional

Up to 3, each up to 120 characters. Short real messages the rule should catch.

rules[].channelsstring[]optional

"public", "private" or both. Defaults to both.

historyobject[]optional

Up to 5 earlier turns, oldest first. Each is { sender_role, text } with text up to 500 characters.

external_idstringoptional

Your id for the message. Echoed back in the response.

languagestringoptional

A hint such as "en". The response reports the language used.

rulebook_idstringoptional

Reserved. Sending it returns 400 until saved rulebooks can be used on this call. Sending it together with rules also returns 400.

includeTtcbooleanoptional

When true, the response includes ttcMs. Defaults to false.

idstring

The decision id. Always present, even when history is off.

schema_versionstring

The contract version. The current version is "2026-10-18".

taskstring

"moderation".

verdict"allow" | "flag" | "block"

The strictest action among matched rules, or allow when none matched.

confidencenumber

The winning rule’s score. With no match, 1 minus the highest score. Not a calibrated probability.

reasonobject | null

The winning rule’s key, name and description, exactly as you sent them. Null when nothing matched.

rulesobject[]

Every scored rule, in request order: key, action, score, threshold, matched.

skipped_rulesobject[]

Rules that were not scored, each with a reason such as "channel".

languageobject

code and supported. Unsupported languages cap confidence at 0.2; the verdict stands.

modelobject

id and version of the model that scored the call. A threshold tuned on one version does not carry to another.

usageobject

decisions is 1 for a counted call and 0 for a replay or test key. quota_remaining is the organization’s remaining allowance (omitted for test keys).

external_idstring

Echoed when you sent one.

ttcMsnumber

Milliseconds from receiving the request to the response. Present when the request set includeTtc.

How the verdict is computed

The model returns a score per rule. The verdict is computed from those scores the same way every time.

  1. 01Only scored rules count. Rules scoped to another channel are skipped.
  2. 02A rule matches when its score is at or above its threshold.
  3. 03Among matched rules the strictest action wins: block, then flag, then allow.
  4. 04Within the same action, the higher score wins. If scores tie, the earlier rule in the request wins.
  5. 05confidence is the winning rule’s score.
  6. 06If nothing matched, the verdict is allow, reason is null and confidence is 1 minus the highest score.
  7. 07If the language is unsupported, the verdict is unchanged and confidence is capped at 0.2.
NOTE

An allow rule never cancels a matched flag or block. It only wins when nothing stricter matched.

Every error has the same shape: error.type, code, message and request_id. Branch on code. The message names the field and the limit, and never quotes the chat text. Errors are never billed.

STATUSCODEWHENRETRY
400invalid_requestSchema or limit failed. The message names the field.No. Fix the request.
401unauthenticatedMissing or unknown key.No.
403forbiddenRevoked or expired key.No.
409idempotency_conflictSame Idempotency-Key, different body.No. Use a new key.
413input_too_largeRules and message exceed the model’s input budget.No. Shorten rules.
429quota_exceededThe allowance is used up.No. Wait or upgrade.
429rate_limitedPer-key rate limit.Yes, after Retry-After.
503model_unavailableModel down or queue full.Yes, with backoff.
503internal_errorA failure other than the model. The body is not allow.Yes, with backoff.

400invalid_request

Schema or limit failed. The message names the field.

RetryNo. Fix the request.

401unauthenticated

Missing or unknown key.

RetryNo.

403forbidden

Revoked or expired key.

RetryNo.

409idempotency_conflict

Same Idempotency-Key, different body.

RetryNo. Use a new key.

413input_too_large

Rules and message exceed the model’s input budget.

RetryNo. Shorten rules.

429quota_exceeded

The allowance is used up.

RetryNo. Wait or upgrade.

429rate_limited

Per-key rate limit.

RetryYes, after Retry-After.

503model_unavailable

Model down or queue full.

RetryYes, with backoff.

503internal_error

A failure other than the model. The body is not allow.

RetryYes, with backoff.

FAIL CLOSED

A 503 is never an allow. If the model can't answer, you get an error, not a guess. Decide your fallback in code: hold the message, or deliver it and review later.

Retries and idempotency

Send an Idempotency-Key, such as your message id, on every call. A retry with the same key and body returns the stored response for 24 hours, with usage.decisions: 0, so it isn't billed twice. The same key with a different body returns 409. The stored response contains no message text.

Retry rate_limited after Retry-After seconds, and model_unavailable with exponential backoff. Don't retry other errors unchanged.

Limits

16rules per call
2,000characters per message
5history turns
500characters per history turn
64characters per rule name
240characters per description
3examples per rule
120characters per example
24 hidempotency window

If the rules and message together exceed the model's input budget, the call returns 413 input_too_large. Shorten descriptions and examples first.

Versioning, usage and data

Versioning. schema_version pins the contract you built against. A breaking change ships under a new date. The current version is 2026-10-18.

Usage. One successful call is one decision, however many rules it scores. Errors, replays and test keys don't count. Past the allowance, calls return 429 quota_exceeded.

Data. Message text is not stored. Sigilline keeps the verdict, the winning rule key, language and latency. History is opt-in per project.

Language. English is supported in v1. Other languages still get a verdict, with confidence capped at 0.2.