Filtering and updated_since
Every filter on GET /sops and GET /assignments, what counts as a change to an SOP, and the exact polling pattern that never misses one.
On this page
Filters at a glance
| Endpoint | Parameter | Type | Effect |
|---|---|---|---|
GET /sops |
library_id |
UUID | Only SOPs in this library. |
GET /sops |
board_id |
UUID | Only SOPs in libraries on this board. |
GET /sops |
updated_since |
ISO 8601 date or date-time | Only SOPs whose updated_at is at or after this time. |
GET /sops |
include_archived |
true or false |
Add the workspace’s own archived SOPs. Default false. |
GET /assignments |
status |
open, overdue or completed |
Only assignments in this state. open includes overdue ones. |
GET /assignments |
assignee_email |
Only this member’s assignments. Not case sensitive. | |
GET /assignments |
sop_id |
UUID | Only this SOP’s assignments. |
GET /search |
q |
string | The search query. See Search. |
Filters combine with AND. An id that matches nothing (a library in another workspace, say) returns an empty list, not an error. A value in the wrong format returns 422 invalid_parameter.
updated_since
updated_since accepts:
- a date,
2026-09-01, meaning midnight UTC at the start of that day; - a date-time with
Z,2026-09-01T14:30:00Zor2026-09-01T14:30:00.123456Z; - a date-time with an offset,
2026-09-01T10:30:00-04:00.
Always send Z or an offset. A date-time without either is interpreted as UTC. In a query string, an offset’s + must be encoded as %2B, or it arrives as a space and the value is refused; curl -G --data-urlencode, URLSearchParams and requests do this for you.
The comparison is inclusive (updated_at >= updated_since), so an SOP that changed exactly at your checkpoint comes back again. That is deliberate: de-duplicating by id is cheap, missing a change is not.
What counts as a change
An SOP’s updated_at moves when:
- its own fields change: title, description, tags, owner, published flag or review date;
- it is archived or restored;
- it is moved to another library;
- any of its cards or steps are added, edited, reordered or removed.
Things that happen around an SOP, such as a new assignment, a comment or someone viewing it, don’t change updated_at.
The sync pattern
GET /sops is ordered by updated_at ascending (then id), so a sync is a simple loop:
- Load your checkpoint: the largest
updated_atyou have stored. On the first run there is none. - Call
GET /sops?updated_since=<checkpoint>&include_archived=true&limit=100and page through withcursoruntilhas_moreisfalse. - For each SOP, compare its
updated_atwith your copy. If it’s newer, fetchGET /sops/{id}and store it. - After the last page, save the largest
updated_atyou saw as the new checkpoint.
import os
import requests
BASE = "https://app.sopcompass.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SOPCOMPASS_API_KEY']}"
def changed_sops(checkpoint):
params = {"include_archived": "true", "limit": 100}
if checkpoint:
params["updated_since"] = checkpoint
while True:
page = session.get(f"{BASE}/sops", params=params, timeout=30).json()
yield from page["data"]
if not page["has_more"]:
return
params["cursor"] = page["next_cursor"]Use the SOP’s own updated_at string as the checkpoint, never your server’s clock: the two can disagree, and the API’s value is what the next comparison uses. ISO 8601 strings in this format sort correctly as text.
Why include_archived=true
Archiving an SOP changes its updated_at, but without include_archived=true an archived SOP drops out of the list, so your sync would never see it go. With it, the SOP comes back with archived_at set and you can remove or hide your copy. Restoring it sets archived_at back to null.
What sync can’t see
- Deleted SOPs and deleted libraries disappear from the API without a trace. To catch them, run an occasional full pass (no
updated_since) and remove anything you have that the pass didn’t return. - Libraries that stop being shared into the workspace take their SOPs with them. A full pass catches those too.
- Archived SOPs in shared-in libraries aren’t returned even with
include_archived=true; the app’s Archived page shows only the workspace’s own. They drop out of the list, and a full pass catches them.
See Keep a knowledge base in sync for a complete, runnable version.
Assignment filters
curl -G https://app.sopcompass.com/api/v1/assignments \
--data-urlencode "status=overdue" \
--data-urlencode "assignee_email=dana.whitfield@example.com" \
-H "Authorization: Bearer $SOPCOMPASS_API_KEY"overduemeansdue_onis before today’s date in UTC and the assignment isn’t completed.openmeans not completed, so it includes overdue ones. For “open but not overdue”, filteropenand drop items whosestatusisoverdue.assignee_emailmatches the member’s sign-in email. An email that isn’t a member returns an empty list.GET /assignmentshas noupdated_since. To track completions, page from newest and stop once you reach assignments older than anything that could still be open for you, or filter bysop_idorassignee_email.