All docs

Reference

Commands and API

Every workshop command, who may send it, and the move-in and backup endpoints.

Use the real app origin. GET /api/mallet/state returns {actor,version,records}. The top-level version is the store version; commands below use the relevant record's version. Read current records after each write: commands can update related records too. A version conflict requires a refresh and review of the changed record.

Send commands as JSON:

HTTP

POST /api/mallet/commands
Content-Type: application/json
Authorization: Bearer <private-preparation-key>

{"command":"invoice.prepare","input":{"jobId":"<completed-job-id>","version":7}}

The placeholders are not usable credentials or record IDs. Successful commands return their result as JSON. Errors return {error,message,details?} with an appropriate 4xx/5xx status. Mallet JSON bodies are limited to 1 MiB. Do not blindly repeat a failed financial decision; refresh its state first. Invoice export specifically returns its saved journal entry on a valid repeated request.

Authentication and permissions

Native owner/admin sessions manage the repair workflow. Cookie-authenticated mutations require the exact app Origin and valid session; cross-site requests are refused. Do not bypass Base's CSRF protections. An agent uses only Authorization: Bearer …, never fabricated actor or role fields. Bearer requests must still address the correct app host and cannot be cross-site browser requests.

Actor Allowed work
Owner/admin Workshop commands, agent-key create/revoke, invoice downloads, snapshot import preview/apply/journal/undo.
Member Read assigned jobs, their assets/estimates/events; work.record on an assigned job. Assignment is by operator ID.
Agent key Read workshop state; only the four preparation commands below.
Owner only Native backup/restore and unused-asset erasure.

A preparation key can see workshop/customer state, so treat it as private customer-data access. Keys expire 30 days after creation. The key is invalid if revoked, expired, or its creating owner/admin becomes inactive or changes operator version/role. Current session/key authority is checked again at mutation time.

Create keys from /app/data, or with an owner/admin native session:

JSON

{"command":"agent.create","input":{"label":"Workshop assistant"}}

The response includes {id,label,token,expiresAt}; expiresAt is epoch milliseconds. Copy token once into private agent configuration. The UI never shows it again. Revoke with {"command":"agent.revoke","input":{"id":"<key-id>"}}. An agent key cannot create or revoke keys.

Four agent preparation commands

Command Required input Result and constraints
estimate.prepare jobId, job version, lines; optional notes Draft estimate revision 1 on an open job with no estimate. Also updates the job.
estimate.revise estimateId, estimate version, current revision, replacement lines; optional notes Appends a draft revision; earlier approval does not approve it. Authorised estimates are frozen.
dispatch.prepare jobId, job version, bayId, technicianId, startAt, endAt, reason Pending proposal with observed conflicts. A person's approval checks current conflicts and job version again.
invoice.prepare jobId, completed job version Draft invoice from authorised actual work, once per job. Do not supply lines or totals.

Example estimate input:

JSON

{
  "command": "estimate.prepare",
  "input": {
    "jobId": "<job-id>",
    "version": 1,
    "lines": [
      {"kind":"part","description":"Oil filter","quantity":1,"unitCents":2500,"gst":true},
      {"kind":"labour","description":"Service labour","quantity":0.5,"unitCents":12000,"gst":true}
    ],
    "notes": "Review both lines before sharing."
  }
}

Each estimate has 1–100 lines. A line uses kind:"part"|"labour", description, positive numeric quantity with up to three decimal places, and non-negative integer unitCents in AUD excluding GST. gst defaults to true and means 10% GST; false means zero GST. The server calculates rounded integer-cent amounts and totals. Optional id must be unique within the revision; otherwise IDs are generated. Only parts may carry optional pierItemId. A reference does not allocate stock.

Times must include a timezone, for example 2026-10-19T09:00:00+10:00. A booking/dispatch interval ends after it starts and lasts no more than seven days. technicianId must be the member's actual operator ID for that member to see and record work; a display label will not grant that access.

Agents are denied estimate.approve, estimate.markSent, estimate.authorise, dispatch.approve, invoice.approve, invoice.export, work.record, job.book, job.complete, asset changes, import, portability and people/key management. The person reviews prepared work in the job UI; approvals and events retain the preparer, decision-maker, exact revision/version and timestamp.

Human command reference

These require an owner/admin native session unless marked otherwise. version always names the target record described in the row.

