Settings & quiet hours

These endpoints read and change Herald's own settings: the general and voice settings as one validated table, and the quiet-hours schedule. Use them to configure Herald from a script, or to build a tool that shows its state. The examples use the $HERALD and $TOKEN variables from Connect.

How settings are organised#

Herald has three kinds of settings, and each has its own endpoints.

KindWhat it coversEndpoints
General settingsThe port, login item, sound mute, stacking, History size, tooltips and voice./v1/settings on this page.
Quiet hoursWhen speech, sounds and banners are held back./v1/settings/quiet-hours on this page.
Per-app settingsOne app's sound, corner, display, mute and voice./v1/apps/settings in the Apps API.

The general settings are a flat list of keys. Each key has a type and a range. The reply to a read includes a schema that describes every key, and options that list the allowed choices on this Mac, such as the installed sounds, the connected displays and the available voices. A program can build a settings screen from those two parts without knowing the keys in advance.

Settings > General. Each control here maps to a key of the settings table.
The General tab of Herald's Settings with the Local API port, Launch at login, Mute all sounds, Tooltips and Keep per app

Settings > General. Each control here maps to a key of the settings table.

Endpoints#

EndpointPurpose
GET /v1/settingsRead every general and voice setting.
PUT /v1/settingsChange one or more of them.
GET /v1/settings/quiet-hoursRead the quiet-hours schedule and what is silenced now.
PUT /v1/settings/quiet-hoursChange the schedule, start a silence, or end one.

The global stacking level also has its own short route, /v1/settings/stacking, documented with the Stacks API.

General and voice settings#

Setting keys#

These are the keys of the settings table. A read returns their current values and a write changes them.

KeyTypeDescription
portintegerThe local API port, from 1024 to 65535. Changing it restarts the server on the new port.
launchAtLoginbooleantrue opens Herald when you log in.
muteAllSoundsbooleantrue mutes every notification sound. It is the Mute Sounds item of the bell menu.
stackingstringThe global stacking level: byApp, byIssuer, bySender or never.
historyCapPerAppintegerHow many notifications History keeps per app, from 1 to 100000. Older ones are removed.
tooltipLevelstringHow much a tooltip says: nameOnly or nameAndDescription.
voiceEnginestringThe speech engine: kokoro, system or off.
voiceDefaultstringThe default voice id, such as af_heart.
voiceSpeednumberThe default speech speed, from 0.5 to 2.0.
voiceLangstringThe default language code, such as en-us.
voiceSystemstring or nullThe macOS voice the system engine uses. null picks the system default.

What the voice keys do is explained in the voice reference.

GET /v1/settings#

Returns the current value of every key, the schema that describes the keys, and the choices available on this Mac.

Request

No parameters.

Example requestshell
curl -s "$HERALD/v1/settings" -H "Authorization: Bearer $TOKEN"

Example response

The schema and options lists are shortened here to one or two entries each.

JSON
{
  "settings": {
    "port": 48617,
    "launchAtLogin": true,
    "muteAllSounds": false,
    "stacking": "bySender",
    "historyCapPerApp": 1000,
    "tooltipLevel": "nameAndDescription",
    "voiceEngine": "kokoro",
    "voiceDefault": "af_heart",
    "voiceSpeed": 1,
    "voiceLang": "en-us",
    "voiceSystem": null
  },
  "schema": [
    {
      "key": "port",
      "group": "General",
      "type": "integer",
      "min": 1024,
      "max": 65535,
      "restartsServer": true,
      "description": "The local API port (General > Local API). Changing it restarts the server; the new port is written to the port file."
    },
    {
      "key": "stacking",
      "group": "General",
      "type": "choice",
      "values": ["byApp", "byIssuer", "bySender", "never"],
      "description": "How banners stack by default: byApp, byIssuer, bySender or never. An app can override it."
    }
  ],
  "options": {
    "sounds": ["none", "Basso", "Blow", "Glass"],
    "displays": [{"id": "main", "name": "Main display"}, {"id": "3", "name": "Studio Display"}],
    "corners": ["topRight", "topLeft", "bottomRight", "bottomLeft"],
    "stackingLevels": ["byApp", "byIssuer", "bySender", "never"],
    "voiceEngines": ["kokoro", "system", "off"],
    "voices": [{"id": "af_heart", "name": "af_heart"}, {"id": "bm_george", "name": "bm_george"}],
    "historyCapChoices": [100, 250, 500, 1000, 2500, 5000]
  }
}

Response fields

FieldTypeDescription
settingsobjectThe current value of each setting key.
schemaarrayOne entry per key. See the next table.
optionsobjectThe choices available on this Mac. See the table after that.

Each schema entry:

FieldTypeDescription
keystringThe setting's key.
groupstringThe Settings tab or section it belongs to.
typestringboolean, integer, number, string or choice.
descriptionstringWhat the setting does, in one sentence.
min, maxnumberThe allowed range of a numeric key.
valuesarrayThe allowed values of a choice key.
maxLengthintegerThe longest allowed string.
nullablebooleantrue when the key accepts null.
restartsServerbooleantrue when changing the key restarts the local API.

The options object:

FieldTypeDescription
soundsarrayThe sound names this Mac offers, with none first.
displaysarrayThe connected displays, each with an id and a name.
cornersarrayThe four screen corners.
stackingLevelsarrayThe four stacking levels.
voiceEnginesarrayThe speech engines.
voicesarrayThe voices of the current engine, each with an id and a name.
historyCapChoicesarrayThe History sizes that Settings offers in its menu.

PUT /v1/settings#

Changes one or more settings. Send an object with only the keys you want to change.

Request

NameInTypeRequiredDescription
any setting keybodyvariesrequiredThe new value for that key. Send at least one key.
Example requestshell
curl -s -X PUT "$HERALD/v1/settings" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"muteAllSounds": true, "voiceSpeed": 1.15}'

Example response

The reply has the same three parts as GET /v1/settings, with the new values, plus applied. The schema and options parts are left out of this example.

JSON
{
  "applied": ["muteAllSounds", "voiceSpeed"],
  "settings": {
    "port": 48617,
    "launchAtLogin": true,
    "muteAllSounds": true,
    "stacking": "bySender",
    "historyCapPerApp": 1000,
    "tooltipLevel": "nameAndDescription",
    "voiceEngine": "kokoro",
    "voiceDefault": "af_heart",
    "voiceSpeed": 1.15,
    "voiceLang": "en-us",
    "voiceSystem": null
  }
}

Response fields

FieldTypeDescription
appliedarrayThe keys that were changed.
settingsobjectEvery setting after the change.
schema, optionsarray, objectAs in GET /v1/settings.
notestringPresent when port changed: a reminder that the server is restarting.

Errors

StatusWhen
400The body is empty, a key is unknown, or a value has the wrong type or is out of range.

Notes

  • All or nothing. If any key is invalid, nothing is changed. The message names the key, and for an unknown key it lists the known ones.
  • When you change port, the reply still arrives on the old port. A moment later the server restarts on the new one and the port file holds the new number. Read the file again before your next request.

Quiet hours#

Quiet hours are times when Herald holds back speech, sounds or banners. There is a weekly schedule of windows, and an ad hoc silence that you can start at any moment for a fixed time. What a window is and what "silenced" means for each kind of output is explained in the quiet-hours reference, which also documents every field of the window object.

GET /v1/settings/quiet-hours#

Returns the schedule, the ad hoc silence if one is running, and what is silenced at this moment.

Request

No parameters.

Example requestshell
curl -s "$HERALD/v1/settings/quiet-hours" -H "Authorization: Bearer $TOKEN"
Example responseJSON
{
  "windows": [
    {
      "id": "night",
      "days": [],
      "start": "22:30",
      "end": "07:30",
      "speech": true,
      "sounds": true,
      "banners": false,
      "speakSummary": false
    }
  ],
  "adHoc": {
    "until": "2026-10-02T14:00:00.000Z",
    "speech": true,
    "sounds": true,
    "banners": false
  },
  "status": {
    "active": true,
    "speech": true,
    "sounds": true,
    "banners": false,
    "until": "2026-10-02T14:00:00.000Z",
    "source": "adhoc"
  }
}

Response fields

FieldTypeDescription
windowsarrayThe weekly schedule. Each item is a window object.
adHocobjectThe running ad hoc silence: when it ends and what it silences. Absent when there is none.
status.activebooleantrue when anything is silenced right now.
status.speechbooleantrue when speech is held back right now.
status.soundsbooleantrue when sounds are held back right now.
status.bannersbooleantrue when banners are held back right now.
status.untilstringWhen the current silence ends. Present while active is true.
status.sourcestringWhat is causing the silence: window, adhoc or both. Present while active.

PUT /v1/settings/quiet-hours#

Changes quiet hours. The body can do three things, alone or together: replace the schedule, end the current silence, and start an ad hoc silence.

Request

NameInTypeRequiredDescription
windowsbodyarrayoptionalReplaces the whole schedule with these window objects.
resumebodybooleanoptionaltrue ends the current silence.
adHocbodyobjectoptionalStarts a silence now. See the next table.

The adHoc object needs until or minutes:

FieldTypeRequiredDescription
minutesnumberoptionalHow long the silence lasts. More than 0 and at most 10080 (7 days).
untilstringoptionalWhen it ends: HH:MM for the next time the clock shows it, or an ISO 8601 date.
speechbooleanoptionalHold back speech. Default true.
soundsbooleanoptionalHold back sounds. Default true.
bannersbooleanoptionalHold back banners. Default false.

Example request

Silence speech and sounds for one hour:

shell
curl -s -X PUT "$HERALD/v1/settings/quiet-hours" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"adHoc": {"minutes": 60}}'

Example response

The reply has the same shape as GET /v1/settings/quiet-hours, after the change.

JSON
{
  "windows": [],
  "adHoc": {"until": "2026-10-02T14:00:00.000Z", "speech": true, "sounds": true, "banners": false},
  "status": {
    "active": true,
    "speech": true,
    "sounds": true,
    "banners": false,
    "until": "2026-10-02T14:00:00.000Z",
    "source": "adhoc"
  }
}

Errors

StatusWhen
400A window's start or end is not HH:MM, they are equal, a day name is unknown, or an id repeats.
400There are more than 24 windows.
400adHoc.minutes is out of range, or adHoc.until is not a time, or is in the past.

Notes

  • The three parts are applied in this order: windows, then resume, then adHoc. A silence started by a request therefore wins over a resume in the same request.
  • windows replaces the schedule. To add one window, read the schedule, add to the list, and send the whole list back.
  • resume ends an ad hoc silence and the current occurrence of every active window. The next occurrence of each window still applies.

A weekday schedule, as a second example:

shell
curl -s -X PUT "$HERALD/v1/settings/quiet-hours" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"windows": [{"days": ["mon", "tue", "wed", "thu", "fri"], "start": "22:30", "end": "07:30",
                    "speech": true, "sounds": true}]}'

Edit this page on GitHub

Esc
Getting started
Guides