Console API

The same API we use in our Console is available via HTTP with API Key authentication. You may use this API to automate some of your tasks or build a mini-console of your own. This API allows you to access data and perform actions on a specific website. Account-level endpoints, such as creating a new website, managing subscription/billing are not available.

Getting Started

User Authentication

By default, the Console API is authenticated as the owner of the website, giving access to all the endpoints. Also, when performing an action, it will be tracked as performed by the owner. For example, if you moderate a comment via the API, you will see it was moderated by the owner of the website in comment history. However, you can change the authenticating user.

To authenticate as a different moderator, set the one of the following headers:

Note that the user must be a moderator of the website to authenticate as them.

Categories

Jump to each category

In this documentation, we use TypeScript syntax to describe the request and response objects. For example, type Response = { id: number } means that the response will be an object with an id property of type number.

Website

Endpoints:

Objects:

Get website data

type Request = {}
type Response = Website

Update website data

type Request = Website // expect id
type Response = Website

Comments

Endpoints:

Objects:

Get comments

GET /comments

type Request = {
    type: null | 'published' | 'pending' | 'deleted' | 'spam' | 'flagged',

    // filter by page
    page_id: number | null,

    // filter by user
    user_htid: string | null, // ID with type: hyvor_100 | sso_100

    // filter by IP
    ip_address: string | null,

    // filter by user's badge
    badge_id: number | null,

    // filter by other properties of the comment
    filter: null | 'unread' | 'unreplied' | 'guest' | 'has_questions' | 'has_links' | 'has_media',

    // search by comment text
    search: string | null,

    sort: 'newest' | 'oldest', // default: newest
    limit: number, // default: 25, max: 100
    offset: number, // default: 0
}

type Response = Comment[]

Note: If you set the search param, badge_id, sort, and type=flagged will be ignored.

Get a comment

GET /comment/{id}

type Request = {}
type Response = Comment

Get unread comment counts

Number of comments unread by moderators (has_mod_seen = 0) for each status. The total is the sum of all statuses.

GET /comments/unread-counts

type Request = {};
type Response = {
    published: number,
    pending: number,
    deleted: number,
    spam: number
}

Mark comments as read

Mark comments as read by moderators (has_mod_seen = 1). If status is set, only comments of that status will be marked as read. Otherwise, all comments will be marked as read. This process is asynchronous.

POST /comments/read

type Request = {
    status: null | 'published' | 'pending' | 'deleted' | 'spam',
}
type Response = {}

Update a comment

PATCH /comment/{id}

// all fields are optional
type Request = {
    status: 'published' | 'pending' | 'deleted' | 'spam',
    is_featured: boolean,
    is_loved: boolean,
    has_mod_seen: boolean,
    body: string,
}
type Response = Comment

Note that when updating the comment body, you should send the new body in ProseMirror JSON format. HTML support is not yet available. This may be non-ideal for most API-based use cases, but we are working on it.

Delete a comment

DELETE /comment/{id}

type Request = {}
type Response = {}

Reply to a comment

By default, the website owner's account will be used to reply. See User Authentication to customize it.

POST /comment/{id}/reply

type Request = {
    body: string // in ProseMirror format
}
type Response = Comment;

Vote on a comment

Add a vote to a comment. By default, the website owner's account will be used to vote. See User Authentication to customize it.

POST /comment/{id}/vote

type Request = {
    type: 'up' | 'down' | null, // null to remove a vote
}

Get voters of a comment

GET /comment/{id}/voters

type Request = {
    type: 'up' | 'down',
    limit: number, // default: 15
    offset: number,
};
type Response = LoggedInUser[]

Delete a vote on a comment

DELETE /comment/{id}/vote

type Request = {
    user_htid: string, // ID of the user who voted, required
}
type Response = {}

Get flags of a comment

GET /comment/{id}/flags

type Request = {
    limit: number, // default: 15, max: 30
    offset: number,
};
type Response = CommentFlag[]

Get history of a comment

Comment history is a list of all the times a comment's status was changed and the comment was edited. This includes actions from Rules, Spam detection, Moderators, and the comment author. This endpoint returns all data related to the comment history, compared to a minified version in the Comment object.

GET /comment/{id}/history

type Request = {};
type Response = CommentHistoryObject[]

Pages

Endpoints:

Replace {id} with the ID of the page. This is the ID of the page set by Hyvor Talk, not the identifier set by you in page-id attribute of the embed.

Objects:

Get pages

GET /pages

type Request = {
    search: string | null, // search by title or identifier
    filter: 'open' | 'closed' | 'premoderation_on',
    sort: 'newest' | 'oldest' | 'most_commented', // newest
    limit: number, // default: 25, max: 100
    offset: number, // 0
}

