Filtering and Editorial Signals
Overview
Every call to GET /recommend/v1/recommendations runs in two stages:
- Pre-personalization filtering — narrow the universe of candidate items down to the slice that is eligible for the request.
- Editorial reranking — apply newsroom signals on top of the model’s ranking.
Filtering decides which items are even allowed to compete for a slot. Editorial Signals then decide what is editorially encouraged, discouraged, or required for a given reader to see (or not see). In short: AI decides what is likely to engage a reader; filters and signals let your newsroom shape that output without retraining anything — the changes take effect on the very next recommendation request.
Pre-personalization filters
Scope recommendations to a specific section, content type, or both — before personalization runs — so your readers only see candidates that belong in the placement.
Pre-filters are more efficient than excluding the same items in your own client code: items that don’t match are never scored, never returned, and never compete with matching items for the slot.
The filter dimensions
| Query param | Required | Effect |
|---|---|---|
site_id | yes | Hard partition by website. A single tenant may host many sites; this scopes the catalog. |
section | no | Editorial section (e.g. politics, sports). Restricts to items tagged with that category. |
content_type | no | Content type (e.g. article, video, podcast). Restricts to items of that type. |
Examples
Homepage feed for an anonymous reader on the news site:
GET /recommend/v1/recommendations ?site_id=news.example.com &user_id=anon-abc-123 &num_results=10Returns up to 10 items from news.example.com.
Sports section page:
GET /recommend/v1/recommendations ?site_id=news.example.com &user_id=u-42 §ion=sports &num_results=20Returns up to 20 items in the sports category.
Video-rail module on the homepage:
GET /recommend/v1/recommendations ?site_id=news.example.com &user_id=u-42 &content_type=video &num_results=8Returns up to 8 video items only.
Politics-section video rail:
GET /recommend/v1/recommendations ?site_id=news.example.com &user_id=u-42 §ion=politics &content_type=video &num_results=6Returns up to 6 politics videos.
Key details
- No model changes needed. Adjustments to filtering happen at the point of serving recommendations: the underlying model stays completely intact.
- Content is always scoped to your site. Every request is automatically limited to your site’s catalog.
- Extra candidates are fetched behind the scenes. The system requests more results than you asked for, so that editorial exclusions and pinned items (see Editorial Signals below) can be applied without leaving gaps in the final response.
- Filter values must match exactly. Filters look for precise matches only: no partial or fuzzy matching. If your content taxonomy uses nested categories (e.g.
sports/nfl/scores), make sure values are fully resolved before sending them.
Query parameters
site_id and user_id are required; num_results defaults to 5 (range 1–50); section and content_type are the optional filters described above. For the full parameter table and the response shape, see the Query Parameters table in the API Developer Guide.
Editorial Signals
Editorial Signals are a layer of human-controlled ranking rules that sit on top of the Recommendations API’s machine-learned recommendations.
They let your newsroom override, nudge, or filter the model’s output at serving time — like filters, they take effect on the very next recommendation request (see Overview).
Signals are exposed through a dedicated service: the Signals API (/signals/v1).
The four signal types
There are four distinct signal types. Each one answers a different editorial question.
| Signal type | Editorial question | Effect on the ranked list |
|---|---|---|
| Boost | ”I want this to do better than the model thinks it will.” | Multiplies the item’s AI score by a weight between 1.0 and 10.0. Item rises naturally in the ranking. |
| Bury | ”I want this to do worse than the model thinks it will.” | Multiplies the item’s AI score by a weight between 0.0 and 0.9. Item falls in the ranking but isn’t removed. |
| Pin | ”This item must appear at position N.” | Moves the item to a specific 1-indexed slot, overriding its ranked position. Applies only to items already in the candidate set — see the caveat below. |
| Exclude | ”This item must not appear at all, regardless of relevance.” | Removes the item from the candidate set entirely. Takes precedence over every other signal, including Pin. |
Boost and Bury are soft — they bias the ranking. Pin and Exclude are hard — they override it. The reranker applies them in a deterministic order (exclude → boost/bury → sort → pin → truncate), so the outcome is predictable no matter how many signals stack up on the same response.
Two ways to drive signals
The Signals API is intentionally agnostic about who is creating signals. That gives you two complementary integration patterns, and most newsrooms will run both at once.
1. Rules-based, automated from the content ingestion process
This pattern produces signals as a side effect of content moving through your CMS or content pipeline. It is best for organization-wide editorial policy: rules that should always be true without anyone clicking a button.
There’s no user interface to manage rules, so implementing these within custom middleware code is required.
For Arc XP customers, that will typically entail extending the content synchronization integration to set Signals during the content ingest/update moment.
Common examples:
- Always favor staff-bylined content. When a story is ingested with a staff byline, your ingestion worker calls
POST /signals/v1/signalswith aboostof, say,1.5. Wire content from outside the newsroom no longer competes on equal footing with staff-produced reporting. - Promote breaking news automatically. When the CMS flags a story as breaking, your pipeline opens a
boostsignal with a higher weight (e.g.5.0) and a shortend_atwindow — typically 30 to 90 minutes. The story floods every reader’s feed for the duration of the breaking-news window and quietly steps back to AI-only ranking when it expires. - Enforce content category policy. Suppress unloved content categories during specific dayparts using a
burywith a weight of0.3and a scheduledstart_at/end_at.
2. Manual, driven by editorial curation tools
This pattern produces signals from a person clicking a button. It is best for moment-to-moment editorial judgment.
Common examples:
- Highlight a single story. A homepage editor clicks “Pin to slot 1” on a developing investigation. The curation tool calls
POST /signals/v1/signalswithsignal_type: "pin"andposition: 1. From that moment on, every reader’s personalized recommendation list opens with that story. - Quietly suppress a story. Legal asks for a piece to be pulled from recommendations while a correction is drafted. An editor clicks “Hide from recommendations.” The curation tool creates an
excludesignal. The story remains published and linkable, it just stops appearing in any Compass-driven module until the signal is removed. - Counter-program the algorithm. A producer notices that the model is over-saturating one beat. They apply a short-lived
buryto two or three specific items to rebalance the page in real time. - Time-boxed promotion. A live blog or live event needs to dominate the page for the duration of the event. Editorial sets a
pinwithstart_at= event start,end_at= event end. The pin self-removes when the event ends.
What this looks like at runtime
When a reader requests recommendations, the Recommendations API:
- Asks the AI model’s top candidates and scores.
- Loads every active signal.
- Hands the candidate list and the signals to the reranker, which:
- Drops any item under an active
excludesignal. - Multiplies each remaining item’s score by its
boostorburyweight. - Re-sorts by the adjusted score.
- Moves pinned items to their absolute positions, in ascending
positionorder (and synthesizes a coherent score so the response is internally consistent). A pin whose item is not in the candidate list is skipped. - Truncates to the requested page size.
- Drops any item under an active
- Returns the final list.
No model retraining. No batch job. No cache to bust.
The signal you wrote a second ago is the signal that ranks the next request.
Scheduling
Every signal supports optional start_at and end_at timestamps:
start_at— the signal does nothing until this time. Useful for scheduled promotions, or pre-staging top-news rules ahead of a known event.end_at— the signal stops applying at this time without you having to call back to delete it. Critical for breaking-news boosts (which should decay, not linger) and any time-boxed editorial campaign.
A signal with neither field is active immediately and active forever, until explicitly deleted via DELETE /signals/v1/signals/{id}.
Site isolation
All signals are scoped to a single site within your instance. You can boost, bury, pin, and exclude items in the context of one site without impacting any others.
Operational properties
- No retraining required. Signals are pure post-processing.
- Bounded volume. The list endpoint returns all matching signals in one response, because the reranker already loads the full set on every recommendation request.
- Editorial provenance. Every signal records its
created_byandcreated_attimestamp, so it is always possible to audit who applied what override and when. - Reversible. Deleting a signal restores pure AI ranking for that item on the next request. There is no “soft delete,” the change is immediate and complete.
When not to use signals
Signals are a precision tool. They are not a substitute for:
- Removing content from the index — if a story is unpublished, it should leave the content collection through normal CMS lifecycle, not via a permanent
excludesignal.
Signals API reference
All endpoints are under /signals/v1 and require tenant-scoped authentication.
| Method | Path | Purpose |
|---|---|---|
POST | /signals | Create a signal. Body: site_id, item_id, signal_type, optional weight / position / start_at / end_at. |
GET | /signals | List signals for the tenant. Optional filters: site_id, item_id, signal_type. |
GET | /signals/{id} | Fetch a single signal. |
DELETE | /signals/{id} | Remove a signal. Effective on the next recommendation request. |