Standing rules
Inject your organization's standing rules into chat as defaults. Rules are on for signed-in keys and off for developer keys unless you opt in.
Standing rules are instructions your organization sets for chat, such as
"Always answer in British English." A vault can have its own. When rules
apply, the gateway injects them into the turn as defaults that the user's own
message overrides, never as constraints that outrank the user. The rules
field on a chat request turns injection on or off for that request.
Defaults depend on the key
| Key | Rules when rules is omitted |
|---|---|
| Signed-in credential: device login, OAuth login, and the Cruxy web app, desktop, CLI, and mobile | Injected |
| Developer API key or headless key | Not injected, unless the key was issued with allowed scopes that name the rules family |
An unscoped developer key has every family but has not opted in, so it gets no rules by default. A key's rules are its owner's organization's guidance, and a developer key often serves people other than its owner.
Opt in
A developer key opts in one of two ways:
- Per request: send
rules: true. - Once, on the key: issue the key with allowed scopes that name the
rulesfamily. Every request from it then gets rules unless it sendsrules: false.
curl https://api.cruxy.ai/api/v1/chat \
-H "Authorization: Bearer $CRUXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "vaani",
"stream": false,
"rules": true,
"messages": [
{ "role": "user", "content": "Draft a two-line status update for the launch." }
]
}'const res = await fetch('https://api.cruxy.ai/api/v1/chat', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CRUXY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'vaani',
stream: false,
rules: true,
messages: [{ role: 'user', content: 'Draft a two-line status update for the launch.' }],
}),
})
const completion = await res.json()
console.log(completion.message.content)
console.log(completion.cruxy.rules_applied) // { count, sources }, or undefinedrules: true needs a key whose allowed scopes include rules. A key without
it gets 403 key_scope_forbidden with required_scope: "rules", refused
before anything is billed, and such a key never gets rules. rules: false
needs no scope: any key may send it to decline rules for one turn, since the
user's own message already overrides them.
What gets injected
The switch covers the whole resolved ruleset: a turn gets exactly what
GET /api/v1/rules/resolved returns for its context, or nothing.
- No
vault_id: the organization's rules. - A
vault_idfor a vault that inherits (the default): the organization's rules first, then the vault's own. - A
vault_idfor a vault that does not inherit: the vault's own rules only.
A vault_id you cannot read is treated as none, so the turn runs with
organization rules. Injected rule text is real input and counts in
usage.input_tokens. Rules are separate from memory: the memory field never
affects them. POST /v1/chat/completions never injects rules.
Preview the rules a turn would get. Pass vault_id as a query parameter for a
chat inside a vault:
curl https://api.cruxy.ai/api/v1/rules/resolved \
-H "Authorization: Bearer $CRUXY_API_KEY"const res = await fetch('https://api.cruxy.ai/api/v1/rules/resolved', {
headers: { Authorization: `Bearer ${process.env.CRUXY_API_KEY}` },
})
const resolved = await res.json()
for (const rule of resolved.rules) {
console.log(rule.source, rule.text) // "org" or "vault", then the instruction
}Each rule carries its source, org or vault, so an unexpected instruction
traces back to the ruleset that contributed it. An empty rules array is a
real answer: there is no default ruleset. Unlike chat, this endpoint answers
404 vault_not_found for a vault_id you cannot read, so a mistyped id is
reported rather than resolved for the wrong context.
Know when rules applied
When at least one rule applied, the response carries cruxy.rules_applied:
how many rules applied and from which rulesets, never the rule text. The field
is absent when nothing was injected. On a stream it rides the role primer.
{
"cruxy": {
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"routed_tier": "vaani",
"reasoning_effort": "low",
"rules_applied": {
"count": 3,
"sources": [{ "scope": "org", "count": 3 }]
}
}
}Edit the rules
GET /api/v1/rules returns one stored ruleset, the organization's or, with
vault_id, a vault's, and PUT /api/v1/rules replaces it whole. Its
permissions.can_write tells you whether you may change it: the
organization's ruleset is owner or admin only, and a vault's also admits a
member the vault grants can_write. A refused write is 403
rules_forbidden.
Rules are re-injected on every request in scope, so each ruleset is capped:
| Cap | Limit |
|---|---|
| Rules per ruleset | 50 |
| Characters per rule | 1000 |
| Characters in total | 8000 |
Exceeding a cap is 400 rules_too_large. A rule carrying an email address,
a phone number, or a card number is refused with 400 pii_rejected, and
nothing is stored. API Reference has every field of
the rules endpoints.
Next steps
- Memory: recall and capture, with the same default split.
- Get your API key: allowed scopes and other key restrictions.
- Chat: the rest of the request and the
cruxyblock.