Moliri API v1: designed as a standard
The Node backend gets an API of its own design instead of copying the PHP endpoints one by one.
The full design is docs/plan/backend-node-api-design.md; this page shows what changes and the choices still open.
317PHP endpoint files read and mapped to v1
7 → 1response shapes; errors always RFC 9457
~40 → 0endpoints that report errors with HTTP 200
12 → 1poll endpoints for background work → one job resource
1. Saving a field
PHP todayPOST /api/U/object_field.php
{ "project_id": 12, "object_id": 345,
"field_id": 101, "field_type": "text",
"value_table": "d_varchar",
"new_value": "Öl auf Holz",
"batch_id": "7f6c…" }
→ 200 { "success": "1", … }
last write wins; meta (hesitant, source…)
is a second endpoint, one column per call
v1PATCH /api/v1/objects/345/values/101
?project_id=12
If-Match: "17"
Moliri-Batch-Id: 7f6c…
{ "value": "Öl auf Holz", "hesitant": true }
→ 200 the value with version "18"
→ 412 someone else saved this field first:
the answer carries their value, the UI
shows both instead of losing one
Each field value has its own version, so two people editing different fields of one object never collide.
2. Deleting a record that is still used
PHP todayPOST /api/D/normdata.php
{ "normdata_id": 5512, "project_id": 12 }
→ 200 { "error": "…", "usage_count": 14 }
(D/layout, D/virtual_field and four more
delete without checking at all)
v1DELETE /api/v1/normdata/5512
If-Match: "4"
→ 409 application/problem+json
{ "type": ".../problems/in_use",
"title": "Still in use", "status": 409,
"usage": { "total": 14, "by_project": […] } }
The same rule for every library record. The app translates the error by its type; the server sends no German text.
3. Loading the gallery
PHP todayPOST /api/R/gallery.php?p=12
{ "view_config": …, "only_ids": true }
POST /api/R/gallery.php?p=12
{ "object_ids": [ …1000 ids ] }
values keyed by field NAME ("Material~sub");
filter = flat list with "(" and ")" as tokens
v1POST /api/v1/projects/12/objects/search/ids
{ "filter": { "all": [
{ "field": { "field_id": 101 },
"op": "contains", "value": "Öl" } ] },
"sort": [ … ] }
→ { "ids": […], "total": 812, … }
POST /api/v1/projects/12/objects/search
→ { "items": [ { "values": { "101": … } } ],
"next_cursor": "…" }
Values are keyed by field id, so renaming a field breaks nothing; the filter is a tree that never reaches SQL as text.
4. Long work: exports, OCR, AI runs, uploads
PHP todayR/excel.php streams up to the time limit
R/export_progress.php?cancel=1 (a GET that writes)
R/natural_filter.php?id=…
R/ocr_assign.php?id=…
R/template_rewrite.php … twelve shapes
upload errors are only logged on the server
v1POST /api/v1/projects/12/exports
Prefer: wait=10
→ 201 result directly when done in 10 s
→ 202 Location: /api/v1/jobs/8812
GET /api/v1/jobs/8812 status, progress
POST /api/v1/jobs/8812/cancel
GET /api/v1/uploads/{id} → shows failures
Also in the design
- One URL per resource, actions as
POST …/{id}/apply; creates answer201with the id. - Retries are safe:
Idempotency-Keyon creates, so a double click makes one object. - Batches report a result per item and form one undo step.
- OpenAPI generated from the same schemas is the contract; the app's client is generated from it, a lint keeps the rules.
- Sign-in: cookies + CSRF for the app, access tokens for MCP and scripts. Unreadable data is always
404. - The work packages N1c to N8 are adjusted: N1c builds these conventions first, every later card ports resources, not PHP files.
Choices for the owner
| Question | Recommendation | Alternative | |
|---|---|---|---|
| A | Style of paths | snake_case like the database: /access_groups | kebab-case: /access-groups |
| B | Property names in JSON | snake_case like the database: project_id | camelCase: projectId |
| C | Key of field values | field id (survives renaming) | field name (readable, breaks on rename) |
| D | Two people save the same field | always checked; the second sees both values | only checked when the client asks; otherwise last write wins (today) |
| E | Paging through lists | cursor only | page numbers as well for admin lists |
| J | Batch where some items fail | apply what may be applied, report the rest | all or nothing |