Developers › API reference › Compliance
PATCH /api/v1/compliance/rules/{id}
Change a draft (If-Match).
- Scope
compliance:rules:write - If-Match
- operationId
complianceUpdate
The token needs the scope compliance:rules:write, and the person or service account behind it needs the matching privileges and account permissions, exactly as on the screens. See Scopes.
If-Match. Send If-Match with the ETag of your latest GET. If someone changed the resource since, you get 412 precondition_failed and nothing changes: GET it again, re-apply your change and retry.
Request
PATCH /api/v1/compliance/rules/{id}
Path parameters
| Name | Description | Example |
|---|---|---|
id | The id of the rule. | 41 |
Headers
| Header | Required | Value |
|---|---|---|
Authorization | yes | Bearer <token>: an OAuth access token (vat_…) or a personal access token (vpt_…). Get a token |
If-Match | yes | The ETag of your latest GET of this resource. ETags |
Content-Type | yes | application/json |
Body: RuleIn
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | The rule's name. |
rule_type | string | no | One of the 25 rule types: max_issuer_pct, max_sector_pct, max_position_pct, cash_minimum, restricted_list, wash_sale, fat_finger, udf_condition… |
params | object | no | The type's parameters, e.g. {"limit_pct": "5", "include_etfs": false} for max_issuer_pct. |
scope | object | no | What it applies to: {"type": "firm"|"group"|"package"|"account", "ids": [...]} (default: the firm). |
severity | string | no | "block", "warn" or "info". |
run_points | list of string | no | When the rule runs: pretrade (each order), intrade (each executed slice), posttrade (end-of-day holdings). |
change_of | any | no | To change an active rule: the id of the version you are replacing (a new version is drafted). |
source_text | string | no | Your wording, kept with the rule. |
Decimals (money, prices, quantities, percentages) are sent as strings, ids as strings, times as ISO 8601 UTC.
Examples
Keep the token in an environment variable (VESTORIS_TOKEN), never in the script.
curl
curl -X PATCH "https://vestoris.com/api/v1/compliance/rules/41" \
-H "Authorization: Bearer $VESTORIS_TOKEN" \
-H 'If-Match: "3f1a9c0e5b7d2a4c8e6f0b1d3a5c7e9f"' \
-H "Content-Type: application/json" \
-d '{"name":"Issuer limit 5% (Growth)","rule_type":"max_issuer_pct","params":{"limit_pct":"5","include_etfs":false},"scope":{"type":"package","ids":["3"]},"severity":"block","run_points":["pretrade","posttrade"],"change_of":null,"source_text":"No more than 5% of any Growth account in a single issuer, except ETFs."}'
Python (requests)
import os, requests
r = requests.patch(
"https://vestoris.com/api/v1/compliance/rules/41",
headers={
"Authorization": f"Bearer {os.environ['VESTORIS_TOKEN']}",
"If-Match": "\"3f1a9c0e5b7d2a4c8e6f0b1d3a5c7e9f\"",
},
json={
"name": "Issuer limit 5% (Growth)",
"rule_type": "max_issuer_pct",
"params": {
"limit_pct": "5",
"include_etfs": False
},
"scope": {
"type": "package",
"ids": [
"3"
]
},
"severity": "block",
"run_points": [
"pretrade",
"posttrade"
],
"change_of": None,
"source_text": "No more than 5% of any Growth account in a single issuer, except ETFs."
},
timeout=30,
)
r.raise_for_status() # errors are application/problem+json with a stable "code"
print(r.json())
JavaScript (fetch)
const res = await fetch("https://vestoris.com/api/v1/compliance/rules/41", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.VESTORIS_TOKEN}`,
"If-Match": "\"3f1a9c0e5b7d2a4c8e6f0b1d3a5c7e9f\"",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Issuer limit 5% (Growth)",
"rule_type": "max_issuer_pct",
"params": {
"limit_pct": "5",
"include_etfs": false
},
"scope": {
"type": "package",
"ids": [
"3"
]
},
"severity": "block",
"run_points": [
"pretrade",
"posttrade"
],
"change_of": null,
"source_text": "No more than 5% of any Growth account in a single issuer, except ETFs."
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.detail}`); // problem+json
console.log(data);
Response
200 OK, application/json: Rule. With an ETag header. Rate-limit headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset.
Response fields
| Field | Type | Description |
|---|---|---|
id | id (string) | The record's id (a string of digits). |
lineage_id | id (string) | The id shared by every version of the rule. |
version | integer | Version number. |
name | string | The rule's name. |
rule_type | string | The rule type (max_issuer_pct, max_sector_pct, restricted_list, wash_sale, udf_condition…). |
severity | string | "block", "warn" or "info". |
status | string | draft, pending_approval, active, retired or superseded. |
display_status | string | The status as the screens show it. |
pending_action | string | What is waiting for sign-off: "activate", "change" or "retire". |
definition | object | The structured rule the engine enforces. |
definition.params | object | Parameters. |
definition.scope | object | What the rule applies to. |
definition.scope.type | string | The type. |
definition.scope.ids | list of id (string) | Ids the scope covers. |
definition.run_points | list | When the rule runs: pretrade (each order), intrade (each executed slice), posttrade (end-of-day holdings). |
definition_hash | string | SHA-256 of the structured definition: the exact version that was signed off. |
restatement | string | What the engine will enforce, written by code from the structured definition. |
source_text | string | The words it was written from. |
drafted_by_ai | boolean | True when the AI drafted it (a person still checks and signs). |
test_bench | object or null | The latest test-bench result: what the rule would have blocked, warned on or logged. |
effective_from | date-time or null | When this version took (or takes) effect. |
effective_to | date-time or null | When this version stopped being in force (null: still in force). |
created_by | id (string) or null | The user who created it. |
created_at | date-time or null | When it was created. |
decided_by | id (string) or null | Who decided. |
decided_at | date-time or null | When the decision was made. |
decision_note | string | The note written with the decision. |
Example response
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "3f1a9c0e5b7d2a4c8e6f0b1d3a5c7e9f"
{
"id": "1042",
"lineage_id": "41",
"version": 1,
"name": "Issuer limit 5% (Growth)",
"rule_type": "max_issuer_pct",
"severity": "block",
"status": "active",
"display_status": "Active",
"pending_action": "",
"definition": {
"params": [],
"scope": {
"type": "text",
"ids": []
},
"run_points": [
"pretrade",
"posttrade"
]
},
"definition_hash": "9f2c…",
"restatement": "Block any order that would make one issuer more than 5.00% of a Growth account (ETFs excepted).",
"source_text": "No more than 5% of any Growth account in a single issuer, except ETFs.",
"drafted_by_ai": false,
"test_bench": null,
"effective_from": null,
"effective_to": null,
"created_by": "7",
"created_at": "2026-10-27T14:30:00Z",
"decided_by": null,
"decided_at": null,
"decision_note": ""
}
Example values. Your values depend on your firm's data; prices, orders and fills are simulated.
Status codes and errors
Errors are application/problem+json (RFC 9457) with a stable code to program against; the detail says what to fix. See Errors.
| Status | When | Problem codes |
|---|---|---|
| 200 | Success | |
| 400 | The request is malformed | invalid_request invalid_json |
| 401 | No token, or it is invalid or expired | invalid_token |
| 403 | The token lacks the scope, or the person or service account behind it lacks the privilege or account permission | insufficient_scope forbidden ip_not_allowed |
| 404 | Not found, or not visible to you (the API never confirms what you may not see) | not_found |
| 409 | Not possible in the current state | invalid_transition conflict |
| 412 | The resource changed since your GET: get it again and retry with the new ETag | precondition_failed |
| 422 | Validation failed (the detail says which field), or blocked | validation_failed |
| 428 | A required header is missing | precondition_required |
| 429 | Rate limited: wait for Retry-After seconds | rate_limited |
{
"type": "https://vestoris.com/developers/errors#validation_failed",
"title": "Validation failed",
"status": 422,
"detail": "Quantity must be greater than zero.",
"code": "validation_failed",
"instance": "/api/v1/compliance/rules/41"
}
On the screens
The same thing in the app is explained in: