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

  • Create a Console API key at Console → Settings → API.
  • In each request, set the X-API-KEY header to the API key you created.
  • The Base URL is https://talk.hyvor.com/api/console/v1/{website_id}
  • All endpoints require website_id in the URL. You can find your website ID in the Console.
  • All endpoints return JSON data. The response will be an object or an array of objects.
  • HTTP methods are as follows:
    • GET - Read a resource or a list or resource
    • POST - Create a resource, or perform an action
    • PATCH - Update a resource
    • DELETE - Delete a resource
  • Request params can be set as JSON (recommended) or as application/x-www-form-urlencoded.

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:

  • 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.

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 // except 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,
    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.

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,
    spam: number,
    deleted: 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 = {}

Get a comment

GET /comment/{id}
type Request = {}
type Response = Comment

Update a 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 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

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 voters of a 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 a vote on a comment

DELETE /comment/{id}/vote
type Request = {
    user_htid: string // ID of the user who voted, required. Ex: hyvor_100 | sso_100
}
type Response = {}

Get flags of a comment

GET /comment/{id}/flags
type Request = {
    limit: number | null, // default: 25, max: 100
    offset: number | null // default: 0
};
type Response = Flag[]

Delete a flag on a comment

DELETE /comment/{id}/flag
type Request = {
    flag_id: number // ID of the flag to delete
}
type Response = {}

Moderate multiple comments

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 = {}

Reactions

Endpoints:

Objects:

Get reactions

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 a reaction

DELETE /reaction/{id}
type Request = {}
type Response = {}

Ratings

Endpoints:

Objects:

Get ratings

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 a rating

DELETE /rating/{id}
type Request = {}
type Response = {}

Pages

Endpoints:

{id} in the URL

By 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

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[];

Update a Page

PATCH /page/{id}
type Request = {
    is_closed: boolean | null,
    is_premoderation_on: boolean | null
}
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

type Request = {
    is_closed: boolean | null,
    is_premoderation_on: boolean | null
}
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:

{id} in the URL

By 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

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:

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

Get a user

GET /user/{id}
type Request = {}
type Response = LoggedInUser

Update a user

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 comments and flags count of a user

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 a user

DELETE /user/{id}
type Request = {
    data: boolean // set true to delete the user with all its data
}
type Response = {}

Get email notification subscription status of a user

GET /user/{id}/email-notification
type Request = {}
type Response = {
    reply: boolean,
    mention: boolean
}

Update email notification subscription status of a user

POST /user/{id}/email-notification
type Request = {
    reply: boolean | null,
    mention: boolean | null
}
type Response = {}

Analytics

Endpoints:

Get website statistics

GET /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 credit analytics

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 comment analytics

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

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

Endpoints:

Objects:

Get moderators

GET /mods
type Request = {}
type Response = Mod[]

Update a moderator

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 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 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

Resend a moderator invite

POST /mod-invite/{id}/resend
type Request = {}
type Response = {}

Delete a moderator invite

DELETE /mod-invite/{id}
type Request = {}
type Response = {}

Memberships

Endpoints:

Objects:

Get Stripe Status

GET /memberships/stripe/connect
type Request = {}
type Response = {
    is_connected: boolean,
    data: StripeConnect | null
}

Get Stripe onboarding URL

POST /memberships/stripe/connect/
type Request = {}
type Response = {
    url: string // redirect here for Stripe onboarding
}

Get membership plans

GET /membership-plans
type Request = {}
type Response = Plan[]

Create a membership 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

Update a membership 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 a membership plan

DELETE /membership-plan/{id}
type Request = {}
type Response = {}

Get gated content

GET /memberships/gated-content
type Request = {
    limit: number | null, // default: 10, max: 100
    offset: number | null // default: 0
}
type Response = GatedContent[]

Create gated content

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

Update gated content

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 gated content

DELETE /memberships/gated-content/{id}
type Request = {}
type Response = {}

Newsletters

Endpoints:

Objects:

Get Newsletter

GET /newsletter
type Request = {}
type Response = {
    segments: Segment[],
    issues: Issue[],
    subscribers_change_30d: number,
    subscribers_total: number,
    issues_sent_total: number
}

Get newsletter subscribers

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[]

Import newsletter subscribers

POST /newsletter/subscribers/import
type Request = {
    file: File,
    segments: number[] | null,
    status: null | 'subscribed' | 'unsubscribed' | 'pending'
}
type Response = Job

Create newsletter subscribers

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[]

Update a newsletter subscriber

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 a newsletter subscriber

DELETE /newsletter/subscriber/{id}
type Request = {}
type Response = {}

Get issues

GET /newsletter/issues
type Request = {
    limit: number | null, // default: 25, max: 100
    offset: number | null // default: 0
}
type Response = Issue[]

Create an issue

POST /newsletter/issue
type Request = {}
type Response = Issue

Get an issue

GET /newsletter/issue/{id}
type Request = {}
type Response = Issue

Send an issue

Only draft issues can be sent.

POST /newsletter/issue/{id}/send
type Request = {}
type Response = Issue

Send a test

POST /newsletter/issue/{id}/test
type Request = {
    email: string
}
type Response = {}

Preview an issue

GET /newsletter/issue/{id}/preview
type Request = {}
type Response = {
    html: string
}

Get issue progress

GET /newsletter/issue/{id}/progress
type Request = {}
type Response = {
    total: number,
    pending: number,
    sent: number,
    progress: number // percentage
}

Get issue report

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 issue sends