type Response = Page[];

Update a page

PATCH /page/{id}

// all fields are optional
type Request = {
    is_closed: boolean,
    is_premoderation_on: boolean
}

type Response = Page;

Reset page data

This endpoint will reset the data of the page (comments/reactions/ratings) you request. Use with caution. There is no undo.

POST /page/{id}/reset

// set which data to delete
type Request = {
    comments: boolean, // deletes comments and all data related to comments (votes, flags, etc.)
    reactions: boolean,
    ratings: boolean,
}

type Response = Page;

Move page data

Use this endpoint to move all data from one page to another. This is useful when you change the page-id attribute of the embed.

POST /page/{id}/move

type Request = {
    to_page_id: number,
}

type Response = {};

Delete a page

This endpoint will delete all data of the page (comments, reactions, ratings) and will delete the page too. Use with caution. There is no undo.

DELETE /page/{id}

type Request = {}
type Response = {};

Users

Endpoints:

{htid} is htid property in the User object.

Objects:

Get users

GET /users

type Request = {
    search: string | null,
    badge_id: number | null,
    plan_id: number | null,
    state: null | 'default' | 'banned' | 'shadowed' | 'trusted', // default null
    sort: 'recently_commented' | 'most_commented', // default: recently_commented
    limit: number, // default: 25, max: 100
    offset: number, // default: 0
}
type Response = LoggedInUser[]

search can be used to filter users:

  • by name
  • by exact HYVOR username (HYVOR users only)
  • by exact email (SSO users only)
  • by exact SSO ID

Update a user

PATCH /user/{htid}

// all fields are optional
type Request = {
    state: 'default' | 'banned' | 'shadowed' | 'trusted',
    state_ends_at: number | null, // unix timestamp when the banned users are automatically unbanned
    note: string | null,
    badge_ids: number[]
}

Moderate all comments of a user

This endpoint changes the status of all comments of the user to the status you specify.

POST /user/{htid}/moderate-comments

type Request = {
    state: 'published' | 'deleted' | 'spam' | 'pending',
}

Analytics

Endpoints:

Analytics data is generated based on the given timezone_offset in each endpoint. Set this to the offset in minutes (return value of JS getTimezoneOffset).

Get comment analytics

GET /analytics/comments

type Request = {
    timezone_offset: number,
    period: 'last_30_days' | 'last_12_months'
}
type Response = {
    timeseries: [
        {
            date: string, // Y-m-d
            counts: {
                published: number,
                pending: number,
                spam: number,
                deleted: number,
            }
        },
        ...
    ]
}

Get pageview analytics

GET /analytics/pageviews

type Request = {
    timezone_offset: number,
    period: 'last_30_days' | 'last_12_months'
}
type Response = {
    timeseries: [
        {
            date: string, // Y-m-d
            pageviews: number,
        },
        ...
    ]
}

Moderators

Moderators can access the Console to moderate comments/users and change settings of your website.

Endpoints:

Objects:

Get moderators

A website can have up to 100 moderators. All are returned from this endpoint.

GET /mods

type Request = {}
type Response = Mod[]

Update a moderator

PATCH /mod/{id}

// all fields are optional
type Request = {
    role: 'mod' | 'admin',
    sso_user_htid: string | null,
    is_alias_used: boolean
}
type Response = Mod

role: You can change the role of a mod to admin or vice versa. The role of the owner cannot be changed. See make-owner endpoint to transfer ownership of the website.

sso_user_htid: If your website uses SSO, you can set sso_user_htid to connect the moderator to a SSO user. This will allow the moderator to moderate comments in the embed. Also, when replying from the Console, their SSO account will be used instead of HYVOR account.

is_alias_used: If you have set up an alias for your website, comments of this moderator will be posted with the alias. This is useful if you want to post comments as your website.

Delete a moderator

DELETE /mod/{id}

type Request = {}
type Response = {}

Make a moderator the owner

This endpoint makes the given moderator the owner of the website. The current owner will be demoted to an admin. Only the current owner can access this endpoint.

POST /mod/{id}/make-owner

type Request = {}
type Response = {}

Get moderator invites

GET /mod-invites

type Request = {}
type Response = ModInvite[]

Invite a moderator

All moderators require a HYVOR account. username/email is the moderator's HYVOR account username/email. An invitation email will be sent to the user to confirm to join.

POST /mod-invite

type Request = {
    // either username or email is required
    username: string,
    email: string,
    role: 'mod' | 'admin',
}
type Response = ModInvite

Resend mod invite

