You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The HTTP server (internal/api) is a thin client: it owns no database and no
business logic. Every route translates an HTTP request into a single proto call
to the daemon — exactly as the TUI does. Both frontends are built from the same
command catalog (internal/command/catalog.go), so an action is defined once and
every frontend stays in step.
Base server:internal/api/server.go (NewServer → routes())
Route table:internal/api/routes.go
Command catalog (source of truth):internal/command/catalog.go
Proto method names:internal/proto/proto.go
Conventions
All responses are JSON unless noted (text/markdown, text/plain for metrics/notes).
Errors return {"error": "..."} with an HTTP status mapped from the proto error
code: NotFound/NoMethod → 404, BadParams/Parse/InvalidReq → 400,
Busy → 503, otherwise 500.
Per-call timeout is 20s (apiCallTimeout); notes export uses 30s.
{number} is a patent number parsed by domain.ParsePatentNumber; {id} is a
project ID.
List endpoints page with ?limit=&offset= and accept filter/sort query params
(see handlePatentList / handleRelations).
Endpoint reference
Health & introspection
Method
Path
Proto method
Notes
GET
/healthz
ping
Daemon liveness ping.
GET
/commands
—
Returns the full command registry (registry.All()).
GET
/metrics
metrics.get
Prometheus exposition format (text/plain).
GET
/metricsz
metrics.get
In-memory timing/counter snapshot as JSON.
POST
/metrics
metrics.push
Merge a client-side snapshot. Body: component, snapshot.
Activity & history (served from the local JSONL journal, not proto)
Method
Path
Notes
GET
/activity
Alias of /activity/raw.
GET
/activity/raw
Raw activity records. Query: limit, component, action, entity, since.
POST
/activity
Record a UI/browser activity event. Body: action, entity, entity_id, status, duration_ms, before, after, attributes.
GET
/activity/replay_history
Capped log of rows opened from replay UI. Query: limit.
Parsed grant/pgpub body. Query: kind (default grant, daemon falls back to pgpub).
POST
/patents/{number}/uspto/assignments
uspto.fetch_assignments
Fetch & store assignment chain (also rebuilds the assignee timeline).
GET
/patents/{number}/uspto/assignments
uspto.assignment.list
Persisted raw assignment chain.
GET
/patents/{number}/assignees
assignee.history
Record.id-keyed ownership timeline: at-grant owner + each recorded assignment, oldest first, current owner(s) flagged is_latest.
POST
/assignments/fetch
uspto.fetch_assignments.batch
Batch fetch for an explicit list. Body { "numbers": ["US…", …], "include_expired": false }. Skips expired unless include_expired. Returns {total,fetched,skipped,failed,assignments,parties,failures[]}.
Stats
Method
Path
Proto method
Notes
GET
/assignees/history
patent.all_assignees_history
Query: project. Get assignee history rollups.
GET
/assignees
patent.all_assignees_history
Alias of /assignees/history.
GET
/assignees/stats
patent.all_assignees_history
Alias of /assignees/history.
GET
/classifications/stats
patent.classification_stats
Query: project.
Projects
Method
Path
Proto method
Notes
GET
/projects
project.list
POST
/projects
project.create
Body: name.
PUT
/projects/{id}
project.update
Body: a domain.Project.
POST
/projects/{id}/patents
membership.add
Body: patent, source, confirm_merge.
POST
/projects/{id}/patents/related
membership.add_related
Body: patent.
GET
/projects/{id}/ids
ids.export
Build the IDS for a project.
POST
/projects/{id}/ids/pdf
ids.export.pdf
Render PTO/SB/08a+08c. Optional body overrides (cumulative_count, fee_amount, deposit_account, signer_*).
POST
/projects/{id}/ids/pdf/preview
ids.export.pdf.preview
Dry-run summary (counts, fee tier, missing fields). Same optional body as the PDF export.
POST
/projects/{id}/ids/status
ids.entry.bulk_set_status
Apply one status to many patents. Body: patents[], status, default_in_full.
GET
/projects/{id}/ids/entries/{number}
ids.entry.get
One curated IDS entry.
PUT
/projects/{id}/ids/entries/{number}
ids.entry.save
Insert/update an IDS entry. Body: a domain.IDSEntry (project & patent taken from path).
DELETE
/projects/{id}/ids/entries/{number}
ids.entry.delete
Remove an IDS entry.
GET
/projects/{id}/added/export
added.export
text/plain list of the project's manually-added patents (the :export.added format). The patent count is also returned in the X-Patent-Count response header.
POST
/projects/{id}/added
added.import
Bulk-add patents from a list. Body: raw list text (text/plain), or JSON {"content":"…"} / {"path":"…"}. Returns total, added, failed, failures[].
GET
/projects/{id}/assignees
project.assignees
Assignee rollup: current_owners (deduped, live patents only) vs. all_assignees (every owner ever), plus live_patents, expired_patents, not_fetched coverage. Grouped by normalized name.
POST
/projects/{id}/assignees/fetch
uspto.fetch_assignments.batch
Batch fetch over the project's curated members. Body (optional): { "include_expired": false }. Returns the batch result.
GET
/projects/{id}/notes
patent.note.list
All notes for a project. Query: sort_by=date|patent.
GET
/projects/{id}/notes/export
patent.note.export
text/markdown. Query: sort_by=date|patent.
GET
/projects/{id}/patents/{number}/note
patent.note.get
One patent note.
PUT
/projects/{id}/patents/{number}/note
patent.note.save
Insert/update a note. Body: a domain.PatentNote (project & patent taken from path).
DELETE
/projects/{id}/patents/{number}/note
patent.note.delete
Remove a note.
Tags
Method
Path
Proto method
Notes
POST
/projects/{id}/tags
tag.create
Body: name.
GET
/projects/{id}/tags
tag.list
DELETE
/projects/{id}/tags/{name}
tag.delete
POST
/projects/{id}/patents/{number}/tags
patent.tag.add (strict)
Body: name.
DELETE
/projects/{id}/patents/{number}/tags/{name}
patent.tag.delete (strict)
GET
/projects/{id}/patents/{number}/tags
patent.tag.list
Classifications
Method
Path
Proto method
Notes
GET
/classifications
classification.list
GET
/classifications/{system}/{code}
classification.get
POST
/classifications
classification.save
Body: classification.
DELETE
/classifications/{system}/{code}
classification.delete
POST
/classifications/lookup
classification.lookup
Body: code.
POST
/classifications/by_codes
classification.list_by_codes
Body: codes[].
GET
/projects/{id}/patents/{number}/classifications
patent.classification.list
Crawl & source mode
Method
Path
Proto method
Notes
POST
/crawl
crawl.family
Body: root, depth, force, profile, project.
POST
/crawl/cancel
crawl.cancel
Body: job_id.
GET
/events
—
SSE stream of crawl.* and db.changed (and session.changed).
Load a patent from a local fixture file. Body: path.
GET
/crawl/config
crawl.config
Daemon crawl defaults.
GET
/source_mode
source_mode.get
PUT
/source_mode
source_mode.set
Body: proto.SourceModeParams.
GET
/config/source_mode
source_mode.get
Alias.
PUT
/config/source_mode
source_mode.set
Alias.
Table views (persisted UI layouts)
Method
Path
Proto method
Notes
GET
/table_views
table_view.list
Query: owner, table_type.
GET
/table_views/{id}
table_view.get
Query: owner.
POST
/table_views
table_view.save
Body: a domain.SavedTableView.
DELETE
/table_views/{id}
table_view.delete
Query: owner.
Consistency with the TUI
The check below is grounded in internal/command/catalog.go. KindView commands
(navigation, opening panes, visual selection, jump mode, etc.) are intentionally
client-local and have no REST equivalent — that is correct, not a gap.
The interesting question is whether every KindEngine command (a real daemon
operation) is reachable over REST.
ℹ️ Defined proto methods still used by neither frontend directly
uspto.lookup is declared in proto.go but not referenced by the TUI command
dispatch or REST. (crawl.cancel is wired as a catalog command and dispatched
generically.)
How to keep this consistent
Because every route in routes.go is meant to mirror a catalog command, the
cleanest enforcement would be a startup/test assertion that every
command.KindEngine entry in command.Default() has a corresponding route. The
gaps in the ❌ table above would be the first failures to address.