The review queue over HTTP
v2.24.0 closes the API gap — fourteen new /api/v1 endpoints give review, feedback and learnings full parity with the dashboard, and admins can now mint API tokens without shell access.
Anything the Vocion review UI can do, an API client can now do. Until v2.24.0 the dashboard
review screens talked to the server over the internal oRPC client, which only accepts a browser
session — so eleven operations had no HTTP equivalent at all, and an external admin panel simply
could not drive them. This release closes that gap and, in the same breath, makes issuing the
token to call it a dashboard action rather than a shell one.
What's new
- One authentication path.
authApi()in_shared.tsaccepts either a tenant API token or a dashboard session and hands both back as a singleApiCaller.writeApitakes that caller instead of a raw header, so every/api/v1endpoint works with either credential and every mutation runs the same authz check. - Fourteen new endpoints, covering the review record, the trust-ladder audit list, triage signals, draft rewrites, proposals, reviewer feedback, and the whole learnings surface.
- API tokens from the dashboard, at
/dashboard/api-tokens— admin-only, with an expiry. - A
learning_candidatetable, so a proposed rule waits for a person instead of going live.
Why we built it
Pointing an outside admin panel at an install requires two things: the endpoints, and a
credential you can create without SSH. Both were missing. The endpoints existed only as oRPC
procedures behind a session cookie, and minting a vcn_live_… token meant running the
manage-tokens CLI on the box.
How to use it
Mint a token in the dashboard: /dashboard/api-tokens → name it, choose how long it lives,
copy the secret once. Then drive the queue:
curl -H "Authorization: Bearer $VOCION_TOKEN" \
"https://your-install/api/v1/reviews?kind=personalization.enroll&includeSnoozed=false&limit=25"
GET /api/v1/reviews gains kind, assignedTo, includeSnoozed, limit and offset, and
returns a real total. The rest of the new surface:
GET /api/v1/reviews/:kind/:id full record + proposal + card
GET /api/v1/reviews/auto-executed trust-ladder audit list
POST /api/v1/reviews/signal skip / save triage
POST /api/v1/reviews/rewrite rewrite a draft, unsaved
POST /api/v1/reviews/propose queue a proposed action
POST /api/v1/feedback submit reviewer feedback
GET /api/v1/feedback feedback jobs
GET /api/v1/learnings steps with rule counts
GET /api/v1/learnings/:step/rules rules in a step
POST /api/v1/learnings/:step/rules/check dedupe pre-check
GET /api/v1/learning-candidates rules awaiting a person
GET /api/v1/learning-candidates/:id
PATCH /api/v1/learning-candidates/:id reword or retarget
POST /api/v1/learning-candidates/:id/decide
POST /api/v1/reviews/decide now accepts editedInput for edit-then-approve — on a workflow,
that is the resume input.
Learning candidates are the notable new object. The feedback worker classifies a job and, when the classification proposes a rule, records a pending candidate. It still never writes a live rule: a person approves it into one or rejects it with a reason, and the reason is kept. The dashboard learnings page grows the same queue, driven by the same endpoints.
About the tokens
api_token.expires_at is nullable, so every token issued before this migration keeps working,
and verifyToken refuses a past expiry the same way it refuses a revoked one. The row is left in
place so the dashboard can still show what expired and when.
One deliberate asymmetry: the token routes are oRPC procedures, not REST. oRPC has no bearer
path, so minting is session-only — a leaked token cannot issue itself a replacement and
outlive the revoke meant to kill it. The shell path stays available and now matches:
manage-tokens grows --expires-in-days and reports expiry in its list.
Verification
759 unit tests, plus a 50-check live pass against a real server using only a tenant token. A specialist token returned 403 on every mutation; a second tenant returned 0 rows or 404 on every read; and both dashboard surfaces were walked in a signed-in browser.
What's next
- Expired proposals now drop from the action queue, matching what the dashboard list already did.
- The review queue pages in SQL rather than in memory, and an unknown assignee is rejected up front instead of failing on the foreign key.
Links
- GitHub release: https://github.com/vocion/vocion-core/releases/tag/v2.24.0
- Dashboard surface:
/dashboard/api-tokens,/dashboard/learnings - Code:
packages/core/src/app/api/v1/_shared.ts,features/api-tokens/ApiTokensPanel.tsx