The API: pages built after loading everything, an unpaginated /captures, captures that duplicate on retry, a public schema, no change feed #230
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#230
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The capture API is what the extensions and
postulo-mcpare built on. Found in the 2026-09-15 code audit.1. List endpoints serialise every row before
@paginatetakes a pageapi/routers/applications.py:57doesreturn [application_out(request, a) for a in applications]. The same pattern is incompanies.py:43,listings.py:56,interviews.py:61,reminders.py:28anddocuments.py:64,75.len()and slices it in Python.Fix: return the queryset and map rows in schema resolvers (
resolve_*), or subclass the paginator to map rows only after slicing.2.
GET /capturesis not paginated, though the wiki says lists areapi/api.py:352-362returns a bare list cut at 50, with no explicit order. The capture API promiseslimit/offsetand{"items", "count"}.Fix:
@paginatewith an explicit ordering, plus a test that every list operation in the OpenAPI schema is paginated.test_wiki_surface.pychecks paths and scopes, not response shape.3. Capturing is not idempotent
api/api.py:271-279creates a capture every time. The code's own comment expects retries (:285-286), and/captures/knownis only advisory. A client retrying after a lost201creates a duplicate capture and a duplicate notification.Fix: honour an
Idempotency-Keyheader (owner + key + body hash, kept 24 h, replaying the first response), or return the existing pending capture for the same owner and URL within a short window.4.
/api/v1/openapi.jsonanswers without a tokenapi/api.py:54-65setsdocs_url=Nonebut leavesopenapi_urlat its default, and django-ninja protects that view only whendocs_decoratoris set. This contradictsTHREAT-MODEL.md:13("the API answers 401 to everything without a live token"), and makes Postulo instances easy to fingerprint.Fix: a
docs_decoratorrequiring a live token or a staff session, oropenapi_url=Nonewith the schema published in the repository or wiki.5. No change feed
List endpoints filter by applied date only; there is no
updated_since. An agent or extension has to poll everything and diff.Fix: an
updated_sincecursor on the list endpoints. The outbound webhooks that pair with this are a separate feature issue (#240).updated_at#245