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
.
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 a resource or a list or resourcePOST
- Create a resource, or perform an actionPATCH
- Update a resourceDELETE
- Delete a resourceapplication/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 an id
property of type number
.
Endpoints:
GET /website
- Get website dataPATCH /website
- Update website dataObjects:
type Request = {}
type Response = Website
type Request = Website // except id
type Response = Website
Endpoints:
GET /comments
- Get commentsGET /comments/unread-counts
- Get unread comment countsPOST /comments/read
- Mark comments as readGET /comment/{id}
- Get a commentPATCH /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 commentDELETE /comment/{id}/flag
- Delete a flag on a commentPOST /comments/bulk-moderate
- Moderate multiple commentsObjects:
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,
user_sso_id: string | null, // SSO user ID
// 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: null | 'newest' | 'oldest', // default: newest
limit: number | null, // default: 50, max: 100
offset: number | null // default: 0
}
type Response = Comment[]
Note: If you set the search
param, badge_id
, sort
,
and type=flagged
will be ignored.
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,
spam: number,
deleted: 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 = {}
GET /comment/{id}
type Request = {}
type Response = Comment
PATCH /comment/{id}
type Request = {
status: null | 'published' | 'pending' | 'deleted' | 'spam',
is_featured: boolean | null,
is_loved: boolean | null,
has_mod_seen: boolean | null,
body: string | null,
created_at: number | null, // unix timestamp,
guest_name: string | null,
}
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;
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
}
type Response = Comment;
GET /comment/{id}/voters
type Request = {
type: 'up' | 'down',
limit: number | null, // default: 25, max: 100
offset: number|null // default: 0
};
type Response = LoggedInUser[]
DELETE /comment/{id}/vote
type Request = {
user_htid: string // ID of the user who voted, required. Ex: hyvor_100 | sso_100
}
type Response = {}
GET /comment/{id}/flags
type Request = {
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
};
type Response = Flag[]
DELETE /comment/{id}/flag
type Request = {
flag_id: number // ID of the flag to delete
}
type Response = {}
POST /comments/bulk-moderate
type Request = {
// only one of user_htid/user_sso_id, ip_address or comment_ids should be set
user_htid: string | null, // ID with type: hyvor_100 | sso_100
user_sso_id: string | null, // SSO user ID
ip_address: string | null,
comment_ids: number[] | null,
status: 'published' | 'pending' | 'deleted' | 'spam'
}
type Response = {}
Endpoints:
GET /reactions
- Get reactionsDELETE /reaction/{id}
- Delete a reactionObjects:
GET /reactions
type Request = {
page_id: number | null,
user_htid: string | null, // ID with type: hyvor_100 | sso_100
user_sso_id: string | null, // SSO user ID
type: null | 'superb' | 'love' | 'wow' | 'sad' | 'laugh' | 'angry',
limit: number | null, // default: 50, max: 100
offset: number | null // default: 0
}
type Response = Reaction[]
DELETE /reaction/{id}
type Request = {}
type Response = {}
Endpoints:
GET /ratings
- Get ratingsDELETE /rating/{id}
- Delete a ratingObjects:
GET /ratings
type Request = {
page_id: number | null,
user_htid: string | null, // ID with type: hyvor_100 | sso_100
user_sso_id: string | null, // SSO user ID
rating: number | null, // min: 1, max: 5
limit: number | null, // default: 50, max: 100
offset: number | null // default: 0
}
type Response = Rating[]
DELETE /rating/{id}
type Request = {}
type Response = {}
Endpoints:
GET /pages
- Get pagesPATCH /page/{id}
- Update a pagePOST /page/{id}/reset
- Reset page dataPOST /page/{id}/move
- Move page data to another pageDELETE /page/{id}
- Delete a page{id}
in the URLBy default {id}
is the ID of the page set by Hyvor Talk (internal). You can
find this in the Page Object. However, for most cases, you may want
to use the
page-id
attribute you set in the embed. To use this, set the
HTTP header X-ID-Type
to page_id
.
Objects:
GET /pages
type Request = {
search: string | null, // search by title or identifier
filter: null | 'open' | 'closed' | 'premoderation_on',
sort: null | 'newest' | 'oldest' | 'recently_commented' | 'most_commented' | 'most_reactions' | 'most_ratings', // default: newest
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = Page[];
PATCH /page/{id}
type Request = {
is_closed: boolean | null,
is_premoderation_on: boolean | null
}
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
type Request = {
is_closed: boolean | null,
is_premoderation_on: boolean | null
}
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/{id}
- Get a user, with statsPATCH /user/{id}
- Update a userGET /user/{id}/counts
- Get comments
and flags count of a userDELETE /user/{id}
- Delete a userGET /user/{id}/email-notification
- Get email notification subscription status of a userPOST /user/{id}/email-notification
- Update email notification subscription status of a user{id}
in the URLBy default, {id}
is the htid
property in the
User object. If you use Single Sign-on, it
makes sense to use the ID of the user in your system. To do this, set the HTTP header
X-ID-Type
to sso_user_id
.
Objects:
GET /users
type Request = {
search: string | null,
badge_id: number | null,
plan_id: number | null,
last_ip_address: string | null,
state: null | 'default' | 'banned' | 'shadowed' | 'trusted', // default: null
sort: null | 'recently_commented' | 'most_commented' | 'recently_seen' | 'recently_joined', // default: recently_commented
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = LoggedInUser[]
search
can be used to filter users:
GET /user/{id}
type Request = {}
type Response = LoggedInUser
PATCH /user/{id}
type Request = {
state: null | 'default' | 'banned' | 'shadowed' | 'trusted',
state_ends_at: number | null, // unix timestamp when the banned users are automatically unbanned
note: string | null,
badge_ids: number[] | null
}
type Response = LoggedInUser
GET /user/{id}/counts
type Request = {}
type Response = {
comments: {
total: number,
published: number,
pending: number,
spam: number,
deleted: number
},
flags: {
received: number,
given: number
}
}
DELETE /user/{id}
type Request = {
data: boolean // set true to delete the user with all its data
}
type Response = {}
GET /user/{id}/email-notification
type Request = {}
type Response = {
reply: boolean,
mention: boolean
}
POST /user/{id}/email-notification
type Request = {
reply: boolean | null,
mention: boolean | null
}
type Response = {}
Endpoints:
GET /analytics/stats
- Get website statisticsGET /analytics/credits
- Get credit analyticsGET /analytics/comments
- Get comment analyticsGET /analytics/stats
type Request = {}
type Response = {
comments: {
total: number,
last_30d: number
},
users: {
total: number,
last_30d: number
},
members: {
total: number,
last_30d: number
},
newsletter_subscribers: {
total: number,
last_30d: number
}
}
GET /analytics/credits
type Request = {
event: null | 'embed_comments' | 'embed_gated_content' | 'embed_comment_counts' |
'embed_newsletter' | 'email_notifications' | 'email_newsletter' |
'spam_detection_akismet' | 'spam_detection_fortguard' | 'api_data',
start_timestamp: number | null,
end_timestamp: number | null,
group_by: 'day' | 'week' | 'month' | 'year'
}
type Response = {
timeseries: [
{
date: string, // YYYY-MM-DD 00:00:00,
credits: number,
events: number
},
...
]
}
GET /analytics/comments
type Request = {
start_timestamp: number | null,
end_timestamp: number | null,
group_by: 'day' | 'week' | 'month' | 'year'
}
type Response = {
timeseries: [
{
date: string, // YYYY-MM-DD 00:00:00,
count: number,
published: number,
pending: number,
spam: number,
deleted: 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 a moderator
inviteDELETE /mod-invite/{id}
- Delete a mod inviteObjects:
GET /mods
type Request = {}
type Response = Mod[]
PATCH /mod/{id}
type Request = {
role: null | 'mod' | 'admin',
sso_user_htid: string | null, // ID with type: hyvor_100 | sso_100
is_alias_used: boolean | null
}
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
or email
is the moderator's
HYVOR account username/email. An invitation email will be sent to the user and the user will be promoted
to a moderator/admin once the invitation is accepted.
POST /mod-invite
type Request = {
// either username or email is required
username: string | null,
email: string | null,
role: 'mod' | 'admin',
}
type Response = ModInvite
POST /mod-invite/{id}/resend
type Request = {}
type Response = {}
DELETE /mod-invite/{id}
type Request = {}
type Response = {}
Endpoints:
GET /memberships/stripe/connect
- Get Stripe StatusPOST /memberships/stripe/connect/
- Get Stripe onboarding
URLGET /membership-plans
- Get membership plansPOST /membership-plan
- Create a membership
planPATCH /membership-plan/{id}
- Update a
membership planDELETE /membership-plan/{id}
- Delete
a membership planGET /memberships/gated-content
- Get gated contentPOST /memberships/gated-content
- Create gated
contentPATCH /memberships/gated-content/{id}
-
Update gated contentDELETE /memberships/gated-content/{id}
-
Delete gated contentObjects:
GET /memberships/stripe/connect
type Request = {}
type Response = {
is_connected: boolean,
data: StripeConnect | null
}
POST /memberships/stripe/connect/
type Request = {}
type Response = {
url: string // redirect here for Stripe onboarding
}
GET /membership-plans
type Request = {}
type Response = Plan[]
A website can have a maximum of 3 membership plans.
POST /membership-plan
type Request = {
name: string,
description: string | null,
features: string | null, // max length: 1024
monthly_price: number, // min: 1, max: 9999 Ex: 4.99
badge_id: number | null
}
type Response = Plan
PATCH /membership-plan/{id}
type Request = {
name: string | null,
description: string | null,
features: string | null, // max length: 1024
monthly_price: number | null, // min: 1, max: 9999 Ex: 4.99
badge_id: number | null
}
type Response = Plan
DELETE /membership-plan/{id}
type Request = {}
type Response = {}
GET /memberships/gated-content
type Request = {
limit: number | null, // default: 10, max: 100
offset: number | null // default: 0
}
type Response = GatedContent[]
POST /memberships/gated-content
type Request = {
key: string, // key must be unique
content: string, // max length: 60,000
gate: string | null, // max length: 60,000
minimum_plan_id: number | null
}
type Response = GatedContent
PATCH /memberships/gated-content/{id}
type Request = {
key: string | null, // key must be unique
content: string | null, // max length: 60,000
gate: string | null, // max length: 60,000
minimum_plan_id: number | null
}
type Response = GatedContent
DELETE /memberships/gated-content/{id}
type Request = {}
type Response = {}
Endpoints:
GET /newsletter
- Get newsletterGET /newsletter/subscribers
- Get newsletter
subscribersPOST /newsletter/subscribers/import
- Import newsletter subscribersPOST /newsletter/subscribers
- Create
newsletter subscribersPATCH /newsletter/subscriber/{id}
- Update a newsletter subscriberDELETE /newsletter/subscriber/{id}
- Delete a newsletter subscriberGET /newsletter/issues
- Get issuesPOST /newsletter/issue
- Create an issueGET /newsletter/issue/{id}
- Get an issuePOST /newsletter/issue/{id}/send
- Send an issuePOST /newsletter/issue/{id}/test
- Send a testGET /newsletter/issue/{id}/preview
- Preview an
issueGET /newsletter/issue/{id}/progress
- Get
issue progressGET /newsletter/issue/{id}/report
- Get issue
reportGET /newsletter/issue/{id}/sends
- Get issue
sendsPATCH /newsletter/issue/{id}
- Update an issueDELETE /newsletter/issue/{id}
- Delete an issuePOST /newsletter/segment
- Create a segmentPATCH /newsletter/segment/{id}
- Update a segmentDELETE /newsletter/segment/{id}
- Delete a segmentPOST /newsletter/segment/{id}/add
- Add to a segmentObjects:
GET /newsletter
type Request = {}
type Response = {
segments: Segment[],
issues: Issue[],
subscribers_change_30d: number,
subscribers_total: number,
issues_sent_total: number
}
GET /newsletter/subscribers
type Request = {
status: null | 'subscribed' | 'unsubscribed' | 'pending',
segment: number | null,
emails: string[] | null, // maxCount: 100
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = NewsletterSubscriber[]
POST /newsletter/subscribers/import
type Request = {
file: File,
segments: number[] | null,
status: null | 'subscribed' | 'unsubscribed' | 'pending'
}
type Response = Job
POST /newsletter/subscribers
type Request = {
emails: string[], // maxCount: 100
segments: number[] | null,
on_duplicate: null | 'error' | 'update' | 'ignore', // default: error
on_update_segments: null | 'replace' | 'extend' // default: replace
}
type Response = NewsletterSubscriber[]
PATCH /newsletter/subscriber/{id}
type Request = {
email: string | null,
status: null | 'subscribed' | 'unsubscribed' | 'pending',
segments: number[] | null,
on_update_segments: null | 'replace' | 'extend' // default: replace
}
type Response = NewsletterSubscriber
DELETE /newsletter/subscriber/{id}
type Request = {}
type Response = {}
GET /newsletter/issues
type Request = {
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = Issue[]
POST /newsletter/issue
type Request = {}
type Response = Issue
GET /newsletter/issue/{id}
type Request = {}
type Response = Issue
Only draft issues can be sent.
POST /newsletter/issue/{id}/send
type Request = {}
type Response = Issue
POST /newsletter/issue/{id}/test
type Request = {
email: string
}
type Response = {}
GET /newsletter/issue/{id}/preview
type Request = {}
type Response = {
html: string
}
GET /newsletter/issue/{id}/progress
type Request = {}
type Response = {
total: number,
pending: number,
sent: number,
progress: number // percentage
}
GET /newsletter/issue/{id}/report
type Request = {}
type Response = {
counts: {
total: number,
sent: number,
failed: number,
pending: number,
opened: number,
clicked: number,
unsubscribed: number,
bounced: number,
complained: number
}
}
GET /newsletter/issue/{id}/sends
type Request = {
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = Send[]
PATCH /newsletter/issue/{id}
type Request = {
subject: string | null,
from_name: string | null,
from_email: string | null,
reply_to_email: string | null,
content: string | null,
segments: number[] | null
}
type Response = Issue
DELETE /newsletter/issue/{id}
type Request = {}
type Response = {}
POST /newsletter/segment
type Request = {
name: string | null,
description: string | null
}
type Response = Segment
PATCH /newsletter/segment/{id}
type Request = {
name: string | null,
description: string | null
}
type Response = Segment
DELETE /newsletter/segment/{id}
type Request = {}
type Response = {}
POST /newsletter/segment/{id}/add
type Request = {}
type Response = {}
Endpoints:
POST /email/domain
- Create an email domainPOST /email/domain/verify
- Verify an email domainDELETE /email/domain
- Delete an email domainObjects:
POST /email/domain
type Request = {
domain: string // Ex: example.com
}
type Response = EmailDomain
POST /email/domain/verify
type Request = {}
type Response = {
data: {
verified: string,
debug: string | null
},
domain: EmailDomain
}
DELETE /email/domain
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 = {}
Endpoints:
GET /email-logs
- Get email logsObjects:
GET /email-logs
type Request = {
type: null | 'reply' | 'mention' | 'mod' | 'author',
user_htid: string | null, // ID with type: hyvor_100 | sso_100
user_sso_id: string | null,
limit: number | null, // default: 50, max: 100
offset: number | null // default: 0
}
type Response = EmailLog[]
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 | null, // default: 25, max: 100
offset: number | null // default: 0
};
type Response = IP[]
GET /ip/{ip}
type Request = {}
type Response = IP
PATCH /ip/{ip}
type Request = {
state: null | 'default' | 'banned' | 'shadowed' | 'trusted',
note: string | null,
state_ends_at: number | 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, // max length: 50
color: string, // hex color code Ex: #ff0000
background_color: string // hex color code Ex: #0000ff
}
type Response = Badge
PATCH /badge/{id}
type Request = {
text: string | null, // max length: 50
color: string | null, // hex color code Ex: #ff0000
background_color: string | null // hex color code Ex: #0000ff
}
type Response = Badge
DELETE /badge/{id}
type Request = {}
type Response = {}
Endpoints:
GET /sso/users
- Get SSO usersDELETE /sso/user
- Delete a SSO userObjects:
GET /sso/users
type Request = {
limit: number | null, // default: 50, max: 100
offset: number | null, // default: 0
search: string | null // search by name, email, or given ID
}
type Response = LoggedInUser[]
DELETE /sso/user
type Request = {
id: string,
data: boolean | null // default: false
}
type Response = {}
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". Set
{data: true}
, if you want to delete user's comments as well.
Endpoints:
GET /jobs
- Get jobsPOST /data/import/comments
- Import commentsPOST /data/export
- Export dataObjects:
GET /jobs
type Request = {
type: null | 'import_comments' | 'import_newsletter_subscribers' | 'export' | 'bulk_moderate_comments'
}
type Response = Job[]
See importing docs for more details. Importing is asynchronous. We will notify the website owner via email when it's done.
POST /data/import/comments
type Request = {
file: File,
format: 'wordpress' | 'disqus' | 'hyvor',
identifier_type: null | 'post_id' | 'relative_path' | 'absolute_url'
}
type Response = Job
See exporting docs. Exporting is asynchronous. We will email to the given email (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' | 'newsletter_subscribers_csv',
from: number | null, // unix timestamp, filter comments by date (lower bound)
to: number | null, // unix timestamp, filter comments by date (upper bound)
email: string | null // if empty, owner's email will be used
}
type Response = Job
Endpoints:
GET /webhooks
- Get webhook configurationsPOST /webhook
- Create a webhook configurationPATCH /webhook/{id}
- Update a webhook
configurationDELETE /webhook/{id}
- Delete a
webhook configurationGET /webhook/deliveries
- Get webhook deliveriesObjects:
GET /webhooks
type Request = {}
type Response = WebhookConfiguration[]
POST /webhook
type Request = {
url: string,
event: [
'comment.create' | 'comment.update' | 'comment.delete' |
'reaction.created' | 'reaction.updated' | 'reaction.deleted' |
'rating.created' | 'rating.updated' | 'rating.deleted' |
'comment.vote.created' | 'comment.vote.updated' | 'comment.vote.deleted' |
'comment.flag.created' | 'comment.flag.deleted' |
'user.created' | 'user.updated' | 'media.created' | 'media.deleted' |
'newsletter.subscriber.created' | 'newsletter.subscriber.updated' |
'newsletter.subscriber.deleted' | 'memberships.subscription.created' |
'memberships.subscription.updated' | 'memberships.subscription.deleted'
] // one or more events can be present in the array
}
type Response = WebhookConfiguration
PATCH /webhook/{id}
type Request = {
url: string | null,
event: null | [
'comment.create' | 'comment.update' | 'comment.delete' |
'reaction.created' | 'reaction.updated' | 'reaction.deleted' |
'rating.created' | 'rating.updated' | 'rating.deleted' |
'comment.vote.created' | 'comment.vote.updated' | 'comment.vote.deleted' |
'comment.flag.created' | 'comment.flag.deleted' |
'user.created' | 'user.updated' | 'media.created' | 'media.deleted' |
'newsletter.subscriber.created' | 'newsletter.subscriber.updated' |
'newsletter.subscriber.deleted' | 'memberships.subscription.created' |
'memberships.subscription.updated' | 'memberships.subscription.deleted'
] // one or more events can be present in the array
}
type Response = WebhookConfiguration
DELETE /webhook/{id}
type Request = {}
type Response = {}
GET /webhook/deliveries
type Request = {
limit: number | null, // default: 25, max: 100
offset: number | null // default: 0
}
type Response = WebhookDelivery[]
Endpoints:
GET /integrations/slack
- Get Slack integration
statusPOST /integrations/slack
- Initialize Slack
integrationPOST /integrations/slack/channel
- Set a Slack
ChannelDELETE /integrations/slack
- Disconnect
Slack integrationObjects:
GET /integrations/slack
type Request = {}
type Response = {
connection: SlackConnection
}
POST /integrations/slack
type Request = {}
type Response = {
url: string // redirect here for OAuth confirmation
}
POST /integrations/slack/channel
type Request = {
channel: string
}
type Response = {}
DELETE /integrations/slack
type Request = {}
type Response = {}
Endpoints:
GET /media
- Get mediaPOST /media/image
- Upload an imageDELETE /media/{id}
- Delete mediaObjects:
GET /media
type Request = {
limit: number | null, // default: 50, max: 100
offset: number | null // default: 0
}
type Response = Media[]
POST /media/image
type Request = {
image: File // max size: 5 MB,
// supported formats: jpg, jpeg, png, gif, svg, webp, apng, avif
}
type Response = Media
DELETE /media/{id}
type Request = {}
type Response = {}
interface Website = {
id: number,
name: string,
auth_type: 'hyvor' | 'sso',
auth_sso_type: null | 'stateless' | 'openid',
sso_stateless_private_key: string | null,
sso_stateless_login_url: string | null,
sso_stateless_is_keyless: boolean,
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', // default: 'off'
is_realtime_on: boolean,
realtime_typing: 'off' | 'on_without_typer' | 'on_with_typer',
is_realtime_online_count_on: boolean,
is_realtime_users_on: boolean, // default: true
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: 1
default_avatar: string | null,
text_comment_box: string | null, // default: 'Write your comment...'
text_no_comments: string | null, // default: 'Be the first to comment...'
text_comment_button: string | null,
text_reply_box: string | null, // default: 'Reply to this comment...'
text_reply_button: string | null,
text_reactions: string | null, // default: 'What is your reaction?'
text_ratings: string | null, // default: 'What is your rating?'
text_comment_count_0: string | null,
text_comment_count_1: string | null,
text_comment_count_multi: string | null,
top_widget: 'none' | 'reactions' | 'ratings', // default: 'reactions'
ui_box_shadow: number, // default: 1
ui_box_roundness: number, // default: 4
ui_box_border_size: number, // default: 0
ui_box_width: number | null,
ui_button_roundness: number, // default: 4
ui_box_border_color: string, // default: 'aaaaaa'
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', // default: name
comments_per_request: number, // default: 50
replies_per_request: number, // default: 25
nested_levels: number, // default: 3
display_replied_to_type: 'none' | 'deep' | 'all', // default: 'deep'
comments_char_limit: number, // default: 50000
comments_editing_enabled: boolean,
comments_editing_timeout: number, // default: 0 (no timeout) in minutes
is_emoji_enabled: boolean,
is_images_enabled: boolean,
is_embed_enabled: boolean,
is_math_enabled: boolean,
is_spam_detection_on: boolean,
spam_detection_provider: 'none' | 'akismet' | 'fortguard',
spam_detection_fortguard_content_score: number | null,
spam_detection_fortguard_languages: string[],
spam_detection_fortguard_languages_exclude: boolean,
spam_detection_fortguard_countries: string[],
spam_detection_fortguard_countries_exclude: boolean,
spam_detection_fortguard_sentiments: string[],
vote_type: 'both' | 'upvotes' | 'none', // default: 'both'
is_vote_viewing_on: boolean,
is_guest_voting_on: boolean,
sort: 'top' | 'newest' | 'oldest', // default: 'top'
language: string, // default: 'en-us'
note: string | null,
close_after_days: number,
color_text: string | null,
color_background_text: string | null, // default: '111111'
color_accent: string | null, // default: '000000'
color_accent_text: string | null, // default: 'ffffff'
color_box: string | null, // default: 'ffffff'
color_box_text: string | null, // default: '111111'
color_box_text_light: string | null, // default: '767676'
color_input: string | null,
color_dark_text: string | null,
color_dark_background_text: string | null, // default: 'ffffff'
color_dark_accent: string | null, // default: 'ffffff'
color_dark_accent_text: string | null, // default: '000000'
color_dark_box: string | null, // default: '232121'
color_dark_box_text: string | null, // default: 'ffffff'
color_dark_box_text_light: string | null, // default: 'aaaaaa'
color_dark_input: string | null,
color_theme: 'light' | 'dark' | 'os',
ratings_color: string | null, // default: 'ffcc48'
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', // default: 'both'
email_send_to_users: boolean,
notif_channel: 'email' | 'slack' | 'off', // default: 'email'
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,
email_website_url: string | null,
email_notification_sending_address: string | null,
encryption_key: string | null,
data_api_key: string | null,
is_data_api_public: boolean,
console_api_key: string | null,
webhook_url: string | null,
webhook_on_create: boolean,
webhook_on_edit: boolean,
webhook_on_delete: boolean,
webhook_secret: string | null,
mod_badge_id: number | null,
mod_alias_name: string | null,
mod_alias_picture_url: string | null,
memberships_enabled: boolean,
memberships_currency: 'usd' | 'eur' | 'gbp', // default: 'usd'
memberships_yearly_discount: number | null,
memberships_text_button: string | null,
memberships_text_modal_title: string | null,
memberships_text_modal_title_members: string | null,
memberships_text_login_to_subscribe: string | null,
memberships_text_subscribe_as: string | null,
memberships_text_manage_subscription: string | null,
memberships_text_payment_success: string | null,
memberships_payment_success_url: string | null,
memberships_gated_allow_google: boolean,
email_report_daily: boolean,
email_report_weekly: boolean,
email_report_monthly: boolean,
highlight_new: boolean,
highlight_new_color: string | null, // default: '29ab3f',
highlight_upvote_threshold_1: number | null,
highlight_upvote_threshold_1_color: string | null, // default: 'f96436',
highlight_upvote_threshold_2: number | null,
highlight_upvote_threshold_2_color: string | null, // default: '223696',
newsletter_enabled: boolean,
newsletter_title: string | null,
newsletter_description: string | null,
newsletter_button_text: string | null,
newsletter_success_message: string | null,
newsletter_custom_css: string | null,
newsletter_width: number | null,
newsletter_ask_commenters_subscribe: boolean,
newsletter_auto_subscribe_members: boolean,
newsletter_auto_subscribe_commenters: boolean,
newsletter_sending_addresses: string[],
}
interface Comment = {
id: number,
created_at: number,
user: User,
page: Page,
body: string,
body_html: 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,
is_automatic_spam: boolean,
flags_count: number,
last_flag_at: number | null,
upvotes: number,
downvotes: number,
user_vote: null, 'up' | 'down',
history: CommentHistory[]
}
interface CommentHistory = {
id: number,
created_at: number,
type: 'rule' | 'spam_detection' | 'moderation' | 'edit',
new_status: null | 'published' | 'pending' | 'spam' | 'deleted',
rule: Rule | null,
mod: Mod | null,
via: 'email' | 'slack' | null,
additional_info: string | null
}
interface Page = {
id: number,
created_at: number,
last_commented_at: number | null,
title: string,
identifier: string,
url: string,
is_closed: boolean,
is_premoderation_on: boolean,
author_email: string | null,
comments_count: number,
ratings: Rating
reactions: Reaction
}
interface Flag = {
id: number,
user: User | null,
comment_id: number,
reason: string,
has_mod_seen: boolean
}
interface Vote = {
id: number,
created_at: number | null,
comment_id: number,
user: User | null,
ip_hash: string | null,
type: 'up' | 'down'
}
interface Rating = {
id: number,
created_at: number | null,
page: Page | null,
user: User | null,
rating: number
}
interface Reaction = {
id: number,
created_at: number | null,
page: Page | null,
user: User | null,
type: 'superb' | 'love' | 'wow' | 'sad' | 'laugh' | 'angry'
}
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,
name: string,
username: string | null,
email: string | null,
picture_url: string | null,
bio: string | null,
location: string | null,
website_url: string | null,
created_at: number | null,
last_commented_at: number | null,
comments_count: number,
state: 'default' | 'banned' | 'shadowed' | 'trusted',
state_ends_at: number | null,
note: string | null,
ban_reason: string | null,
badge_ids: number[],
register_ip: string | null,
last_ip: string | null,
days_visited: number,
last_seen_at: number | null,
emails_sent: number,
last_emailed_at: number | null,
membership_plan_id: number | null,
membership_stripe_customer_id: string | null,
membership_started_at: number | null,
membership_ends_at: number | null,
role: null | 'owner' | 'mod' | 'admin'
}
interface GuestUser = {
type: null,
name: string | 'Anonymous',
email: string | null,
picture_url: 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.
interface UserMini = {
id: number,
type: null | 'hyvor' | 'sso',
htid: string,
name: string,
username: string | null,
picture_url: string | null
}
interface Mod = {
id: number,
created_at: number,
role: 'owner' | 'mod' | 'admin
user: UserMini,
sso_user: UserMini | null,
is_alias_used: boolean
}
interface ModInvite = {
id: number,
created_at: number,
role: 'owner' | 'mod' | 'admin
user: UserMini,
expires_at: number
}
interface StripeConnect = {
id: number,
stripe_account_id: string,
is_active: boolean
}
interface Plan = {
id: number,
created_at: number,
name: string,
description: string | null,
features: string | null,
monthly_price: number,
badge_id: number | null,
stripe_product_id: string | null,
stripe_monthly_price_id: string | null,
stripe_yearly_price_id: string | null,
members_count: number
}
interface GatedContent = {
id: number,
created_at: number,
key: string,
content: string,
gate: string | null,
minimum_plan_id: number | null
}
interface Segment = {
id: number,
created_at: number,
name: string,
description: string | null,
subscribers: number
}
interface Issue = {
id: number,
uuid: string,
created_at: number,
subject: string,
from_name: string,
from_email: string,
reply_to_email: string,
content: string,
status: 'draft' | 'scheduled' | 'sending' | 'failed' | 'sent',
segments: number[],
scheduled_at: number | null,
sending_at: number | null,
sent_at: number | null
}
interface NewsletterSubscriber = {
id: number,
created_at: number,
email: string,
segments: number[],
status: 'subscribed' | 'unsubscribed' | 'pending',
subscribed_at: number | null,
unsubscribed_at: number | null,
source: 'console' | 'form' | 'import' | 'auto_subscribe',
source_id: string | null
}
interface Send = {
id: number,
created_at: number,
subscriber: NewsletterSubscriber | null,
email: string,
status: 'sending' | 'sent' | 'failed' | 'delivered',
sent_at: number | null,
failed_at: number | null,
delivered_at: number | null,
first_opened_at: number | null,
last_opened_at: number | null,
first_clicked_at: number | null,
last_clicked_at: number | null,
unsubscribed_at: number | null,
bounced_at: number | null,
hard_bounce: boolean,
complained_at: number | null,
open_count: number,
click_count: number
}
interface EmailDomain = {
id: number,
domain: string,
dkim_public_key: string,
dkim_txt_name: string,
dkim_txt_value: string,
verified: boolean,
verified_in_ses: boolean,
requested_by_current_website: boolean
}
interface Rule = {
id: number,
type: 'word_matches' | 'link_domain_matches' | 'user_name_matches' | 'link_count_exceeds' | 'flags_exceeds' | 'downvotes_exceeds' | 'upvotes_exceeds' | 'user_reputation_exceeds',
value: string,
to_status: 'published' | 'pending' | 'spam' | 'deleted',
priority: number
}
interface EmailLog = {
id: number,
created_at: number,
comment_id: number,
type: 'reply' | 'mention' | 'mod' | 'author',
comment_url: string,
user: LoggedInUser | null,
guest_email: string | null
}
interface IP = {
ip: string,
note: string | null,
state: 'default' | 'banned' | 'shadowed' | 'trusted',
state_ends_at: number | null
}
interface Domain = {
id: number,
domain: string
}
interface Badge = {
id: number,
text: string,
background_color: string,
color: string
}
interface Job = {
id: number,
created_at: number,
started_at: number | null,
completed_at: number | null,
failed_at: number | null,
data: { string : any },
result: { string : any } | null,
type: 'import_comments' | 'import_newsletter_subscribers' | 'export' | 'bulk_moderate_comments',
status: 'pending' | 'running' | 'completed' | 'failed',
error: string | null
}
interface WebhookConfiguration = {
id: number,
website_id: number,
url: string,
events: [
'comment.create' | 'comment.update' | 'comment.delete' |
'reaction.created' | 'reaction.updated' | 'reaction.deleted' |
'rating.created' | 'rating.updated' | 'rating.deleted' |
'comment.vote.created' | 'comment.vote.updated' | 'comment.vote.deleted' |
'comment.flag.created' | 'comment.flag.deleted' |
'user.created' | 'user.updated' | 'media.created' | 'media.deleted' |
'newsletter.subscriber.created' | 'newsletter.subscriber.updated' |
'newsletter.subscriber.deleted' | 'memberships.subscription.created' |
'memberships.subscription.updated' | 'memberships.subscription.deleted'
], // one or more events can be present in the array
secret: string
}
interface WebhookDelivery = {
id: number,
created_at: number | null,
website_id: number,
webhook_configuration_id: number,
url: string,
event: 'comment.create' | 'comment.update' | 'comment.delete' |
'reaction.created' | 'reaction.updated' | 'reaction.deleted' |
'rating.created' | 'rating.updated' | 'rating.deleted' |
'comment.vote.created' | 'comment.vote.updated' | 'comment.vote.deleted' |
'comment.flag.created' | 'comment.flag.deleted' |
'user.created' | 'user.updated' | 'media.created' | 'media.deleted' |
'newsletter.subscriber.created' | 'newsletter.subscriber.updated' |
'newsletter.subscriber.deleted' | 'memberships.subscription.created' |
'memberships.subscription.updated' | 'memberships.subscription.deleted',
status: 'pending' | 'completed' | 'retrying' | 'failed',
request_body: string | null,
num_attempts: number | null,
last_attempt_at: number | null,
response_body: string | null,
response_code: number | null
}
interface SlackConnection = {
has_token: boolean,
channel_name: string | null
}
interface Media = {
id: number,
created_at: number,
comment_id: number | null,
name: string,
size: number,
url: string
}