From 5805124763335041d88e870b1069a15eefc37a8b Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Mon, 18 May 2026 00:44:16 -0500 Subject: [PATCH] docs(readme): endpoint table reflects the full surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three gaps in the README endpoint table after the audit cycle: 1. **`GET /metrics`** wasn't listed at all. Added with a note on the optional `METRICS_BEARER_TOKEN` gate. 2. **The 13 `POST /v1//bulk` endpoints** were invisible to anyone reading just the README. Added one row covering the pattern (500-entry cap, transactional all-or-nothing) — the per-entity body shape is in the OpenAPI spec / Swagger UI. 3. **Cross-cutting headers** had no documentation: - `Idempotency-Key` (24h dedup window on POSTs) - `Link` (RFC 5988 pagination) - `X-Request-Id` (trace correlator) - `RateLimit-*` (RFC standard rate-limit signals) New section right after the table describes each. 4. **`migration` field on `/healthz`** body is mentioned in the updated /healthz row. No code changes; documentation only. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 57809bd..fd4d936 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ Working example at [node.timetrackerapi.com](http://node.timetrackerapi.com). | Endpoint | Auth required | Description | |-------------------------------------|---------------|----------------------------------------------| -| `GET /healthz` | no | Liveness + DB-readiness probe (returns `{status, db, uptime_s, version, elapsed_ms}`; 200 ok / 503 degraded). | +| `GET /healthz` | no | Liveness + DB-readiness probe (returns `{status, db, uptime_s, version, elapsed_ms, migration}`; 200 ok / 503 degraded). `migration` carries the last applied migration name from `SequelizeMeta`, useful for verifying rolling-deploy schema versions. | +| `GET /metrics` | no (or bearer)| Prometheus scrape endpoint. Default Node.js metrics + per-request `http_requests_total` / `http_request_duration_seconds`. Authentication is OPTIONAL: leave `METRICS_BEARER_TOKEN` unset for an open scrape (private-network deployment) or set it to require `Authorization: Bearer `. | | `GET /docs` | no | Interactive Swagger UI for the full API. | | `GET /openapi.json` | no | Raw OpenAPI 3.0 spec (machine-readable). | | `GET /v1/customer/:id` | yes (`authKey`) | Single customer lookup. Master key sees all; non-master only sees customers in its own company. | @@ -43,6 +44,25 @@ Working example at [node.timetrackerapi.com](http://node.timetrackerapi.com). | `* /v1/purchaseorderheader/*` | yes (`authKey`) | Purchase orders. Vendor-scoped — auth resolves via `pohPovId → vendor.povCompId`. `GET /byvendor/:id` lists POs for a vendor, newest first. | | `* /v1/purchaseorderline/*` | yes (`authKey`) | PO line items. Header-scoped via `polpoh → header → vendor → company`. `GET /byheader/:id` lists line items on a PO. | | `* /v1/inventorytransaction/*` | yes (`authKey`) | Inventory movement log. Direct company scoping via `invtCompanyId`. `invtDirection` is `0` (inbound) or `1` (outbound). PATCH/DELETE exposed for surface parity; audit-grade deployments may want to disable them at the proxy. | +| `POST /v1//bulk` | yes (`authKey`) | Transactional all-or-nothing bulk-create on all 13 soft-deletable entities (customer, worker, billingtype, inventoryitem, inventorytransaction, purchaseordervendor, job, invoice, customerpayment, invoicejob, productentry, purchaseorderheader, purchaseorderline). Body: `{ : [{...}, ...] }` capped at 500 entries. Same auth scoping as the single-create POST. If any entry fails to insert, the whole batch rolls back. | + +### Cross-cutting headers + behaviors + +- **`Idempotency-Key` (request header, optional)** — set on any POST to make it + idempotent for 24h. Identical retry replays the cached response with + `Idempotency-Replay: true`. Same key + different body → `409 + { code: "idempotency_key_reused" }`. Printable ASCII, 1-255 chars. +- **`Link` (response header, RFC 5988)** — every paginated list endpoint emits + `next` / `prev` / `first` / `last` URLs when applicable, so clients can walk + the result set without doing offset arithmetic. +- **`X-Request-Id` (response header, also accepted on request)** — every + response carries a UUID correlator; the same id appears in every structured + log line for that request. Supply your own X-Request-Id on the way in to + propagate trace context from a reverse proxy / mesh. +- **`RateLimit-*` (response headers, RFC standard)** — `RateLimit-Limit`, + `RateLimit-Remaining`, `RateLimit-Reset` on every /v1/* response. +- Browser JS reading any of the above on a cross-origin response works + out-of-the-box: the CORS layer's `Access-Control-Expose-Headers` covers them. Every v1 request must include the API key in the `authKey` HTTP header. The `/healthz` endpoint is intentionally unauthenticated so it can be