Console API
The Console API allows you to automate your newsletter-related tasks over HTTP with API key authentication. This is the same API that we internally use at the Console.
Getting Started
- Create a Console API key at Console β Settings β API Keys. Each key must be granted one or more scopes, which limit what it can access.
- The base URL:
https://post.hyvor.com/api/console - For each request, set
Authorizationheader toBearer <API_KEY>. - Available HTTP methods:
GET- Retrieve a resourcePOST- Create a resource or perform an actionPUT- Update a resourceDELETE- Remove a resource
- Request params can be set as
JSON(recommended) or asapplication/x-www-form-urlencoded. - All endpoints return JSON data. The response will be an object or an array of objects.
In this documentation, all objects, request params, and responses are written as Typescript interfaces in order to make type declarations concise.
Categories
The Console API endpoints are categorized based on the resource they interact with.
Jump to each category:
- Newsletter
- Issue
- Lists
- Subscriber
- Subscriber Metadata
- Sending Profile
- Template
- User
- API Keys
- Media
- Imports
- Export
Newsletter
Endpoints:
GET /newsletter- Get newsletter dataPATCH /newsletter- Update a newsletterDELETE /newsletter- Delete a newsletter
Objects:
Get newsletter data
GET /newsletter
type Request = {}
type Response = NewsletterUpdate a newsletter
PATCH /newsletter
type Request = Partial<Newsletter> // except id, created_at
type Response = NewsletterDelete a newsletter
DELETE /newsletter
type Request = {}
type Response = {}Issue
Endpoints:
GET /issues- Get issuesPOST /issues- Create an issueGET /issues/{id}- Get an issuePATCH /issues/{id}- Update an issueDELETE /issues/{id}- Delete an issuePOST /issues/{id}/send- Send an issueGET /issues/{id}/preview- Preview an issueGET /issues/{id}/progress- Get issue sending progressGET /issues/{id}/sends- Get issue sendsGET /issues/{id}/report- Get issue report
Objects:
Get issues
GET /issues
type Request = {
limit?: number; // default: 50
offset?: number; // default: 0
}
type Response = Issue[]Create an issue
POST /issues
type Request = {}
type Response = IssueGet an issue
GET /issues/{id}
type Request = {}
type Response = IssueUpdate an issue
PATCH /issues/{id}
type Request = {
subject?: string;
lists?: number[];
content?: string;
sending_profile_id?: number;
}
type Response = IssueDelete an issue
DELETE /issues/{id}
type Request = {}
type Response = {}Send an issue
POST /issues/{id}/send
type Request = {}
type Response = IssuePreview an issue
Renders the HTML preview of an issue, and returns the number of subscribers it would be sendable to.
GET /issues/{id}/preview
type Request = {}
type Response = {
html: string;
sendable_subscribers_count: number;
}Get issue sending progress
Get the sending progress of an issue that is currently being sent.
GET /issues/{id}/progress
type Request = {}
type Response = {
total: number;
sent: number;
progress: number; // percentage, 0-100
} | null // null if the issue has no sends yetGet issue sends
GET /issues/{id}/sends
type Request = {
limit?: number; // default: 50
offset?: number; // default: 0
search?: string;
type?: string;
}
type Response = Send[]Get issue report
Get the delivery, open, click, bounce, and complaint counts of an issue.
GET /issues/{id}/report
type Request = {}
type Response = {
counts: {
total: number;
pending: number;
sent: number;
failed: number;
unsubscribed: number;
bounced: number;
complained: number;
}
}Lists
Endpoints:
POST /lists- Create a listPATCH /lists/{id}- Update a listDELETE /lists/{id}- Delete a list
Objects:
Create a list
POST /lists
type Request = {
name: string; // max length: 255
description?: string;
}
type Response = ListUpdate a list
PATCH /lists/{id}
type Request = {
name?: string; // max length: 255
description?: string;
}
type Response = ListDelete a list
DELETE /lists/{id}
type Request = {}
type Response = {}Subscriber
Endpoints:
GET /subscribers- Get subscribersGET /subscribers/email/{email}- Get a subscriber by emailPOST /subscribers- Create or update a subscriberPOST /subscribers/{id}/resend-opt-in- Resend opt-in confirmation emailDELETE /subscribers/{id}- Delete a subscriberPOST /subscribers/bulk- Bulk update subscribers
Objects:
Get subscribers
GET /subscribers
type Request = {
limit?: number; // default: 50
offset?: number; // default: 0
// filter by status
status?: 'subscribed' | 'unsubscribed' | 'pending';
// filter by list
list_id?: number;
// search by email
search?: string;
}
type Response = Subscriber[]Get a subscriber by email
GET /subscribers/email/{email}
type Request = {}
type Response = Subscriber // 404 if not foundCreate or update a subscriber
POST /subscribers
type Request = {
// If a subscriber with the given email already exists, it will be updated.
// Otherwise, a new subscriber will be created.
email: string;
// Subscribe to or unsubscribe from lists based
// on the given \`lists_strategy\`.
// an array of list IDs or names.
lists?: (number | string)[];
// The subscriber's subscription status
// default: subscribed
status?: 'subscribed' | 'pending';
// the source of the subscriber
// default: console
source?: 'console' | 'form' | 'import';
// subscriber's IP address
subscribe_ip?: string | null;
// unix timestamp of when the subscriber opted in
// if not set, it will be set to the current time if status is 'subscribed'
subscribed_at?: number | null; // unix timestamp
// additional metadata for the subscriber
// keys must be defined in the Subscriber Metadata Definitions section (or using the API)
metadata?: Record<string, string>;
// ============ SETTINGS ===========
// change how the endpoint behaves
// how \`lists\` field is processed when updating an existing subscriber's list subscriptions.
// merge: merges the lists (default)
// overwrite: overwrites the lists
// remove: removes from the current lists
lists_strategy?: 'merge' | 'overwrite' | 'remove';
// if the subscriber was previously removed from a list,
// define the reason(s) for skipping the re-subscription to that list.
// see below for more info
// default: ['unsubscribe', 'bounce', 'complaint']
list_skip_resubscribe_on?: ('unsubscribe' | 'bounce' | 'complaint' | 'other')[];
// define the reason for removing the subscriber from a list
// (only when updating, see below for more info)
// default: 'unsubscribe'
list_removal_reason?: 'unsubscribe' | 'bounce' | 'complaint' | 'other';
// whether to overwrite or merge the subscriber's metadata
// when updating an existing subscriber.
// default: 'merge'
metadata_strategy?: 'merge' | 'overwrite';
// whether to send a confirmation email when adding a subscriber with 'pending' status
// or when changing an existing subscriber's status to 'pending'.
// default: false
send_pending_confirmation_email?: boolean;
}
type Response = SubscriberManaging list unsubscriptions and re-subscriptions
For all subscribers, Hyvor Post records the lists they have previously unsubscribed from. This makes it easier to build automations around list subscriptions while respecting subscribersβ preferences.
list_skip_resubscribe_on: when adding an existing subscriber to a list they were previously removed from, this setting controls which of the removal reasons below should block the re-add. By default, previous unsubscribes, bounces, and complaints all block a re-add; pass an empty array to always re-add regardless of why they left.
list_removal_reason:
unsubscribe- use this reason if the subscriber is explicitly asking to be removed from the list (e.g. they unchecked a checkbox to unsubscribe). This will record an unsubscription, blocking future re-adds unless the re-add requestβslist_skip_resubscribe_onexcludesunsubscribe. Hyvor Postβs default unsubscribe form uses this.bounce- recorded automatically when a send to the subscriber hard-bounces.complaint- recorded automatically when the subscriber marks a send as spam.other- use this reason if you want to remove the subscriber from the list without recording it as one of the reasons above (does not block future re-adds by default).
Examples
{
"email": "example@example.com",
"lists": ["Default"]
}{
"email": "example@example.com",
"lists": [123],
"lists_strategy": "add"
}{
"email": "example@example.com",
"lists": ["Paid Users"],
"lists_strategy": "remove",
// unsubscribe, bounce, or other
"list_removal_reason": "unsubscribe"
}{
"email": "example@example.com",
"lists": ["Default"],
"status": "pending",
"send_pending_confirmation_email": true
}{
"email": "example@example.com",
"lists": ["Default"],
"lists_strategy": "add",
// ignore unsubscription if the subscriber was removed from the list due to a bounce
// but allow re-adding if they previously unsubscribed themselves
"list_skip_resubscribe_on": ["bounce"]
}To force re-adding both previous unsubscribes and bounces, use an empty array for list_skip_resubscribe_on.
Resend opt-in confirmation email
Resends the opt-in confirmation email to a pending subscriber.
POST /subscribers/{id}/resend-opt-in
type Request = {}
type Response = {}Delete a subscriber
DELETE /subscribers/{id}
type Request = {}
type Response = {}Bulk update subscribers
POST /subscribers/bulk
type Request = {
subscribers_ids: number[];
action: 'delete' | 'status_change' | 'metadata_update';
status?: 'subscribed' | 'unsubscribed' | 'pending'; // required if action is status_change
metadata?: Record<string, string>; // required if action is metadata_update
}
type Response = {
status: string;
message: string;
subscribers: Subscriber[];
}Subscriber Metadata
Subscriber metadata definitions allow you to define custom fields for subscribers. These fields can be used to store additional information about subscribers.
Endpoints:
POST /subscriber-metadata-definitions- Create a subscriber metadata definitionPATCH /subscriber-metadata-definitions/{id}- Update a subscriber metadata definitionDELETE /subscriber-metadata-definitions/{id}- Delete a subscriber metadata definition
Objects:
Create a subscriber metadata definition
POST /subscriber-metadata-definitions
type Request = {
key: string; // max length: 255
name: string; // max length: 255
}
type Response = SubscriberMetadataDefinitionkeycan only contain lowercase letters, numbers, and underscores.- Once created, the
keycannot be changed.
Update a subscriber metadata definition
PATCH /subscriber-metadata-definitions/{id}
type Request = {
name: string; // max length: 255
}
type Response = SubscriberMetadataDefinitionDelete a subscriber metadata definition
DELETE /subscriber-metadata-definitions/{id}
type Request = {}
type Response = {}Sending Profile
Endpoints:
GET /sending-profiles- Get sending profilesPOST /sending-profiles- Create a sending profilePATCH /sending-profiles/{id}- Update a sending profileDELETE /sending-profiles/{id}- Delete a sending profile
Objects:
Get sending profiles
GET /sending-profiles
type Request = {}
type Response = SendingProfile[]Create a sending profile
POST /sending-profiles
type Request = {
from_email: string;
from_name?: string | null;
reply_to_email?: string | null;
brand_name?: string | null;
brand_logo?: string | null; // a publicly accessible URL of the logo
brand_url?: string | null;
}
type Response = SendingProfileUpdate a sending profile
PATCH /sending-profiles/{id}
type Request = {
from_email?: string;
from_name?: string | null;
reply_to_email?: string | null;
brand_name?: string | null;
brand_logo?: string | null; // a publicly accessible URL of the logo
brand_url?: string | null;
is_default?: boolean;
}
type Response = SendingProfileDelete a sending profile
The system sending profile cannot be deleted.
DELETE /sending-profiles/{id}
type Request = {}
type Response = SendingProfile[] // the newsletter's remaining sending profilesTemplate
Hyvor Post provides a flexible newsletter template system that allows you to customize the appearance of your newsletters.
Endpoints:
GET /templates- Get newsletter templatePATCH /templates- Update newsletter templatePOST /templates/render- Render newsletter template with content
Objects:
Get newsletter template
GET /templates
type Request = {}
type Response = TemplateUpdate newsletter template
PATCH /templates
type Request = {
template?: string;
}
type Response = TemplateRender newsletter template with content
POST /templates/render
type Request = {
template?: string | null;
}
type Response = {
html: string;
}User
Admins of the organization that owns the newsletter can be added as users to collaborate on managing it.
Endpoints:
GET /users- Get userPOST /users- Create userDELETE /users- Delete user
Objects:
Get user
GET /users
type Request = {}
type Response = User[]Create user
POST /users
The user must already be a member of the organization that owns this newsletter.
type Request = {
user_id: number; // the user's id in HYVOR (AuthInterface)
// what to do if the user is already added to the newsletter
// throw: return a 400 error (default)
// ignore: return the existing user without an error
on_duplicate?: 'throw' | 'ignore';
}
type Response = User- Returns a 400 error if the user is not a member of the organization that owns this newsletter.
Delete user
DELETE /users
type Request = {
// one of user_id or id is required
user_id?: number; // the user's id in HYVOR (AuthInterface)
id?: number; // the user's id in this newsletter's user list
}
type Response = {}API Keys
Every API key is granted one or more scopes, which limit what resources and actions it can access. Available scopes:
newsletter.read/newsletter.write/newsletter.deleteissues.read/issues.writesending_profiles.read/sending_profiles.writesubscribers.read/subscribers.writeusers.read/users.writetemplates.read/templates.writeapi_keys.read/api_keys.writemedia.writedata.read/data.write- subscriber lists, imports, and exports
Endpoints:
GET /api-keys- Get API keysPOST /api-keys- Create an API keyPATCH /api-keys/{id}- Update an API keyPOST /api-keys/{id}- Regenerate an API keyDELETE /api-keys/{id}- Delete an API key
Objects:
Get API keys
The raw key is not returned; only its metadata is.
GET /api-keys
type Request = {}
type Response = ApiKey[]Create an API key
POST /api-keys
type Request = {
name: string; // max length: 255
scopes: string[]; // see the list of scopes above
}
type Response = ApiKeykey property (the raw key) is only returned once, on creation. Store it securely -
it cannot be retrieved again. Update an API key
PATCH /api-keys/{id}
type Request = {
name?: string; // max length: 255
is_enabled?: boolean;
scopes?: string[];
}
type Response = ApiKeyRegenerate an API key
Regenerates the raw key of an API key. The previous key is invalidated immediately, and the new raw key is returned once.
POST /api-keys/{id}
type Request = {}
type Response = ApiKeyDelete an API key
Requests made with the deleted key will be rejected immediately.
DELETE /api-keys/{id}
type Request = {}
type Response = {}Media
Endpoints:
POST /media- Upload media
Objects:
Upload media
POST /media
type Request = {
// max size: 10MB
// supported formats: jpg, jpeg, png, gif, webp
file: File;
folder: 'issue_images' | 'newsletter_images';
}
type Response = MediaExport
Endpoints:
GET /export- Get subscriber exportsPOST /export- Create a subscriber export
Objects:
Get subscriber exports
GET /export
type Request = {}
type Response = SubscriberExport[]Create a subscriber export
POST /export
type Request = {}
type Response = SubscriberExportObjects
Newsletter Object
interface Newsletter {
id: string;
subdomain: string;
created_at: number; // unix timestamp
name: string;
language_code: string | null;
is_rtl: boolean;
metadata: Record<string, string>;
address: string | null;
unsubscribe_text: string | null;
branding: boolean;
template_color_accent: string | null;
template_color_accent_text: string | null;
template_color_background: string | null;
template_color_background_text: string | null;
template_color_box: string | null;
template_color_box_text: string | null;
template_box_shadow: string | null;
template_box_radius: string | null;
template_box_border: string | null;
template_font_family: string | null;
template_font_size: string | null;
template_font_weight: string | null;
template_font_weight_heading: string | null;
template_font_line_height: string | null;
form_title: string | null;
form_description: string | null;
form_footer_text: string | null;
form_button_text: string | null;
form_success_message: string | null;
form_width: number | null; // null = 100%
form_custom_css: string | null;
form_color_light_text: string | null; // null = inherit
form_color_light_text_light: string | null;
form_color_light_accent: string | null;
form_color_light_accent_text: string | null;
form_color_light_input: string | null;
form_color_light_input_text: string | null;
form_light_input_box_shadow: string | null;
form_light_input_border: string | null;
form_light_border_radius: number | null;
form_color_dark_text: string | null; // null = inherit
form_color_dark_text_light: string | null;
form_color_dark_accent: string | null;
form_color_dark_accent_text: string | null;
form_color_dark_input: string | null;
form_color_dark_input_text: string | null;
form_dark_input_box_shadow: string | null;
form_dark_input_border: string | null;
form_dark_border_radius: number | null;
form_default_color_palette: 'light' | 'dark' | 'os';
form_input_border_radius: number;
}Issue Object
interface Issue {
id: number;
uuid: string;
created_at: number; // unix timestamp
subject: string | null;
content: string | null;
sending_profile_id: number;
status: 'draft' | 'scheduled' | 'sending' | 'sent';
lists: number[];
scheduled_at: number | null; // unix timestamp
sending_at: number | null; // unix timestamp
sent_at: number | null; // unix timestamp
total_sends: number;
from_email: string | null;
from_name: string | null;
reply_to_email: string | null;
sendable_subscribers_count: number;
}Send Object
interface Send {
id: number;
created_at: number; // unix timestamp
subscriber: Subscriber | null;
email: string;
status: 'pending' | 'sent' | 'failed';
sent_at: number | null; // unix timestamp
failed_at: number | null; // unix timestamp
delivered_at: number | null; // unix timestamp
unsubscribed_at: number | null; // unix timestamp
bounced_at: number | null; // unix timestamp
hard_bounce: boolean;
complained_at: number | null; // unix timestamp
}List Object
interface List {
id: number;
created_at: number; // unix timestamp
name: string;
description: string | null;
subscribers_count: number;
}Subscriber Object
interface Subscriber {
id: number;
email: string;
source: 'console' | 'form' | 'import';
status: 'subscribed' | 'pending';
list_ids: number[];
lists: string[]; // list names
subscribe_ip: string | null;
subscribed_at: number | null; // unix timestamp
metadata: Record<string, string>;
}Subscriber Metadata Definition Object
interface SubscriberMetadataDefinition {
id: number;
created_at: number; // unix timestamp
key: string;
name: string;
type: 'text'; // only 'text' is currently supported
}Sending Profile Object
interface SendingProfile {
id: number;
created_at: number; // unix timestamp
from_email: string;
from_name: string | null;
reply_to_email: string | null;
brand_name: string | null;
brand_logo: string | null;
brand_url: string | null;
is_default: boolean;
is_system: boolean;
}Template Object
interface Template {
template: string;
}User Mini Object
interface UserMiniObject {
name: string;
email: string;
username: string | null;
picture_url: string | null;
}User Object
interface User {
id: number;
role: 'owner' | 'admin';
created_at: number; // unix timestamp
user: UserMiniObject;
}Media Object
interface Media {
id: number;
created_at: number; // unix timestamp
folder: 'issue_images' | 'newsletter_images' | 'import' | 'export';
url: string;
size: number; // in bytes
extension: string;
}Subscriber Export Object
interface SubscriberExport {
id: number;
created_at: number; // unix timestamp
status: 'pending' | 'completed' | 'failed';
error_message: string | null;
url: string | null;
}