Issues
As your knowledge base grows, Mereon flags issues: conflicts, duplicates,
gaps, and stale or misclassified knowledge. These endpoints expose them so a tool
or dashboard can surface what needs attention. Reading issues needs the
issue.read permission, which every role has except external; an external
account gets 403, as it does in the app. People raise issues too, and a client
can file one for them, or decide one that
is already open.
List issues
Section titled “List issues”GET /v1/issues
Most recent first, cursor-paginated. You see an issue you filed, and any other issue whose every topic or item target you can read.
Query parameters
Section titled “Query parameters”| Parameter | Type | Notes |
|---|---|---|
status |
string | OPEN, IN_PROGRESS, RESOLVED, or DISMISSED. |
type |
string | CONFLICT, DUPLICATE, MISCLASSIFICATION, STALE, CONTENT_GAP, STRUCTURE_GAP, OTHER, UPDATE_HELD (an update to a resource held because it would remove too much), or RETIRED_ITEM_REAPPEARED (an item someone retired that a new version of its source says again). |
severity |
string | CRITICAL, HIGH, MEDIUM, or LOW. |
assignedToMe |
boolean | Only issues assigned to the connected user, either directly or through a role they currently hold. |
targetType |
string | TOPIC, ITEM, SPACE, or CATEGORY. Use it with targetId to find the issues on one thing. |
targetId |
string | The id of the topic, item, space, or category the issue concerns. |
search |
string | Text to find in the title, the description, or a target’s title. The whole text has to appear, in that order, and case does not matter. |
cursor |
string | Pagination cursor from a previous response. |
limit |
number | 1–100. Default 50. |
Request
Section titled “Request”curl "https://api.mereon.ai/v1/issues?status=OPEN&severity=HIGH" \ -H "Authorization: Bearer $MEREON_TOKEN"Response
Section titled “Response”{ "data": [ { "id": "iss_4f9c...", "type": "CONFLICT", "severity": "HIGH", "status": "OPEN", "title": "Two documents disagree on the expense approval threshold", "description": "The Finance Handbook says $500; the Travel Policy says $750.", "assignedToName": "Alex Doe", "assignedToRoleName": null, "raisedByName": null, "trigger": "AI_REVIEW", "targets": [ { "targetType": "TOPIC", "targetId": "top_9f2a...", "title": "Expense approval" } ], "createdAt": "2026-06-01T09:00:00.000Z", "resolvedAt": null } ], "nextCursor": null}Fields
Section titled “Fields”| Field | Type | Notes |
|---|---|---|
type |
string | The kind of issue (see the parameter table). |
severity |
string | CRITICAL, HIGH, MEDIUM, or LOW. |
status |
string | OPEN, IN_PROGRESS, RESOLVED, or DISMISSED. |
title |
string | Short summary of the issue. |
description |
string | null | Longer explanation, when present. |
assignedToName |
string | null | Assigned user’s display name, when assigned to a person. |
assignedToRoleName |
string | null | Assigned role’s name, when assigned to a role. |
raisedByName |
string | null | Who raised it (manual issues only). |
trigger |
string | What raised the issue: AUTO_DETECTED (the system’s own detection), AI_REVIEW (an AI knowledge review), REVIEW (a content review), or MANUAL (a person). |
targets |
object[] | The entities the issue concerns, each with targetType, targetId, and title. |
resolvedAt |
string | null | When it was resolved, or null if still open. |
Get an issue
Section titled “Get an issue”GET /v1/issues/{id}
One issue in full: the list fields, plus each target’s affected content and how the issue was resolved. That extra detail is what lets a conflict or duplicate be explained rather than only named.
Request
Section titled “Request”curl "https://api.mereon.ai/v1/issues/iss_3d81" \ -H "Authorization: Bearer $MEREON_TOKEN"Response
Section titled “Response”{ "id": "iss_3d81...", "type": "CONFLICT", "severity": "HIGH", "status": "RESOLVED", "title": "Two different torque values for the same fastener", "description": "One manual says 50 Nm, a later bulletin says 65 Nm.", "assignedToName": null, "assignedToRoleName": "Maintenance Lead", "raisedByName": null, "trigger": "AUTO_DETECTED", "targets": [ { "id": "tgt_81c4...", "targetType": "ITEM", "targetId": "itm_5c02...", "title": "Torque the wheel fasteners to 50 Nm", "blockType": "SOP_STEP", "body": "Torque the wheel fasteners to 50 Nm in a star pattern.", "handled": true } ], "resolution": { "strategy": "supersede" }, "resolvedAt": "2026-07-30T14:05:00.000Z"}Each target carries the list fields (targetType, targetId, title) plus:
| Field | Type | Notes |
|---|---|---|
id |
string | This target’s own row id, which is distinct from targetId, the id of the thing it points at. |
body |
string | null | The item’s own text, for an ITEM target. This is the wording the conflict or duplicate is about, which is what makes the issue explainable rather than merely named. null for other target types. |
blockType |
string | null | The kind of item, for an ITEM target — for example SOP_STEP or DEFINITION. An open set: new kinds can appear without a new API version. null otherwise. |
handled |
boolean | Whether this particular target has been dealt with. An issue can be resolved for one target and still open against another. |
resolution |
object | null | How it was resolved, as a strategy name. null while the issue is still open; resolvedAt carries when. |
Get an issue’s research log
Section titled “Get an issue’s research log”GET /v1/issues/{id}/research-logs
What the AI knowledge review has already looked at for an issue. Each entry is one pass: its status, the searches it ran, whether it decided the knowledge base already covers the topic, and the summary it wrote. Read it before researching an issue again, so you do not repeat what has been checked. Newest first, not paginated.
Request
Section titled “Request”curl "https://api.mereon.ai/v1/issues/iss_3d81/research-logs" \ -H "Authorization: Bearer $MEREON_TOKEN"Response
Section titled “Response”{ "data": [ { "id": "log_52e0...", "status": "COMPLETE", "selfResolved": false, "findings": "The bulletin supersedes the manual, but the manual was never retired.", "searchQueries": ["wheel fastener torque", "torque bulletin"], "createdAt": "2026-07-29T08:12:00.000Z" } ]}| Field | Type | Notes |
|---|---|---|
status |
string | PENDING, IN_PROGRESS, COMPLETE, or FAILED. |
selfResolved |
boolean | null | true when the review decided the knowledge base already covers the topic. |
findings |
string | null | The review’s summary of what it found. null unless you can read the whole organization. |
searchQueries |
string[] | null | The searches it ran. null unless you can read the whole organization. |
The log is visible to whoever can read the issue. An issue the caller cannot read, for the reasons given above, answers 404 here too.
What the review wrote, findings and searchQueries, is the model’s own text from
a run that searched all of the organization’s content, so it can quote material a
narrower reader cannot open. The access rules return AI free text on an issue only
to admins, and this follows them: anyone who does not read the whole organization
sees that each pass ran and how it ended, with those two fields null.
File an issue
Section titled “File an issue”POST /v1/issues
Files an issue for your organization’s reviewers, as the form on the issues page
does: something wrong, missing, out of date, duplicated, conflicting or in the
wrong place. It is recorded as raised by the person behind the token, with
trigger set to MANUAL.
This needs a token granted the issue:write scope, which is an OAuth path today,
and the person behind it must be allowed to file issues in Mereon, which owners,
admins and team admins are. Both are checked on every call, so a demotion takes
effect on the next one. A member can read issues but not file them, and gets 403.
Request
Section titled “Request”curl -X POST "https://api.mereon.ai/v1/issues" \ -H "Authorization: Bearer $MEREON_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "STALE", "severity": "HIGH", "title": "The lockout steps show the old breaker panel", "description": "Step 3 shows the panel that was replaced in March.", "targets": [ { "targetType": "TOPIC", "targetId": "top_9f2a...", "targetTitle": "Lockout/Tagout" } ] }'| Field | Type | Notes |
|---|---|---|
type |
string | CONFLICT, DUPLICATE, MISCLASSIFICATION, STALE, CONTENT_GAP, STRUCTURE_GAP, or OTHER. UPDATE_HELD and RETIRED_ITEM_REAPPEARED are raised by Mereon and cannot be filed. |
severity |
string | CRITICAL, HIGH, MEDIUM, or LOW. |
title |
string | A short statement of the problem, up to 500 characters. It cannot be blank. |
description |
string | null | Optional. What is wrong and what would fix it, up to 10,000 characters. |
targets |
object[] | What the issue is about: 1 to 20 entries, each with targetType, targetId and targetTitle. |
assignedToRoleId |
string | null | Optional. A company role to assign it to, from GET /v1/company-roles. |
assignedToId |
string | null | Optional. A person to assign it to, from GET /v1/members. |
Each target names one thing:
| Field | Type | Notes |
|---|---|---|
targetType |
string | TOPIC, ITEM, SPACE, or CATEGORY. Every target of one issue has the same type. |
targetId |
string | null | A topic’s id from GET /v1/topics, an item’s from GET /v1/items/{id}, a space’s from GET /v1/spaces, or a category’s slug. Leave it out to name something by title alone, such as a topic your knowledge base is missing. |
targetTitle |
string | What the issue calls it, as people will read it. |
includeHistory |
boolean | Optional. File about this topic or item even though it is archived or superseded. Leave it out unless you mean this record rather than what replaced it. |
The body is checked as the app checks it. CONFLICT and DUPLICATE need at
least two targets, MISCLASSIFICATION cannot name a space or an item that only
exists as a step of another, and an id or assignee that is not in your
organization is a 400. A field the body does not take, trigger included, is
a 400 too, so a misspelt field is refused rather than ignored. A topic or
item you can read that is archived or superseded is a 422 unless that target
sets includeHistory to true. The error names its status and, when it was
corrected, the id of what replaced it.
Response
Section titled “Response”201 with the issue, in the shape GET /v1/issues/{id} returns,
so you do not have to read it back.
{ "id": "iss_7a10...", "type": "STALE", "severity": "HIGH", "status": "OPEN", "title": "The lockout steps show the old breaker panel", "description": "Step 3 shows the panel that was replaced in March.", "assignedToName": null, "assignedToRoleName": null, "raisedByName": "Alex Doe", "trigger": "MANUAL", "targets": [ { "id": "tgt_c3e9...", "targetType": "TOPIC", "targetId": "top_9f2a...", "title": "Lockout/Tagout", "blockType": null, "body": null, "handled": false } ], "resolution": null, "createdAt": "2026-10-13T09:00:00.000Z", "resolvedAt": null}Assigning the issue to a person or a company role notifies them, by email or text as their notification settings allow, as it does in the app. The person who filed it is not notified, and an issue left unassigned waits in the issue list.
Filing is limited to 10 calls a minute and 60 an hour per connection.
Decide an issue
Section titled “Decide an issue”These routes dismiss an issue, reopen one, change who it is assigned to, merge
a duplicate, choose the correct version of a conflict, or fill a content gap.
Each needs a token granted the issue:manage scope, which is an OAuth path
today, and the person behind it must be allowed to update issues in Mereon,
which owners, admins and team admins are. Both are checked on every call.
Filing stays on issue:write.
Each returns 200 with the issue, in the shape
GET /v1/issues/{id} returns. Deciding an issue is limited to
10 calls a minute and 40 an hour per connection, and each route has its own
budget.
When a decision can retrain people who were trained on the material it
replaces, leave retrainingIntent out to store none, which retrains nobody.
Send full_retake or changed_material when those people should train again.
retrainingDueDate is YYYY-MM-DD, and it belongs with one of those two
choices. A dismiss and a category upload have no retraining fields.
Superseding a claim, declaring one version canonical, reclassifying an issue, clearing every issue that blocks one resource, removing a note, and filing a proposal stay on the issues page.
Dismiss an issue
Section titled “Dismiss an issue”POST /v1/issues/{id}/dismiss
Closes an open issue with your reason. The reason is kept on the issue.
An update held because it would remove too much (UPDATE_HELD) cannot be
dismissed while it is held, and the call returns 400. It is answered in the
app, by publishing the update or having it read again. An issue asking whether
a retired item should come back (RETIRED_ITEM_REAPPEARED) can be dismissed,
or answered by restoring the item, which resolves it.
| Field | Type | Notes |
|---|---|---|
reason |
string | Why it is being dismissed, up to 10,000 characters. It cannot be blank. |
Reopen an issue
Section titled “Reopen an issue”POST /v1/issues/{id}/reopen
Reopens an issue that was resolved or dismissed. Send no body. An
UPDATE_HELD issue cannot be reopened, because its answer cannot be undone,
and the call returns 400.
Assign an issue
Section titled “Assign an issue”PUT /v1/issues/{id}/assignment
Replaces who the issue is assigned to. Both fields are required. An id sets
that side, and null clears it. Both null clears the assignment. A person
or role newly assigned is notified.
| Field | Type | Notes |
|---|---|---|
assignedToId |
string | null | A person from GET /v1/members, or null. |
assignedToRoleId |
string | null | A company role from GET /v1/company-roles, or null. |
Merge a duplicate
Section titled “Merge a duplicate”POST /v1/issues/{id}/merge
Keeps one topic a duplicate issue flags and supersedes the other. winnerId
and loserId have to be the two topics this issue flags, and two different
ones. A duplicate that flags more than two topics is resolved from the issues
page, where each one can be included.
| Field | Type | Notes |
|---|---|---|
winnerId |
string | The topic that remains. |
loserId |
string | The topic that is superseded. |
retrainingIntent |
string | Optional. none, full_retake, or changed_material. Leave it out to store none. |
retrainingDueDate |
string | null | Optional. YYYY-MM-DD, with full_retake or changed_material. |
Choose the correct version
Section titled “Choose the correct version”POST /v1/issues/{id}/select
Chooses the correct version of a conflict. selectedTargetId is the id of the
issue’s target, from GET /v1/issues/{id}, rather than the
topic or item behind it.
| Field | Type | Notes |
|---|---|---|
selectedTargetId |
string | The issue target that is correct. |
retrainingIntent |
string | Optional. none, full_retake, or changed_material. Leave it out to store none. |
retrainingDueDate |
string | null | Optional. YYYY-MM-DD, with full_retake or changed_material. |
Fill a content gap
Section titled “Fill a content gap”POST /v1/issues/{id}/content-gap
Fills a content gap in one of four ways. Send only that strategy’s fields. A
field that belongs to another strategy is a 400.
| Field | Type | Notes |
|---|---|---|
strategy |
string | contentGapUpload, contentGapCategoryUpload, contentGapLink, or contentGapNote. |
contentId |
string | A resource already in the library, from GET /v1/content, or one you uploaded. Required for an upload. |
via |
string | Optional, with an upload. CONTENT_GAP_UPLOAD or CONTENT_GAP_RECORD. Leave it out for a file. |
linkedType |
string | TOPIC. Required for a link. |
linkedId |
string | The topic that covers the gap, from GET /v1/topics. Required for a link. |
title |
string | The note’s title, up to 255 characters. Required for a note. |
body |
string | The note. Required for a note. |
override |
boolean | Optional, with a link or a note. Records the resolution after the coverage check declined. Leave it out unless that is what you want. |
retrainingIntent |
string | Optional, except on a category upload, which has none. Leave it out to store none. |
retrainingDueDate |
string | null | Optional. YYYY-MM-DD, with full_retake or changed_material. |
An upload names a resource you can read, or one you uploaded that has no
published version yet. A published resource has to be one you can read.
Anything else is a 404, the same answer as an id that does not exist. A
link names a topic you can read. A note is the text itself.
An upload whose processing has finished with no knowledge items is a 400.
Processing that is still running can fill the gap, and so can a resource that
already has knowledge items. A category upload does not wait on those items.