Command Input
asset.create customerName, optional customerEmail, label, unique identifier.
asset.update assetId, asset version, same asset fields.
job.book assetId, asset version, title, optional notes, bayId, technicianId, startAt, endAt.
estimate.approve estimateId, estimate version, exact current revision.
estimate.markSent estimateId, estimate version, revision, method, optional note. Method: email, printed, phone, in person, manual share. Records manual sharing; no email is delivered or confirmed.
estimate.authorise estimateId, estimate version, revision, nonempty unique lineIds, customerName, method, evidence. Method: signed, email reply, phone, in person. Must follow approval and sharing of this revision. Staff attest to external evidence.
dispatch.approve proposalId, proposal version. Refuses stale jobs, current resource conflicts, completed jobs or jobs with actual work.
work.record jobId, job version, authorised lineId, numeric quantity, optional note. Also allowed for its assigned member. Cumulative quantity cannot exceed authorisation.
job.complete jobId, job version, optional note. Requires actual work.
invoice.approve invoiceId, invoice version, optional exact invoice revision.
invoice.export invoiceId, invoice version. First export requires approval. A repeat accepts the current version or the version immediately before first export and returns the saved entry.
asset.erase Owner only: assetId, confirm:"ERASE". Refuses assets with visits.

Invoices use recorded authorised quantities and the frozen estimate prices. No arbitrary prices or totals may be injected at invoice preparation. The saved export has format:"mallet-invoice-csv"; download it as owner/admin at GET /api/mallet/exports/<export-id>. This generic CSV is not guaranteed to match Xero's import schema. Asset event history is available at GET /api/mallet/assets/<asset-id>/history.csv within the caller's visible state.

Snapshot move-in API

All routes require an owner/admin native session and same-origin mutation checks. These are separate endpoints, not preparation-key commands. Read Move in from RepairShopr before supplying RepairShopr customer/asset snapshots.

Method and path Input / result
POST /api/mallet/imports/preview {customers,assets} → dry-run plan, fingerprint, counts, diagnostics and source links. No writes.
POST /api/mallet/imports/apply {customers,assets,fingerprint} using the identical reviewed snapshots → journal job/progress. Resolve error diagnostics before applying. Repeat identical approved input to continue a bounded apply.
POST /api/mallet/imports/resume {id} → continue the retained approved plan without uploading snapshots again.
GET /api/mallet/imports/journal?id=<id> Retained job, full plan, originals and mapped/source links.
POST /api/mallet/imports/undo {id} → undo; refuses changed assets or those with later visits.

The browser path is /app/move-in: preview, explicitly approve the fingerprint, apply and retain the journal ID. These imports move customers/assets only, not source jobs or authorisations.

Backup and restore

The owner uses /app/data → Download workshop backup, or native rer export / rer import described in Back up, restore and erase. Backups exclude identity and keys. Restore requires an empty product destination with a fresh owner/configuration. An archive that predates an erasure is refused (restore_refused, reason erasure_epoch), with or without --journal: restore a backup made after the erasure. Local --input and --journal files must be owner-only (0600) paths inside the checkout, such as exports/workshop.rer.

Mallet mounts the full Base handler with its archive policy. All routes below require the owner's native session; mutation requests include the matching Origin. The transfer job is scoped to the owner who created it.

Method and path Operation
POST /api/portability/exports Create export job with {}. Returns {id}.
GET /api/portability/exports/<id> Read status.
POST /api/portability/exports/<id>/advance Advance bounded export work; body follows the Base protocol (expectedPart, maxParts).
GET /api/portability/exports/<id>/download Download completed archive as NDJSON.
POST /api/portability/imports Create import with {manifest,confirmed:true} from an archive.
PUT /api/portability/imports/<id>/parts/<index> Upload the archive's numbered part using the Base protocol.
POST /api/portability/imports/<id>/finalize Finalize the staged import using Base's validated protocol.
GET /api/portability/imports/<id> Read import status.
POST /api/portability/exports/<id>/abort or POST /api/portability/imports/<id>/abort Abort a transfer.

Use the pinned Base transfer client/CLI for manifest and part construction; arbitrary workshop JSON is not a portability archive. A backup made after an erasure is the one to restore: an older archive, even with that later backup as --journal, is refused so erased information is not revived. Erasure is restricted to unused assets in this release; visit-bearing assets remain retained.