Filters
- Processor — looks at each item in your feeds and records answers ("outputs"); for example Event labels
records
age_restricted.confidence: 0.92. A processor never includes or excludes anything. Built-in processors are ours; you can create your own. A processor belongs to your organization, not to a feed. - Filter — a rule on one feed: include or exclude items whose processor outputs meet a condition. An item is in the feed when no exclude filter matches and at least one include filter does. A feed runs exactly the processors its filters read.
A feed has three kinds of filter:
- The safety floor. Ours, on every feed. It excludes unsafe items, cannot be switched off and is not listed among a feed's filters.
- Opt-in filters. Ours, added by you. You can add one or remove it, but not edit it: its condition always follows our latest definition. See Opt-in filters.
- Your own filters. Your condition, fully under your control.
A new event feed starts with one filter, the completeness opt-in, which
leaves out events with too little detail to list. Remove it if your feed
should not have it. A new news feed starts with none.
Includes and excludes
An item is in the feed when no exclude filter matches and at least one include filter does. Excludes always win. Includes are alternatives: each include you add lets more items in.
This matters when you mix our opt-ins with your own filters. Your own include widens the feed, it does not narrow it. Say your feed has the "Community events" set, whose "In the area" include lets in events in your area, and you add this include:
{
"name": "Family events",
"action": "include",
"condition": {
"processor": "event_labels",
"output": "family_friendly.confidence",
"op": ">=",
"value": 0.8
}
}
The feed now carries events in the area and also family events from
anywhere. To carry only family events in the area, write one include that
requires both (and remove "In the area"), with your
area match processor's id in place of AREA_MATCH_ID:
{
"name": "Family events in the area",
"action": "include",
"condition": {
"all": [
{
"processor": "AREA_MATCH_ID",
"output": "match",
"op": "=",
"value": "match"
},
{
"processor": "event_labels",
"output": "family_friendly.confidence",
"op": ">=",
"value": 0.8
}
]
}
}
To leave something out, write an exclude: it removes what it matches whatever the includes say.
An enabled feed must keep at least one include filter. Removing its last
include, or turning it into an exclude, is refused with 422
LAST_INCLUDE_FILTER; add another include first, or disable the feed.
Enabling a feed (PATCH /feeds/events/{id} or /feeds/news/{id} with
enabled: true) is refused with 422 VALIDATION_FAILED while it has no
include. A new feed starts disabled: add an include, then enable it. An event
feed created with enabled: true is refused the same way, since a new feed
has no include yet.
Conditions
A condition is a leaf, or all / any of conditions:
{
"any": [
{
"processor": "event_labels",
"output": "age_restricted.confidence",
"op": ">=",
"value": 0.6
},
{
"processor": "event_category",
"output": "primary",
"op": "in",
"value": ["nightlife"]
}
]
}
A leaf is true when the processor recorded an output that meets the comparison:
| Field | Description |
|---|---|
processor | A built-in's key (e.g. event_labels), or the id of one of your processors. Built-ins always come back as their key. |
output | One of the processor's outputs (GET /processors/{id}), e.g. promotional.confidence, score or answer |
op | =, !=, >=, >, <=, < or in. number outputs take every operator; enum and boolean outputs take =, != and in |
value | A number in the output's range, one of an enum output's values, or true / false. With in, a list of them (1 to 100) |
all is true when every condition in it is true, any when at least one is.
A condition nests at most 3 levels of all / any deep and has at most 50
leaves; a deeper or larger one is refused with 400 INVALID_BODY, as is one
that is not a tree of leaves and groups (a leaf missing a field, an empty
all or any). The problems below are about what the leaves name, never the
condition's shape.
A filter can read the built-ins and your organization's processors that run on
the feed's item type and are not archived. Every leaf is checked when you
write it. A leaf that does not check out is refused with 422
INVALID_CONDITION, listing each problem with its path:
{
"success": false,
"error": {
"code": "INVALID_CONDITION",
"message": "The condition cannot be saved: condition.any[0].output: …",
"details": {
"problems": [
{
"path": "condition.any[0].output",
"code": "unknown_output",
"message": "…"
}
]
}
}
}
Problem code | Meaning |
|---|---|
unknown_processor | No such processor, or another organization's |
archived_processor | The processor is archived |
wrong_item_type | The processor does not run on the feed's item type |
not_readable | The processor has no output a filter can read |
unknown_output | The processor has no such output |
bad_operator | The operator does not suit the output's type |
bad_value | The value is out of the output's range, or not one of its values |
unknown_param | Opt-ins only: a parameter in with the opt-in does not take |
missing_param | Opt-ins only: a parameter the opt-in requires is missing, or areaRelevance is given without areaMatch |
wrong_template | Opt-ins only: the parameter's processor is not of the template the parameter takes |
The same codes are the details.problems of an opt-in's refusals, at paths
with.<parameter>: 422 MISSING_OPT_IN_PARAM for missing_param, and
422 INVALID_OPT_IN_PARAM for the others. A processor's own definition is
checked differently: a definition its template refuses is a 422
VALIDATION_FAILED on the processor route (see
Processors), not INVALID_CONDITION.
Changes apply going forward
Adding, changing or removing a filter applies to items evaluated from then on: new items, and items already in the feed when they next change. The feed is not re-evaluated when you save. An item that stops passing is removed from the feed when it is next evaluated, and a removal webhook is sent for it.
Who can see a feed's filters
Filters belong to the feed's owner. Another organization subscribed to the
feed gets 403 PERMISSION_DENIED on every filter route; any other
organization gets 404.
API reference
The routes are the same for both feed types: {type} is events or news.
| Route | Does |
|---|---|
GET /feeds/{type}/{id}/filters | The feed's own and opt-in filters, oldest first (never the safety floor) |
GET /feeds/{type}/{id}/filters/{filterId} | One filter |
POST /feeds/{type}/{id}/filters | Add your own filter, or one of our opt-in filters |
PATCH /feeds/{type}/{id}/filters/{filterId} | Change one of your own filters |
DELETE /feeds/{type}/{id}/filters/{filterId} | Remove a filter, own or opt-in |
POST /feeds/{type}/{id}/filter-sets/{setKey} | Add a set of opt-in filters. See Opt-in filters |
The filter
| Field | Description |
|---|---|
id | Stable identifier: the filterId of the routes above |
name | The filter's name. Free text, not unique |
action | include or exclude |
type | own (yours, editable) or opt_in (ours, removable only) |
optIn | Opt-ins only: key and with, the processors it was added with |
condition | Own filters: the condition as written. Opt-ins: the condition generated from our current definition, read-only. Built-ins named by their key |
createdAt, updatedAt | ISO 8601 |
Add a filter
POST /feeds/{type}/{id}/filters
Your own filter, with name, action and condition:
{
"name": "No family-unfriendly events",
"action": "exclude",
"condition": {
"any": [
{
"processor": "event_labels",
"output": "age_restricted.confidence",
"op": ">=",
"value": 0.6
},
{
"processor": "event_category",
"output": "primary",
"op": "in",
"value": ["nightlife"]
}
]
}
}
On a news feed, for example:
{
"name": "Strong news stories",
"action": "include",
"condition": {
"processor": "news_content_type",
"output": "editorial_news.significance",
"op": ">=",
"value": 7
}
}
Or one of our opt-in filters, with optIn and the processors it takes in
with (see Opt-in filters):
{
"optIn": "in_area",
"with": {
"areaMatch": "5c0e8f9a-3b1d-4f6e-9a2c-7d4b1e0f8a63",
"areaRelevance": "0a7d2c4e-9f1b-4e3a-8c6d-1b2e3f4a5c6d"
}
}
Response 200 OK, the filter:
{
"success": true,
"data": {
"id": "7f3c2b1a-6e5d-4c4b-9a3f-2e1d0c9b8a71",
"name": "In the area",
"action": "include",
"type": "opt_in",
"optIn": {
"key": "in_area",
"with": {
"areaMatch": "5c0e8f9a-3b1d-4f6e-9a2c-7d4b1e0f8a63",
"areaRelevance": "0a7d2c4e-9f1b-4e3a-8c6d-1b2e3f4a5c6d"
}
},
"condition": {
"any": [
{
"processor": "5c0e8f9a-3b1d-4f6e-9a2c-7d4b1e0f8a63",
"output": "match",
"op": "=",
"value": "match"
},
{
"processor": "0a7d2c4e-9f1b-4e3a-8c6d-1b2e3f4a5c6d",
"output": "answer",
"op": "=",
"value": "yes"
}
]
},
"createdAt": "2026-10-28T14:05:00.000Z",
"updatedAt": "2026-10-28T14:05:00.000Z"
}
}
An own filter comes back the same way, with type: "own", no optIn, and
its condition as you wrote it.
Change a filter
PATCH /feeds/{type}/{id}/filters/{filterId}
Send any of name, action and condition; the others stay as they are.
{
"condition": {
"processor": "event_labels",
"output": "age_restricted.confidence",
"op": ">=",
"value": 0.8
}
}
Only your own filters can be changed. An opt-in filter is refused with 403
OPT_IN_NOT_EDITABLE: remove it and add your own filter instead.
Remove a filter
DELETE /feeds/{type}/{id}/filters/{filterId}
Removes an own or opt-in filter. Returns { "success": true }.
Errors
| Status | Code | When |
|---|---|---|
400 | INVALID_BODY | The body is malformed, a condition is deeper than 3 levels or has more than 50 leaves, or optIn is not an opt-in of this feed type |
403 | PERMISSION_DENIED | Your organization subscribes to the feed but does not own it |
403 | OPT_IN_NOT_EDITABLE | A PATCH of an opt-in filter |
404 | NOT_FOUND | No such feed or filter, or a filter of another feed |
409 | FILTER_ALREADY_PRESENT | The opt-in decides the same as a filter the feed already has. details.equalTo is that filter's id |
422 | INVALID_CONDITION | A leaf does not check out. details.problems[{ path, code, message }] |
422 | MISSING_OPT_IN_PARAM | A processor the opt-in requires is missing. details.problems, paths with.<parameter> |
422 | INVALID_OPT_IN_PARAM | A processor the opt-in cannot take. details.problems, paths with.<parameter> |
422 | LAST_INCLUDE_FILTER | Removing the last include of an enabled feed, or turning it into an exclude |