POST /mod-invite/{id}/resend

type Request = {}
type Response = {}

Delete a mod invite

DELETE /mod-invite/{id}

type Request = {}
type Response = {}

Rules

Rules are used to automate moderation.

Endpoints:

Objects:

Get rules

GET /rules

type Request = {}
type Response = Rule[]

Create a rule

POST /rule

type Request = Rule // except id
type Response = Rule

Update a rule

PATCH /rule/{id}

type Request = Rule // except id
type Response = Rule

Delete a rule

DELETE /rule/{id}

type Request = {}
type Response = {}

IP

Moderators can block users at the IP-level.

Endpoints:

Objects:

Get IPs

This endpoint only returns IPs that have been banned, shadow-banned, trusted, or added a note about.

GET /ips

type Request = {
    limit: number, // default 50
    offset: number, // default 0
};
type Response = IP[]

Get IP

GET /ip/{ip}

type Request = {}
type Response = IP

Update IP

PATCH /ip/{ip}

type Request = {
    state: 'default' | 'banned' | 'shadowed' | 'trusted',
    state_ends_at: number | null,
    note: string | null
}
type Response = IP

Domains

Endpoints:

Objects:

Get domains

GET /domains

type Request = {}
type Response = Domain[]

Create a domain

POST /domain

type Request = {
    domain: string
} 
type Response = Domain

Update a domain

PATCH /domain/{id}

type Request = {
    domain: string
}
type Response = Domain

Delete a domain

DELETE /domain/{id}

type Request = {}
type Response = {}

Badges

Endpoints:

Objects:

Get badges

GET /badges

type Request = {}
type Response = Badge[]

Create a badge

POST /badge

type Request = {
    text: string,
    color: string,
    background_color: string,
}
type Response = Badge

Update a badge

PATCH /badge/{id}

// all fields are optional
type Request = {
    text: string,
    color: string,
    background_color: string,
}
type Response = Badge

Delete a badge

DELETE /badge/{id}

type Request = {}
type Response = {}

Single Sign-on

Endpoints:

Get SSO users

GET /sso/users

type Request = {
    limit: number, // default 50
    offset: number, // default 0
    search: string | null, // search by name, email, or given ID
}
type Response = LoggedInUser[]

Delete a SSO User

DELETE /sso/user

type Request = {
    id: string,
    data: boolean, // default false
}

id is the user ID in your system. By default, only user's profile data is deleted. All comments made by the user will be shown with the name "Anonymous user". If you want to also delete user's comments set data to true.

Data

Endpoints:

Objects:

Import Data

See importing docs for more details. Importing is asynchronous. We will notify the website owner via email when it's done.

POST /data/import

type Request = {
    format: 'hyvor_talk_json' | 'wordpress_xml' | 'disqus_xml',
    file: File,
    id_map: File, // optional
    // for wordpress_xml and disqus_xml
    identifier_type: 'post_id' | 'relative_path' | 'absolute_url'
}
type Response = Import

Get import history

GET /data/imports

type Request = {}
type Response = Import[] //default limit 25

Export data

