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.
X-API-KEY
header to the API key you created.https://talk.hyvor.com/api/console/v1/{website_id}
website_id
in the URL. You can find your website ID in the Console.GET
- Read dataPOST
- Create dataPATCH
- Update dataDELETE
- Delete dataapplication/x-www-form-urlencoded
.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:
X-AUTH-USER-EMAIL
- Email of the user's HYVOR account to authenticate as.X-AUTH-USER-SSO-ID
- If your moderators are connected to an SSO account, you may use the SSO user ID (in your system) to authenticate as that user.Note that the user must be a moderator of the website to authenticate as them.
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 anid
property of typenumber
.
Endpoints:
GET /website
- Get website dataPATCH /website
- Update website dataObjects:
type Request = {}
type Response = Website
type Request = Website // expect id
type Response = Website
Endpoints:
GET /comments
- Get commentsGET /comment/{id}
- Get a commentGET /comments/unread-counts
- Get unread comment countsPOST /comments/read
- Mark comments as readPATCH /comment/{id}
- Update a commentDELETE /comment/{id}
- Delete a commentPOST /comment/{id}/reply
- Reply to a commentPOST /comment/{id}/vote
- Vote on a commentGET /comment/{id}/voters
- Get voters of a commentDELETE /comment/{id}/vote
- Delete a vote on a commentGET /comment/{id}/flags
- Get flags of a commentObjects:
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
, andtype=flagged
will be ignored.
GET /comment/{id}
type Request = {}
type Response = Comment
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 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 = {}
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 /comment/{id}
type Request = {}
type Response = {}
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;
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 /comment/{id}/voters
type Request = {
type: 'up' | 'down',
limit: number, // default: 15
offset: number,
};
type Response = LoggedInUser[]
DELETE /comment/{id}/vote
type Request = {
user_htid: string, // ID of the user who voted, required
}
type Response = {}
GET /comment/{id}/flags
type Request = {
limit: number, // default: 15, max: 30
offset: number,
};
type Response = CommentFlag[]
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[]
Endpoints:
GET /pages
- Get pagesPATCH /page/{id}
- Update a pagePOST /page/{id}/reset
- Reset page data POST /page/{id}/move
- Move page data to another pageDELETE /page/{id}
- Delete a pageReplace
{id}
with the ID of the page. This is the ID of the page set by Hyvor Talk, not the identifier set by you inpage-id
attribute of the embed.
Objects:
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[];
PATCH /page/{id}
// all fields are optional
type Request = {
is_closed: boolean,
is_premoderation_on: boolean
}
type Response = Page;
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;
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 = {};
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 = {};
Endpoints:
GET /users
- Get usersGET /user/{htid}
- Get a user, with statsPATCH /user/{htid}
- Update a userGET /user/{htid}/comments
- Get comments of a userPOST /user/{htid}/moderate-comments
- Moderate all comments of a userDELETE /user/{htid}
- Delete a user with all their data
{htid}
ishtid
property in the User object.
Objects:
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
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[]
}
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',
}
Endpoints:
GET /analytics/comments
- Get comment analyticsGET /analytics/pageviews
- Get pageview analyticsAnalytics 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 /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 /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 can access the Console to moderate comments/users and change settings of your website.
Endpoints:
GET /mods
- Get moderatorsPATCH /mod/{id}
- Update a moderatorDELETE /mod/{id}
- Delete a moderatorPOST /mod/{id}/make-owner
- Make a moderator the ownerGET /mod-invites
- Get moderator invitesPOST /mod-invite
- Invite a moderatorPOST /mod-invite/{id}/resend
- Resend mod inviteDELETE /mod-invite/{id}
- Delete a mod inviteObjects:
A website can have up to 100 moderators. All are returned from this endpoint.
GET /mods
type Request = {}
type Response = Mod[]
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 /mod/{id}
type Request = {}
type Response = {}
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 /mod-invites
type Request = {}
type Response = ModInvite[]
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
POST /mod-invite/{id}/resend
type Request = {}
type Response = {}
DELETE /mod-invite/{id}
type Request = {}
type Response = {}
Rules are used to automate moderation.
Endpoints:
GET /rules
- Get rulesPOST /rule
- Create a rulePATCH /rule/{id}
- Update a ruleDELETE /rule/{id}
- Delete a ruleObjects:
GET /rules
type Request = {}
type Response = Rule[]
POST /rule
type Request = Rule // except id
type Response = Rule
PATCH /rule/{id}
type Request = Rule // except id
type Response = Rule
DELETE /rule/{id}
type Request = {}
type Response = {}
Moderators can block users at the IP-level.
Endpoints:
GET /ips
- Get IPsGET /ip/{ip}
- Get IPPATCH /ip/{ip}
- Change state of an IPObjects:
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/{ip}
type Request = {}
type Response = IP
PATCH /ip/{ip}
type Request = {
state: 'default' | 'banned' | 'shadowed' | 'trusted',
state_ends_at: number | null,
note: string | null
}
type Response = IP
Endpoints:
GET /domains
- Get domainsPOST /domain
- Create a domainPATCH /domain/{id}
- Update a domainDELETE /domain/{id}
- Delete a domainObjects:
GET /domains
type Request = {}
type Response = Domain[]
POST /domain
type Request = {
domain: string
}
type Response = Domain
PATCH /domain/{id}
type Request = {
domain: string
}
type Response = Domain
DELETE /domain/{id}
type Request = {}
type Response = {}
Endpoints:
GET /badges
- Get badgesPOST /badge
- Create a badgePATCH /badge/{id}
- Update a badgeDELETE /badge/{id}
- Delete a badgeObjects:
GET /badges
type Request = {}
type Response = Badge[]
POST /badge
type Request = {
text: string,
color: string,
background_color: string,
}
type Response = Badge
PATCH /badge/{id}
// all fields are optional
type Request = {
text: string,
color: string,
background_color: string,
}
type Response = Badge
DELETE /badge/{id}
type Request = {}
type Response = {}
Endpoints:
GET /sso/users
- Get SSO usersDELETE /sso/user
- Delete a SSO userGET /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 /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 setdata
totrue
.
Endpoints:
POST /data/import
- Import dataGET /data/import
- Get import historyPOST /data/export
- Export dataGET /data/exports
- Get export historyObjects:
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 /data/imports
type Request = {}
type Response = Import[] //default limit 25
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',
email: string, // optional, if empty, owner's email will be used
}
type Response = Export
GET /data/exports
type Request = {}
type Response = Export[]
Endpoints:
GET /webhooks
- Get webhooksObjects:
GET /webhooks
type Request = {
limit: number, // default 15
offset: number, // default 0
}
type Response = Webhook[]
Endpoints:
GET /integrations/slack
- Get Slack integrationPOST /integration/slack
- Create a Slack integrationDELETE /integration/slack
- Delete a Slack integrationYour 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
}
POST /integration/slack
type Request = {}
type Response = {
url: string, // redirect here for OAuth confirmation
}
DELETE /integration/slack
type Request = {}
type Response = {}
Endpoints:
GET /misc/upload
- Upload an imagePOST /misc/upload
type Request = {
image: File,
}
type Response = {
url: string,
}
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,
}
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[]
}
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
}
interface CommentFlag {
id: number,
user: User,
comment_id: number,
reason: string | null,
has_mod_seen: boolean,
}
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',
}
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,
},
}
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
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
}
interface ModInvite {
id: number,
created_at: number,
role: 'admin' | 'mod',
user: User,
expires_at: number,
}
interface Domain {
id: number,
domain: string
}
interface Badge {
id: number,
text: string,
background_color: string,
color: string,
}
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';
interface IP {
ip: string,
note: string | null,
state: 'default' | 'banned' | 'shadowed' | 'trusted',
state_ends_at: number | null
}
interface Import {
id: number,
created_at: number,
filename: string,
format: importFormat,
pages_count: number,
comments_count: number,
status: importStatus,
identifier: string | null
}
interface Export {
id: number,
created_at: number,
format: 'hyvor_talk_json' | 'wordpress_xml',
status: 'pending' | 'completed' | 'failed',
url: string | null,
}
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,
}