GET /newsletter/issue/{id}/sends
type Request = {
    limit: number | null, // default: 25, max: 100
    offset: number | null // default: 0
}
type Response = Send[]

Update an issue

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 an issue

DELETE /newsletter/issue/{id}
type Request = {}
type Response = {}

Create a segment

POST /newsletter/segment
type Request = {
    name: string | null,
    description: string | null
}
type Response = Segment

Update a segment

PATCH /newsletter/segment/{id}
type Request = {
    name: string | null,
    description: string | null
}
type Response = Segment

Delete a segment

DELETE /newsletter/segment/{id}
type Request = {}
type Response = {}

Add to a segment

POST /newsletter/segment/{id}/add
type Request = {}
type Response = {}

Email Domain

Endpoints:

Objects:

Create an email domain

POST /email/domain
type Request = {
    domain: string // Ex: example.com
}
type Response = EmailDomain

Verify an email domain

POST /email/domain/verify
type Request = {}
type Response = {
    data: {
        verified: string,
        debug: string | null
    },
    domain: EmailDomain
}

Delete an email domain

DELETE /email/domain
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 = {}

Email Logs

Endpoints:

Objects:

Get email logs

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[]

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 | null, // default: 25, max: 100
    offset: number | null // default: 0
};
type Response = IP[]

Get IP

GET /ip/{ip}
type Request = {}
type Response = IP

Change state of an IP

PATCH /ip/{ip}
type Request = {
    state: null | 'default' | 'banned' | 'shadowed' | 'trusted',
    note: string | null,
    state_ends_at: number | 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, // max length: 50
    color: string, // hex color code Ex: #ff0000
    background_color: string // hex color code Ex: #0000ff
}
type Response = Badge

Update a 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 a badge

DELETE /badge/{id}
type Request = {}
type Response = {}

Single Sign-on

Endpoints:

Objects:

Get SSO users

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 a SSO user

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.

Jobs

Endpoints:

Objects:

Get jobs

GET /jobs
type Request = {
    type: null | 'import_comments' | 'import_newsletter_subscribers' | 'export' | 'bulk_moderate_comments'
}
type Response = Job[]

Import comments

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

Export data

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

Webhooks

Endpoints:

Objects:

Get webhook configurations

GET /webhooks
type Request = {}
type Response = WebhookConfiguration[]

Create a webhook configuration

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

Update a webhook configuration

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 a webhook configuration

DELETE /webhook/{id}
type Request = {}
type Response = {}

Get webhook deliveries

GET /webhook/deliveries
type Request = {
    limit: number | null, // default: 25, max: 100
    offset: number | null // default: 0
}
type Response = WebhookDelivery[]

Integrations

Endpoints:

Objects:

Get Slack integration status

GET /integrations/slack
type Request = {}
type Response = {
    connection: SlackConnection
}

Initialize Slack integration

POST /integrations/slack
type Request = {}
type Response = {
    url: string // redirect here for OAuth confirmation
}

Set a Slack Channel

POST /integrations/slack/channel
type Request = {
    channel: string
}
type Response = {}

Disconnect Slack integration

DELETE /integrations/slack
type Request = {}
type Response = {}

Media

Endpoints:

Objects:

Get media

GET /media
type Request = {
    limit: number | null, // default: 50, max: 100
    offset: number | null // default: 0
}
type Response = Media[]

Upload an image

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

DELETE /media/{id}
type Request = {}
type Response = {}

Objects

Website Object

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[],
}

Comment Object

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[]
}

Comment History Object

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
}

Page Object

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
}

Flag Object

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

Vote Object

interface Vote = {
    id: number,
    created_at: number | null,
    comment_id: number,
    user: User | null,
    ip_hash: string | null,
    type: 'up' | 'down'
}

Rating Object

interface Rating = {
    id: number,
    created_at: number | null,
    page: Page | null,
    user: User | null,
    rating: number
}

Reaction Object

interface Reaction = {
    id: number,
    created_at: number | null,
    page: Page | null,
    user: User | null,
    type: 'superb' | 'love' | 'wow' | 'sad' | 'laugh' | 'angry'
}

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,
    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.

UserMini Object

interface UserMini = {
    id: number,
    type:  null | 'hyvor' | 'sso',
    htid: string,
    name: string,
    username: string | null,
    picture_url: string | null
}

Mod Object

interface Mod = {
    id: number,
    created_at: number,
    role: 'owner' | 'mod' | 'admin
    user: UserMini,
    sso_user: UserMini | null,
    is_alias_used: boolean
}

Mod Invite Object

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

Stripe Connect Object

interface StripeConnect = {
    id: number,
    stripe_account_id: string,
    is_active: boolean
}

Plan Object

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
}

Gated Content Object

interface GatedContent = {
    id: number,
    created_at: number,
    key: string,
    content: string,
    gate: string | null,
    minimum_plan_id: number | null
}

Segment Object

interface Segment = {
    id: number,
    created_at: number,
    name: string,
    description: string | null,
    subscribers: number
}

Issue Object

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
}

Newsletter Subscriber Object

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
}

Send Object

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
}

Email Domain Object

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
}

Rule Object

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
}

Email Log Object

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
}

IP Object

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

Domain Object

interface Domain = {
    id: number,
    domain: string
}

Badge Object

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

Job Object

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
}

Webhook Configuration Object

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
}

Webhook Delivery Object

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
}

Slack Connection Object

interface SlackConnection = {
    has_token: boolean,
    channel_name: string | null
}

Media Object

interface Media = {
    id: number,
    created_at: number,
    comment_id: number | null,
    name: string,
    size: number,
    url: string
}