# Hash Conflicts API ## Overview When Bookhoard hashes your library (on import, rescan, or the startup backfill), two media items in the same library with the same `file_sha256` indicate duplicate content. Each duplicate group is recorded as a **hash conflict** and exposed here for an explicit keep/merge decision. Conflicts are also surfaced in the admin UI's Hash Conflicts page. **Authentication**: Admin JWT token required **Content-Type**: `application/json` (resolve also accepts form-encoded bodies for htmx) --- ## Endpoints ### List Hash Conflicts List all pending conflict groups, each with its member items and per-item usage counts (reading progress, highlights, bookmarks, notes, collections) to help decide which copy to keep. **Endpoint**: `GET /api/admin/hash-conflicts` **Response**: **200 OK** ```json { "conflicts": [ { "id": "conflict-uuid", "library_id": "library-uuid", "library_name": "Ebooks", "sha256": "abc123...", "created_at": "2026-08-14T12:00:00Z", "items": [ { "id": "media-item-uuid", "title": "The Hobbit", "author": "J. R. R. Tolkien", "file_path": "/books/hobbit.epub", "file_size": 1048576, "created_at": "2026-01-01T00:00:00Z", "progress_count": 2, "highlight_count": 12, "bookmark_count": 3, "note_count": 1, "collection_count": 2 } ] } ], "total": 1 } ``` **Example**: ```bash curl -X GET https://bookhoard.example.com/api/admin/hash-conflicts \ -H "Authorization: Bearer " ``` --- ### Resolve Hash Conflict Resolve one conflict group. **Endpoint**: `POST /api/admin/hash-conflicts/{id}/resolve` **Request Body** (JSON or form-encoded): ```json { "action": "keep", "keep_uuid": "media-item-uuid-to-keep" } ``` | Field | Type | Required | Description | | ----------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `action` | string | Yes | `keep_all` — both copies are intentional; dismiss the conflict. `keep` — keep `keep_uuid` and delete the other copies. | | `keep_uuid` | string | for `action=keep` | The media item UUID to keep. Must belong to this conflict group. With `keep`, every other copy's child rows (progress, highlights, bookmarks, notes, collections, …) are merged into the kept item before the losers are deleted. | **Responses**: - `200 OK` — resolved (body is an HTML confirmation snippet for the admin UI page) - `400 Bad Request` — invalid conflict ID, missing `keep_uuid`, or `keep_uuid` not in the group - `404 Not Found` — conflict doesn't exist - `409 Conflict` — conflict already resolved **Example**: ```bash curl -X POST https://bookhoard.example.com/api/admin/hash-conflicts//resolve \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"action": "keep", "keep_uuid": "media-item-uuid"}' ``` --- ## When Conflicts Are Created - **Startup backfill**: items imported before hashing existed are hashed automatically ~30s after startup; duplicates discovered land here. - **Rescan**: hashes are recomputed and content duplicates are flagged. Files on disk are never deleted — resolution only affects database rows. --- ## Related Endpoints - [System Settings API](../system/settings.md) — scanning configuration - [Scanner API](../scanner/) — triggering scans and watch mode