{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/rumoro-dev/json-schema/rumoro-dev-mention-schema.json",
  "title": "Mention",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/rumoro-dev-openapi.json#/components/schemas/Mention",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "The mention's id (mm_...). A mention is one post matched to one keyword, so a post that matches two keywords has two ids."
    },
    "status": {
      "type": "string",
      "enum": [
        "open",
        "ignored",
        "done"
      ],
      "description": "open means no one has handled it. ignored means you hid it from the feed and channels. done means it was handled."
    },
    "relevant": {
      "type": "boolean",
      "description": "True when the classifier's relevance score is 40 or more, the delivery threshold."
    },
    "delivered": {
      "type": "boolean",
      "description": "true once any channel of yours received it."
    },
    "priority": {
      "type": "number",
      "description": "How much the mention deserves attention, with one decimal, worked out when you read it. It adds half the relevance, points for the author's followers (8 when unknown) and points for the strongest intent (from 20 for buy intent down to 5 for praise). It then takes off 2 points per day of age, at most 20."
    },
    "keyword": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "The keyword's id (kw_...)."
        },
        "term": {
          "type": "string",
          "description": "The term being tracked."
        },
        "group": {
          "$ref": "#/$defs/GroupRef"
        }
      },
      "required": [
        "id",
        "term",
        "group"
      ],
      "description": "The keyword the post matched, with its group."
    },
    "post": {
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "bluesky",
            "hackernews",
            "github",
            "stackoverflow",
            "devto",
            "reddit",
            "x",
            "youtube",
            "news",
            "linkedin",
            "tiktok",
            "instagram",
            "appstore",
            "googleplay",
            "trustpilot",
            "googlemaps",
            "rss"
          ],
          "description": "The platform. Review platforms are appstore, googleplay, trustpilot and googlemaps (a place's Google reviews)."
        },
        "kind": {
          "type": "string",
          "enum": [
            "post",
            "comment"
          ],
          "description": "post: a top-level post. comment: anything that answers a post or another comment (a Hacker News comment, an X or Bluesky reply, a Stack Overflow answer, a comment read from a thread)."
        },
        "url": {
          "type": "string",
          "description": "A stable link to the post."
        },
        "text": {
          "type": "string",
          "description": "The title and body, cut to 8 KB when the post is collected."
        },
        "title": {
          "type": [
            "string",
            "null"
          ],
          "description": "The post's title on platforms that have titles, such as Hacker News stories, Reddit threads, GitHub issues and pull requests, Stack Overflow questions, DEV articles, YouTube videos, news articles and reviews with a title. Null on platforms without titles (X, Bluesky, LinkedIn) and for posts collected before October 2026."
        },
        "imageUrl": {
          "type": [
            "string",
            "null"
          ],
          "description": "Preview image sent by the platform, or null. Examples are YouTube thumbnails, DEV covers, news sharing images and Bluesky images or link cards."
        },
        "links": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Up to 20 links found in the post, in the order they appear. Empty when there are none and for posts collected before September 2026."
        },
        "publishedAt": {
          "type": "string",
          "description": "When the post went live on the platform."
        },
        "engagement": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "likes": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "reposts": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "replies": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "quotes": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "views": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "bookmarks": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            }
          },
          "required": [
            "likes",
            "reposts",
            "replies",
            "quotes",
            "views",
            "bookmarks"
          ],
          "description": "Engagement numbers as the platform gave them when the post was collected, often a few minutes after it was written. A number the platform lacks is null. The whole object is null on platforms that report none and for posts collected before September 2026. X reports all six."
        },
        "replyTo": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "author": {
              "type": [
                "string",
                "null"
              ],
              "description": "Who wrote the parent post, as named on the platform."
            },
            "url": {
              "type": [
                "string",
                "null"
              ],
              "description": "Link to the parent post."
            },
            "text": {
              "type": [
                "string",
                "null"
              ],
              "description": "Text of the parent post, if the platform provided it."
            }
          },
          "required": [
            "author",
            "url",
            "text"
          ],
          "description": "The post being replied to, on X and Bluesky. Null when the post is not a reply."
        }
      },
      "required": [
        "platform",
        "kind",
        "url",
        "text",
        "title",
        "imageUrl",
        "links",
        "publishedAt",
        "engagement",
        "replyTo"
      ]
    },
    "parentMentionId": {
      "type": [
        "string",
        "null"
      ],
      "description": "For a comment whose parent post or comment is itself one of your mentions: that mention's id (mm_...). Null otherwise."
    },
    "author": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "id": {
          "type": [
            "string",
            "null"
          ],
          "description": "aut_... id for GET /v1/people/{id}, or null while the account isn't in your audience yet."
        },
        "name": {
          "type": [
            "string",
            "null"
          ],
          "description": "The name shown on the platform."
        },
        "handle": {
          "type": [
            "string",
            "null"
          ],
          "description": "The handle taken from the profile link, written the platform's way (@name, u/name). Null on platforms without handles."
        },
        "url": {
          "type": [
            "string",
            "null"
          ],
          "description": "Link to the profile."
        },
        "avatarUrl": {
          "type": [
            "string",
            "null"
          ],
          "description": "Avatar URL, or null if the platform has none."
        },
        "followers": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Followers at the time of their latest post. Null if the platform has no follower count."
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Tags your workspace gave this person."
        }
      },
      "required": [
        "id",
        "name",
        "handle",
        "url",
        "avatarUrl",
        "followers",
        "tags"
      ],
      "description": "The author of the post. Null when the platform gave no author."
    },
    "review": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "rating": {
          "type": "integer",
          "minimum": 1,
          "maximum": 5,
          "description": "The reviewer's star rating."
        },
        "ratingMax": {
          "type": "integer",
          "description": "The highest possible rating, which is 5 on both stores."
        },
        "title": {
          "type": [
            "string",
            "null"
          ],
          "description": "The review's title. Null where reviews have none, as on Google Play."
        },
        "version": {
          "type": [
            "string",
            "null"
          ],
          "description": "The app version the reviewer used, if the store reports it."
        },
        "country": {
          "type": [
            "string",
            "null"
          ],
          "description": "A lowercase two-letter code. On the App Store and Google Play it is the storefront the review was read in. On Trustpilot it is the reviewer's country. Null when the platform gives none."
        },
        "verified": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "True when Trustpilot marks the review as verified. Null on platforms without that mark."
        },
        "response": {
          "type": [
            "string",
            "null"
          ],
          "description": "The developer's reply at the time the review was collected. Null when there was none."
        },
        "responseAt": {
          "type": [
            "string",
            "null"
          ],
          "description": "Time of the developer's reply."
        },
        "app": {
          "type": "object",
          "properties": {
            "platform": {
              "type": "string",
              "enum": [
                "appstore",
                "googleplay",
                "trustpilot",
                "googlemaps"
              ],
              "description": "Where the reviews come from. appstore is the Apple App Store, googleplay is Google Play, trustpilot is a company's Trustpilot page and googlemaps is the Google reviews of a place."
            },
            "id": {
              "type": "string",
              "description": "The app's id in the store."
            },
            "url": {
              "type": "string",
              "description": "Link to the app in the store."
            }
          },
          "required": [
            "platform",
            "id",
            "url"
          ],
          "description": "The app the review is about."
        }
      },
      "required": [
        "rating",
        "ratingMax",
        "title",
        "version",
        "country",
        "verified",
        "response",
        "responseAt",
        "app"
      ],
      "description": "Details of an app store review. Null for any other post. The stars set the sentiment, with 4 and 5 positive, 3 neutral, and 1 and 2 negative. A review always counts as relevant, because you picked the app."
    },
    "classification": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "relevance": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 100,
          "description": "Score out of 100, or null if classification failed."
        },
        "sentiment": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "positive",
            "neutral",
            "negative"
          ],
          "description": "The sentiment the classifier assigned."
        },
        "intents": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Tags for intent and topic. The values are buy_intent, question, complaint, praise, comparison, churn_intent (moving away from the keyword), bug_report, pricing, hiring, event, promotional, testimonial (a user recommending it from experience), industry_insight (analysis or numbers about the market), launch (a new product or feature announced) and feedback (an idea or request for it)."
        },
        "automated": {
          "type": "boolean",
          "description": "A label for posts that look machine-made. Examples are bot or app accounts, scheduled or template posts and obvious AI summaries. Such mentions are kept, delivered and billed as usual. false until judged."
        },
        "language": {
          "type": [
            "string",
            "null"
          ],
          "description": "ISO 639-1 code (en, es, de) of the post's language. Null if unknown or scored before languages were stored."
        },
        "confidence": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 1,
          "description": "The classifier's confidence in its relevance score, from 0 to 1. Null when the fallback model scored the post, or when it was scored before confidence was stored."
        },
        "uncertain": {
          "type": "boolean",
          "description": "True when a person should check the score, because confidence is below 0.4 or the model that wrote the note disagreed with it. It is a hint for reviewers and never hides a mention."
        },
        "note": {
          "type": [
            "string",
            "null"
          ],
          "description": "A sentence from the classifier on why it gave this score."
        },
        "failed": {
          "type": "boolean",
          "description": "Set when the model failed to score the post. Such posts remain in the feed and cost nothing."
        },
        "feedback": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "relevant": {
              "type": [
                "boolean",
                "null"
              ],
              "description": "Whether you judged it relevant. Null if you only changed the sentiment."
            },
            "sentiment": {
              "type": [
                "string",
                "null"
              ],
              "enum": [
                "positive",
                "neutral",
                "negative"
              ],
              "description": "The sentiment you set. Null if you only judged relevance."
            },
            "at": {
              "type": "string",
              "description": "When the latest feedback was given."
            },
            "original": {
              "type": "object",
              "properties": {
                "relevance": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 0,
                  "maximum": 100,
                  "description": "The classifier's score before your feedback. Null if it had not scored the post."
                },
                "sentiment": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "positive",
                    "neutral",
                    "negative"
                  ],
                  "description": "The classifier's sentiment before you changed it."
                }
              },
              "required": [
                "relevance",
                "sentiment"
              ],
              "description": "The classifier's values that your feedback overrode."
            }
          },
          "required": [
            "relevant",
            "sentiment",
            "at",
            "original"
          ],
          "description": "Feedback from your team, or null. Judging relevance sets `relevance` to 100 or 0 and updates `relevant` to match. A new sentiment replaces `sentiment`. Lists, filters, digests and reports all use the corrected values."
        }
      },
      "required": [
        "relevance",
        "sentiment",
        "intents",
        "automated",
        "language",
        "confidence",
        "uncertain",
        "note",
        "failed",
        "feedback"
      ],
      "description": "The classifier's result with your feedback applied. Null while the post waits to be classified."
    },
    "triage": {
      "type": "object",
      "properties": {
        "assignee": {
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "id": {
              "type": "string"
            },
            "name": {
              "type": [
                "string",
                "null"
              ]
            },
            "email": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "id",
            "name",
            "email"
          ],
          "description": "The member handling this mention. Null if nobody is assigned."
        },
        "snoozedUntil": {
          "type": [
            "string",
            "null"
          ],
          "description": "The mention is hidden from the feed until this time. Null when it is not snoozed."
        },
        "note": {
          "type": [
            "string",
            "null"
          ],
          "description": "A note for your team, or null."
        }
      },
      "required": [
        "assignee",
        "snoozedUntil",
        "note"
      ]
    },
    "stats": {
      "type": "object",
      "properties": {
        "comments": {
          "type": "integer",
          "minimum": 0,
          "description": "Comments of this post you received (GET /v1/mentions/{id}/comments); 0 until its thread is read, and for keywords without comments."
        }
      },
      "required": [
        "comments"
      ],
      "description": "Computed counts."
    },
    "createdAt": {
      "type": "string",
      "description": "When the match was stored. The feed is sorted by this by default."
    }
  },
  "required": [
    "id",
    "status",
    "relevant",
    "delivered",
    "priority",
    "keyword",
    "post",
    "parentMentionId",
    "author",
    "review",
    "classification",
    "triage",
    "stats",
    "createdAt"
  ],
  "$defs": {
    "GroupRef": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "The group's id (grp_...)."
        },
        "name": {
          "type": "string",
          "description": "Name of the group."
        },
        "externalId": {
          "type": [
            "string",
            "null"
          ],
          "description": "An id from your own system, or null."
        },
        "isDefault": {
          "type": "boolean",
          "description": "True for the workspace's default group. New keywords go there when you don't name a group."
        }
      },
      "required": [
        "id",
        "name",
        "externalId",
        "isDefault"
      ],
      "description": "The keyword's group."
    }
  }
}