Duplicates
NeuralRepo compares each new idea against your existing ones and records pairs that score above your duplicate threshold. This API lists those detections and resolves them.
How detections appear
Section titled “How detections appear”Detection is asynchronous and happens after capture, not during it:
POST /ideasreturns immediately withprocessing: true.- The queue embeds the idea and compares it against your existing vectors.
- Any pair scoring above
dedup_threshold(default 0.75) is written as apendingdetection.
Nothing in the create response tells you a duplicate was found — there is no synchronous warning
and no blocking prompt. Poll this endpoint (or GET /review) a few seconds later instead.
List Duplicates
Section titled “List Duplicates”GET /api/v1/ideas/duplicates
Returns every pending detection where both ideas are still unarchived, highest similarity first. Dismissed and merged detections are never returned, and there is no pagination.
curl https://neuralrepo.com/api/v1/ideas/duplicates \ -H "X-API-Key: nrp_YOUR_KEY"const res = await fetch( "https://neuralrepo.com/api/v1/ideas/duplicates", { headers: { "X-API-Key": "nrp_YOUR_KEY" } });const { duplicates } = await res.json();Response 200 OK
{ "duplicates": [ { "id": 7, "user_id": "3f2a91c47b0e4d5aa8c61e0f2b7d4c93", "idea_id": 158, "idea_number": 58, "idea_title": "Add SSO support for enterprise accounts", "idea_body": "Larger customers keep asking for SAML...", "idea_status": "captured", "idea_source": "cli", "idea_created_at": "2026-03-22 13:58:00", "duplicate_of_id": 137, "duplicate_number": 42, "duplicate_title": "Enterprise SSO integration", "duplicate_body": "SAML and OIDC for teams...", "duplicate_status": "exploring", "duplicate_source": "web", "duplicate_created_at": "2026-02-11 09:04:00", "similarity_score": 0.94, "status": "pending", "created_at": "2026-03-22 14:00:00" } ]}| Field | Meaning |
|---|---|
id | The detection id — this is what the dismiss and merge endpoints take |
idea_id | The idea whose capture or edit triggered the detection — normally the newer one |
duplicate_of_id | The older idea it matched |
idea_body, duplicate_body | First 280 characters only, for previewing |
similarity_score | Cosine similarity, 0–1 |
status | Always pending in this listing |
idea_number and duplicate_number are display numbers for showing the user; every write
endpoint here takes the detection id, and the merge acts on the two idea ids.
Dismiss Duplicate
Section titled “Dismiss Duplicate”POST /api/v1/ideas/duplicates/:id/dismiss
Marks the detection as dismissed. Both ideas are left exactly as they are, and the pair never
appears in the list again.
curl -X POST https://neuralrepo.com/api/v1/ideas/duplicates/7/dismiss \ -H "X-API-Key: nrp_YOUR_KEY"const res = await fetch( "https://neuralrepo.com/api/v1/ideas/duplicates/7/dismiss", { method: "POST", headers: { "X-API-Key": "nrp_YOUR_KEY" } });Response 200 OK
{ "success": true}Only a pending detection can be dismissed. Dismissing one that is already dismissed or merged
returns 404 { "error": "Duplicate not found or already resolved" } — the same body as an id
that does not exist.
Merge Duplicate
Section titled “Merge Duplicate”POST /api/v1/ideas/duplicates/:id/merge
Folds the two ideas together and marks the detection merged.
curl -X POST https://neuralrepo.com/api/v1/ideas/duplicates/7/merge \ -H "X-API-Key: nrp_YOUR_KEY"Exactly what happens, compared with the general merge:
| Aspect | duplicates/:id/merge | ideas/:id/merge |
|---|---|---|
| Survivor | idea_id — the newer idea | The id in the path; you choose |
| Body | Both joined with \n\n---\n\n | Both joined with \n\n---\n\n |
| Tags | Union of both | Union of both |
created_at | The earlier of the two | The earlier of the two |
| Relations | Not transferred | Repointed onto the survivor |
| Links | Not transferred | Not transferred |
| Loser’s status | Unchanged, just archived | Set to shelved, then archived |
| Detection row | Marked merged | Untouched |
| Response | { "success": true } | The surviving idea object |
Archiving is permanent, so pick deliberately: if the older idea is the one with the relations,
the history, or the better title, dismiss the detection and use
POST /ideas/:id/merge instead, naming the keeper
yourself.
Response 200 OK
{ "success": true}Workflow
Section titled “Workflow”- List pending detections with
GET /ideas/duplicates. - Compare
idea_title/idea_bodyagainstduplicate_title/duplicate_body. - Dismiss false positives — nothing changes.
- For true duplicates, decide which idea should survive:
- the newer one →
POST /ideas/duplicates/:id/merge; - the older one, or you want relations preserved → dismiss, then
POST /ideas/:id/mergewith the keeper in the path.
- the newer one →
Tune how many detections you get with the dedup_threshold
user setting: raise it for fewer, stricter matches.
Status Codes
Section titled “Status Codes”| Status | Meaning |
|---|---|
200 OK | Listed, dismissed, or merged |
400 Bad Request | :id is not a number |
401 Unauthorized | Missing or invalid auth |
404 Not Found | Detection not found, not yours, or already dismissed/merged |
No endpoint here returns 403 on Free — the list simply comes back empty.