Document the customer create and merge

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-29 16:08:52 +05:30
parent e27eb8e927
commit e5a63cc412
2 changed files with 106 additions and 0 deletions

49
API.md
View File

@@ -47,6 +47,8 @@ user; the tenant is always taken from the session and never from the request.
| `GET /api/visits` · `GET /api/visits/stream` | authed |
| `GET /api/visitors` · `GET /api/visitors/{id}/history` · `GET /api/visitors/{id}/image` · `GET /api/faces/{id}` | authed |
| `PUT /api/visitors/{id}/profile` · `POST /api/purchases` | staff |
| `POST /api/customers` | staff |
| `POST /api/visitors/{id}/merge` — **irreversible** | manager |
| `DELETE /api/visitors/{id}` — erasure | manager |
| `GET /api/sites` · `GET /api/sites/{site}/check` · `GET /api/cameras` · `GET /api/cameras/{id}/snapshot.jpg` · `GET /api/cameras/{id}/live` | authed |
| `POST /api/sites/{site}/cameras` · `PATCH` / `DELETE /api/cameras/{id}` · `POST /api/cameras/{id}/check` | manager |
@@ -776,6 +778,53 @@ half hours wrong in India.
bars of a footfall report to get either.** `fraction_below_gate` travels with
them because it is what says whether the count is a number or a floor.
### `POST /api/customers` — staff and above
Register a customer before any camera has seen them — somebody standing at the
counter. Body is a profile; **at least a name or a phone** is required, since a
record with neither is a number nobody can search for.
```json
{ "id": "…", "ref": "V-7", "label": "Asha Menon", "visit_count": 0, "has_profile": true }
```
They get a `V-` reference from the same counter an enrolled customer does, so a
hand-created record is indistinguishable from one the engine made.
**Know what this implies.** They have no face template, so when a camera sees
that person later the matcher has nothing to compare against and enrols them
again — by design, not by failure. Join the two with the merge below.
### `POST /api/visitors/{id}/merge` — manager and above
Fold the customer in the path **into** the one named in the body, and delete
the source. `into` takes a uuid or a `V-` reference.
```json
{ "into": "V-12" }
```
```json
{ "visitor_id": "…", "ref": "V-12", "label": "Asha Menon",
"visits": 2, "purchases": 1, "embeddings": 1, "consents": 1,
"retired_ref": "V-7",
"discarded": ["phone: 9000000001"] }
```
- **Irreversible**, which is why it is manager-and-above. Two different people
welded together cannot be separated: nothing records which visit came from
whom. It logs at WARNING and writes an audit row.
- **`retired_ref` is the reference that has stopped resolving.** Staff write
these on cards; asking for it afterwards returns 404.
- **Nothing is discarded silently.** The survivor keeps its own profile values
and its blanks are filled from the source; anything that loses is listed in
`discarded` **and** appended to the survivor's notes — and `GET /api/visitors?q=`
searches notes, so a customer reached by their old phone number is still found.
- Visits, purchases, templates and consents all move. `visit_count` is
recomputed by counting rows, and `first_seen_at` takes the earlier of the two.
- Merging a customer into themselves is **400**; an unknown or another tenant's
customer is **404**.
### `DELETE /api/visitors/{id}` — **erasure**, manager and above
Destroys the face template and the photo outright. Keeps the visit rows,