/v1/saved-filters/{saved_filter_id}betaChange a saved filter
- Needs
- Create and edit contacts, tags, contact groups and custom fields
(crm:write) - Plan
- Any plan with API access
- Limits
- 60 a minute per key
- Dry run
- Yes — a full preview with ?dry_run=true
- Undo
- Change it back with the old values (read them first with Get a saved filter).
Renames a saved contact filter, changes its description, or replaces its filter. Only the staff member who saved it can change it. Fields you leave out stay as they are. changes lists the fields whose saved value differs.
Try it with ?dry_run=true first: every check the change runs (ownership included), and the answer is the saved filter as it would be after it, with changes; nothing is saved. Add count=true to also count the contacts it would match; if counting fails, the preview still answers (count: null and count_error).
Try it
Query (1)
With `dry_run=true`: also count the contacts it would match.
Code and response
curl -X PATCH 'https://mcp.wa-api.cloud/v1/saved-filters/7?dry_run=true' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "Dubai VIPs (2026)"
}'The code reads your key from $API_KEY.
Parameters
| Field | Type | What it is |
|---|---|---|
| saved_filter_idrequired | string | integer · path | The saved filter id.pattern ^[1-9]\d{0,18}$ |
| count | boolean · query | With dry_run=true: also count the contacts it would match. |
Body
| Field | Type | What it is |
|---|---|---|
| name | string | New name.1–255 characters |
| description | string or null | New description; null clears it.0–1000 characters |
| filter | object | New filter (replaces the old one). |
Headers
| Field | Type | What it is |
|---|---|---|
| Authorizationrequired | header | Bearer $API_KEY — your API key. |
| Api-Version | header | The API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$ |
Response 200
The saved filter as it is now (with `dry_run=true`: as it would be).
| Field | Type | What it is |
|---|---|---|
| saved_filterrequired | object | |
| idrequired | string | |
| namerequired | string | |
| descriptionrequired | string or null | |
| owner_staff_idrequired | string or null | |
| filterrequired | object | The definition as stored: the panel format, dates absolute. |
| updated_atrequired | string or null | null in a dry run. |
| changesrequired | array of string | Fields whose saved value differs after this change (empty: it changes nothing). |
| resolved_datesrequired | array of object | |
| pathrequired | string | |
| inputrequired | string | |
| valuerequired | string | |
| warningsrequired | array of object | Filter conditions that may not mean what they look like: {path, message}. |
| path | string | |
| messagerequired | string | |
| dry_run | boolean | true when this was a dry run: every check ran and nothing changed. |
| count | integer or null | With dryRun + count: contacts it would match now (null when the count failed: see countError).-9007199254740991–9007199254740991 |
| approximate | boolean or null | With count: the service counted approximately. |
| count_error | object | Why the count is missing. The preview itself is still valid. |
| coderequired | string | |
| messagerequired | string |
Errors
Errors are application/problem+json. Branch on code.
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | A field is missing or has the wrong format. |
| 401 | unauthenticated | The Authorization header is missing, the key is unknown, expired or revoked. |
| 403 | entitlement_required | The workspace's plan does not include API access ( |
| 403 | forbidden | Someone else saved it ( |
| 403 | insufficient_scope | The key does not have the permission this operation needs. |
| 404 | not_found | No saved filter with this id in your workspace, or the new filter names one that does not exist ( |
| 429 | rate_limited | The key or workspace went over its rate limit. Wait for |
| 503 | upstream_unavailable | A service behind the API is briefly unavailable. Safe to retry with backoff. |
| 504 | timeout | The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice. |
Examples
Rename a saved filter
Request body
{
"name": "Dubai VIPs (2026)"
}Response 200
{
"saved_filter": {
"id": "7",
"name": "Dubai VIPs (2026)",
"description": null,
"owner_staff_id": "501",
"filter": {
"attributes": {
"some": {
"attributeName": {
"eq": "city"
},
"stringValue": {
"eq": "Dubai"
}
}
}
},
"updated_at": "2026-09-30T08:05:00.000Z"
},
"changes": [
"name"
],
"resolved_dates": [],
"warnings": []
}Preview a new filter (dry run)
Request body
{
"filter": {
"createdAt": {
"gte": "-30d"
}
}
}Response 200
{
"saved_filter": {
"id": "7",
"name": "Dubai VIPs",
"description": null,
"owner_staff_id": "501",
"filter": {
"createdAt": {
"gte": "2026-08-31T08:00:00.000Z"
}
},
"updated_at": null
},
"changes": [
"filter"
],
"resolved_dates": [
{
"path": "filter.createdAt.gte",
"input": "-30d",
"value": "2026-08-31T08:00:00.000Z"
}
],
"warnings": [],
"dry_run": true
}Operation path
The same operation is also at POST /v1/ops/crm_update_saved_filter, with every field in the JSON body.