See exporting docs. Exporting is asynchronous. We will email to the given (owner's email if empty) a link to download the file when it's ready.

POST /data/export

type Request = {
    format: 'hyvor_talk_json' | 'wordpress_xml',
    from: number, // optional unix timestamp, filter comments by date (lower bound)
    to: number, // optional unix timestamp, filter comments by date (upper bound)
    email: string, // optional, if empty, owner's email will be used
}
type Response = Export

Get export history

GET /data/exports

type Request = {}
type Response = Export[]

Webhooks

Endpoints:

Objects:

Get webhooks

GET /webhooks

type Request = {
    limit: number, // default 15
    offset: number, // default 0
}
type Response = Webhook[]

Integrations

Endpoints:

Get Slack integration

Your website can be connected to Slack to receive notifications about new comments and moderate comments.

GET /integrations/slack

type Request = {}
type Response = {
    is_connected: boolean // if Slack is connected
}

Create a Slack integration

POST /integration/slack

type Request = {}
type Response = {
    url: string, // redirect here for OAuth confirmation
}

Delete a Slack integration

DELETE /integration/slack

type Request = {}
type Response = {}

Misc

Endpoints:

Upload an image

POST /misc/upload

type Request = {
    image: File,
}
type Response = {
    url: string,
}

Objects

Website Object

interface Website {
    id: number,
    name: string,

    auth_type: 'hyvor' | 'sso',
    auth_sso_type: 'stateless' | 'openid',
    sso_stateless_private_key: string | null,
    sso_stateless_login_url: string | null,
    sso_openid_issuer_url: string | null,
    sso_openid_client_id: string | null,
    sso_openid_client_secret: string | null,
    premoderation_status: 'off' | 'guest' | 'guest_and_new_commenters' | 'all',

    is_realtime_on: boolean,
    realtime_typing: 'off' | 'on_without_typer' | 'on_with_typer',
    is_realtime_online_count_on: boolean,

    is_realtime_users_on: boolean,
    is_user_profile_on: boolean,
    is_keyboard_navigation_on: boolean,
    is_ip_collection_on: boolean,

    is_guest_commenting_on: boolean,
    guest_commenting_email: 'no'|'optional'|'required',
    guest_commenting_delay: number,
    default_avatar: string | null,

    text_comment_box: string | null,
    text_no_comments: string | null,
    text_reactions: string | null,
    text_ratings: string | null,
    text_comment_count_0: string | null,
    text_comment_count_1: string | null,
    text_comment_count_multi: string | null,

    top_widget: 'none' | 'reactions' | 'ratings',

    ui_box_shadow: number,
    ui_box_roundness: number,
    ui_box_border_size: number,
    ui_box_width: number | null,
    ui_button_roundness: number,

    ui_custom_css: string | null,

    is_ch_new_on: boolean,
    ch_new_color: string | null,
    ch_upvote_1_threshold: number | null,
    ch_upvote_2_threshold: number | null,
    ch_upvote_1_color: string | null,
    ch_upvote_2_color: string | null,

    is_profile_pictures_on: boolean,
    display_name_type: 'name' | 'username',

    comments_per_request: number,
    replies_per_request: number,
    nested_levels: number,
    display_replied_to_type: 'none' | 'deep' | 'all',
    comments_char_limit:  number,

    is_emoji_enabled: boolean,
    is_images_enabled: boolean,
    is_embed_enabled: boolean,
    is_math_enabled: boolean,

    is_spam_detection_on: boolean,

    vote_type: 'both'|'upvotes'|'none',
    is_vote_viewing_on: boolean,
    is_guest_voting_on: boolean,

    sort: 'top'|'newest'|'oldest',

    language: string,
    note: string | null,
    close_after_days: number,

    color_background_text: string | null,
    color_accent: string | null,
    color_accent_text: string | null,
    color_box: string | null,
    color_box_text: string | null,
    color_box_text_light: string | null,
    color_box_outline: string | null,
    color_box_outline_text: string | null,

    color_dark_background_text: string | null,
    color_dark_accent: string | null,
    color_dark_accent_text: string | null,
    color_dark_box: string | null,
    color_dark_box_text: string | null,
    color_dark_box_text_light: string | null,
    color_dark_box_outline: string | null,
    color_dark_box_outline_text: string | null,

    ratings_color: string | null,
    reaction_1: string | null,
    reaction_2: string | null,
    reaction_3: string | null,
    reaction_4: string | null,
    reaction_5: string | null,
    reaction_6: string | null,

    is_reaction_1_on: boolean,
    is_reaction_2_on: boolean,
    is_reaction_3_on: boolean,
    is_reaction_4_on: boolean,
    is_reaction_5_on: boolean,
    is_reaction_6_on: boolean,

    reaction_1_text: string | null,
    reaction_2_text: string | null,
    reaction_3_text: string | null,
    reaction_4_text: string | null,
    reaction_5_text: string | null,
    reaction_6_text: string | null,

    reaction_display_type: 'text'|'image'|'both',

    notif_channel: 'email' | 'slack' | 'off',
    email_send_to_users: boolean,
    email_report_frequency: string,
    email_company_name: string | null,
    email_company_logo_url: string | null,
    email_company_address: string | null,
    email_alternate_address: string | null,

    data_api_key: string | null,
    is_data_api_public: boolean,
    console_api_key: string | null,

    webhook_url: string | null,
    webhook_on_create: null,
    webhook_on_edit: null,
    webhook_on_delete: null,

    mod_badge_id: number | null,
    mod_alias_name: string | null,
    mod_alias_picture_url: string | null,

    memberships_enabled: boolean,
    memberships_currency: string | null,
    memberships_yearly_discount: number | null,

    email_report_daily: boolean,
    email_report_weekly: boolean,
    email_report_monthly: boolean,

    highlight_new: boolean,
    highlight_new_color: string,
    highlight_upvote_threshold_1: number | null,
    highlight_upvote_threshold_1_color: string,
    highlight_upvote_threshold_2: number | null,
    highlight_upvote_threshold_2_color: string,
}

Comment Object

interface Comment {
    id: number,
    user: User,
    page: Page,

    body: string, // JSON encoded in ProseMirror format
    body_html: string,
    body_text: string,

    status: 'published' | 'pending' | 'spam' | 'deleted',

    parent: Comment | null,
    ip_address: string | null,

    has_mod_seen: boolean,
    has_mod_replied: boolean,
    is_featured: boolean,
    is_loved: boolean,
    is_edited: boolean,

    has_questions: boolean,
    has_links: boolean,
    has_media: boolean,

    upvotes: number,
    downvotes: number,
    // current mod's vote
    user_vote: 'up' | 'down' | null,

    history: CommentHistory[]
}

Comment History Object

interface CommentHistory {
    created_at: number,
    type: 'rule' | 'spam_detection' | 'moderation' | 'edit',
    new_status: 'published' | 'pending' | 'spam' | 'deleted' | null,
    rule_id: null | number,
    mod_id: null | number
}

Comment Flag Object

interface CommentFlag {
    id: number,
    user: User,
    comment_id: number,
    reason: string | null, 
    has_mod_seen: boolean,
}

Comment Status History

interface CommentStatusHistory {
    id: number,
    comment_id: number,
    by: 'rule' | 'spam_detector' | 'user' // who changed the status
    user: User | null,
    rule: Rule | null,
    old_status: 'published' | 'pending' | 'spam' | 'deleted',
    new_status: 'published' | 'pending' | 'spam' | 'deleted',
}

Page Object

interface Page {
    id: number,
    created_at: number,
    identifier: string,
    title: string,
    url: string,
    is_closed: boolean,
    is_premoderation_on: boolean,
    author_email: string | null,
    comments_count: number,
    reactions: {
        superb: number,
        love: number,
        wow: number,
        sad: number,
        laugh: number,
        angry: number,
    },
    ratings: {
        average: number, // (float)
        count: number,
    },
}

User Object

There are multiple user object variants. LoggedInUser is used for logged-in users (HYVOR and SSO). GuestUser is used with comments when guest commenting is used. CommentingUser is a union of these two.

interface LoggedInUser {

    type: 'hyvor' | 'sso',
    id: number,
    htid: string,
    sso_id?: string, // only for SSO users
    name: string,
    email: string | null,
    username: string | null,
    picture_url: string | null,
    website_url: string | null,
    bio: string | null,
    location: string | null,

    badge_ids: number[],

    membership_plan_id: number | null,
    membership_stripe_customer_id: string | null,
}

interface GuestUser {
    name: string,
    email: string | null,
};

type CommentingUser = LoggedInUser | GuestUser

email is null for HYVOR users as we do not share HYVOR user's email addresses with the website moderators.

Mod Object

interface Mod {
    id: number,
    created_at: number,
    role: 'owner' | 'admin' | 'mod',
    user: User,
    sso_user_htid: string | null, // if connected to an SSO account
    is_alias_used: boolean
}

Mod Invite Object

interface ModInvite {
    id: number,
    created_at: number,
    role: 'admin' | 'mod',
    user: User,
    expires_at: number,
}

Domain Object

interface Domain {
    id: number,
    domain: string
}

Badge Object

interface Badge {
    id: number,
    text: string,
    background_color: string,
    color: string,
}

Rule Object

interface Rule {
    id: number,
    type: RuleType,

    // value to match against (this can be a number for some types)
    // regex is also supported in string values
    value: string,

    // the comment's status will be changed to this when the rule matches
    to_status: 'published' | 'pending' | 'spam' | 'deleted',

    priority: number, // higher priority rules are checked first
}

type RuleType = 'word_matches' |
    'link_domain_matches' |
    'user_name_matches' |
    'link_count_exceeds' |
    'flags_exceeds' |
    'downvotes_exceeds' |
    'upvotes_exceeds';

IP Object

interface IP {
    ip: string,
    note: string | null,
    state: 'default' | 'banned' | 'shadowed' | 'trusted',
    state_ends_at: number | null
}

Import Object

interface Import {
    id: number,
    created_at: number,
    filename: string,
    format: importFormat,
    pages_count: number,
    comments_count: number,
    status: importStatus,
    identifier: string | null
}

Export Object

interface Export {
    id: number,
    created_at: number,
    format: 'hyvor_talk_json' | 'wordpress_xml',
    status: 'pending' | 'completed' | 'failed',
    url: string | null,
}

Webhook Object

interface Webhook {
    id: number,
    created_at: number,
    url: string,
    event: string,
    status: 'pending' | 'completed' | 'failed',
    request_body: string | null,
    num_attempts: number,
    last_attempt_at: number | null,
    response_body: string | null,
    response_code: number | null,
}