Skip to content

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.

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.

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.
Terminal window
curl "https://api.mereon.ai/v1/issues?status=OPEN&severity=HIGH" \
-H "Authorization: Bearer $MEREON_TOKEN"
{
"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
}
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 /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.

Terminal window
curl "https://api.mereon.ai/v1/issues/iss_3d81" \
-H "Authorization: Bearer $MEREON_TOKEN"
{
"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 /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.

Terminal window
curl "https://api.mereon.ai/v1/issues/iss_3d81/research-logs" \
-H "Authorization: Bearer $MEREON_TOKEN"
{
"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.

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.

Terminal window
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.

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.

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.

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.

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.

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.

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.

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.

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.