API reference
The operations Blowhorn defines, with their request fields and responses.
Blowhorn is used through its command-line interface and its MCP server. These are the operations behind them, listed with the fields each one takes and returns.
Not an HTTP API
Blowhorn ships a command-line interface and an MCP server today. It does not run an HTTP REST listener, so there is no server URL to send these requests to.
104 operations in 17 resources
Actions
POST
/cli/accept
Accept pending LinkedIn connection invitations other people have sent you, one at a time with a randomized pause between each. An invitation LinkedIn flags with its "Take care when connecting" warning is dismissed and skipped, never accepted, and the run carries on; accepts and skips are reported separately, by name. Ignoring or declining an invitation is out of scope - this command only accepts.
Accept pending LinkedIn connection invitations other people have sent you, one at a time with a randomized pause between each. An invitation LinkedIn flags with its "Take care when connecting" warning is dismissed and skipped, never accepted, and the run carries on; accepts and skips are reported separately, by name. Ignoring or declining an invitation is out of scope - this command only accepts.
Request body required
The flags and arguments of `blowhorn accept`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
limit |
integer | Maximum acceptances confirmed in one run. A click is not an acceptance: only an invitation confirmed gone from the received list counts, and an invitation skipped because LinkedIn warned about it never consumes the budget. 0 means no limit (the run stops at the examination ceiling). Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn accept' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn accept'. One of: linkedin, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What `blowhorn accept --json` prints on exit 0: the received invitations the run examined as profile URLs, how many it accepted, and how many it skipped. A row never carries a display name: identity is the profile URL only, so no person's name lands in a committed artifact. A flagged invitation ("Take care when connecting") is skipped, never confirmed: the warning's own confirm control is never clicked. On a dry run `sent` is 0 and `candidates` are the rows a real run would accept; the preview cannot know which invitation would raise the warning, because LinkedIn raises it only after Accept is clicked, so a dry run lists every candidate and says so rather than promising none would be skipped.
| Name | Type | Description |
|---|---|---|
accepted |
integer | Invitations confirmed accepted, the same count as `sent`. |
candidates required |
array of object | The received invitations the run examined, as profile URLs, in list order. |
candidates[].profile_url required |
string (uri) | The inviter's LinkedIn profile URL. Never a display name. |
dry_run required |
boolean | True when nothing was clicked and nothing was written. |
examined |
integer | Rows examined, including rows left unanswered. |
failed |
integer | Rows whose acceptance failed after the control was clicked. |
held required |
array of object | Rows held as clicked with the outcome not read. Acceptance records none of its own; the list is empty unless a held row was carried in. |
held[].profile_url required |
string (uri) | |
sent required |
integer | Invitations confirmed accepted. Always 0 on a dry run. |
skipped required |
integer | Rows examined and left unanswered (flagged, stuck, unactionable, unidentifiable, or unreadable). |
unconfirmed |
integer | Rows clicked whose acceptance could not be confirmed from the list. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AcceptResponse
| Name | Type | Description |
|---|---|---|
accepted |
integer | Invitations confirmed accepted, the same count as `sent`. |
candidates required |
array of object | The received invitations the run examined, as profile URLs, in list order. |
candidates[].profile_url required |
string (uri) | The inviter's LinkedIn profile URL. Never a display name. |
dry_run required |
boolean | True when nothing was clicked and nothing was written. |
examined |
integer | Rows examined, including rows left unanswered. |
failed |
integer | Rows whose acceptance failed after the control was clicked. |
held required |
array of object | Rows held as clicked with the outcome not read. Acceptance records none of its own; the list is empty unless a held row was carried in. |
held[].profile_url required |
string (uri) | |
sent required |
integer | Invitations confirmed accepted. Always 0 on a dry run. |
skipped required |
integer | Rows examined and left unanswered (flagged, stuck, unactionable, unidentifiable, or unreadable). |
unconfirmed |
integer | Rows clicked whose acceptance could not be confirmed from the list. |
POST
/cli/comment
Post LinkedIn, Reddit, or Bluesky comments from scheduled activities or ad-hoc target/message flags.
Post LinkedIn, Reddit, or Bluesky comments from scheduled activities or ad-hoc target/message flags.
Request body required
The flags and arguments of `blowhorn comment`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
message |
string | Comment text for ad-hoc mode |
pace |
string | Pace of the random delays between 'blowhorn comment' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn comment'. One of: bluesky, linkedin, reddit, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
target |
string | LinkedIn, Reddit, or Bluesky post URL for ad-hoc mode (for Slack thread replies, use 'blowhorn post --target ...' instead) |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/find
Search Bluesky and GitHub for posts, issues, and pull requests matching a query, with optional date, author, and relevancy filters. Read-only: results are printed, never posted, and no browser is opened.
Search Bluesky and GitHub for posts, issues, and pull requests matching a query, with optional date, author, and relevancy filters. Read-only: results are printed, never posted, and no browser is opened.
Request body required
The flags and arguments of `blowhorn find`, one property per parameter.
| Name | Type | Description |
|---|---|---|
author |
string | Filter by author handle |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
domain |
string | Filter by linked domain |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
lang |
string | Filter by language code |
limit |
integer | Max results (1-100, default 25) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
mentions |
string | Filter by mentioned account |
pace |
string | Pace of the random delays between 'blowhorn find' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn find'. One of: bluesky, github, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
query required |
string | Search query string (Lucene syntax supported) |
repo |
array of string | GitHub only: search only in owner/name repositories (repeatable, or comma-delimited) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
since |
string | Filter posts after date (YYYY-MM-DD) |
sort |
string | Ranking order: 'top' or 'latest' (default: latest) |
state |
string | GitHub only: open, closed, or all (default: open) Default: |
tag |
string | Filter by hashtag(s), comma-delimited |
type_ |
string | GitHub only: issue, pr, or all (default: all) Default: |
until |
string | Filter posts before date (YYYY-MM-DD) |
url |
string | Filter by linked URL |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/follow
Follow accounts on X, Bluesky, or GitHub. --limit 0 is target-only mode: the --target handle itself is followed and nothing else. With a positive --limit, --target is only the seed - the run follows up to --limit accounts drawn from that account's follower graph, and never follows the target. GitHub runs over the REST API with no browser, so its --dry-run performs the real follower walk and names exactly who would be followed.
Follow accounts on X, Bluesky, or GitHub. --limit 0 is target-only mode: the --target handle itself is followed and nothing else. With a positive --limit, --target is only the seed - the run follows up to --limit accounts drawn from that account's follower graph, and never follows the target. GitHub runs over the REST API with no browser, so its --dry-run performs the real follower walk and names exactly who would be followed.
Request body required
The flags and arguments of `blowhorn follow`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
limit |
integer | Max follows; 0 means target-only mode Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn follow' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn follow'. One of: bluesky, github, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
target |
string | Target account handle (comma-delimited when --limit 0) |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/post
Publish pending posts or amplify existing content across supported platforms. GitHub is the one platform 'blowhorn post' cannot publish to: it is amplify-only, so '--platform github' needs an issue or pull-request URL (from --amplify or a github activity's 'Amplify' column) and refuses without one. That amplification adds all five uplifting reactions -- +1, laugh, hooray, heart, rocket -- to the target from every selected profile under its own GH_TOKEN, over the REST API with no browser, and re-running it adds none of them twice. With --payload-stdin it is a dry fire instead: one order that is not saved, read on stdin, is walked through the same dry run as far as the platform's final click, and nothing is posted or written to your Blowhorn account.
Publish pending posts or amplify existing content across supported platforms. GitHub is the one platform 'blowhorn post' cannot publish to: it is amplify-only, so '--platform github' needs an issue or pull-request URL (from --amplify or a github activity's 'Amplify' column) and refuses without one. That amplification adds all five uplifting reactions -- +1, laugh, hooray, heart, rocket -- to the target from every selected profile under its own GH_TOKEN, over the REST API with no browser, and re-running it adds none of them twice. With --payload-stdin it is a dry fire instead: one order that is not saved, read on stdin, is walked through the same dry run as far as the platform's final click, and nothing is posted or written to your Blowhorn account.
Request body required
The flags and arguments of `blowhorn post`, one property per parameter.
| Name | Type | Description |
|---|---|---|
amplify |
string | URL of existing content to amplify instead of publishing something new. Overrides the activity's 'Amplify' column for this run. LinkedIn repost, X retweet/quote, Reddit upvote, Bluesky repost, GitHub reactions. On GitHub it names an issue or pull request and blowhorn adds all five uplifting reactions to it (+1, laugh, hooray, heart, rocket) from every selected profile under that profile's own GH_TOKEN; re-running adds none of them twice. --platform github REQUIRES a target -- this flag, or a github activity's 'Amplify' column -- because it amplifies only and cannot publish a new GitHub post; it refuses the run rather than doing nothing. Ignored for Slack -- use --target instead; Slack's 'Amplify' column selects the channel/DM/thread target, not an amplified source. Ignored for blog platforms (layer5/meshery). |
check_queue |
boolean | Show pending posts from the content queue without publishing anything. Honors --platform / --profile / --exclude for filtering. Read-only - does not open a browser, hit a platform API, or invoke 'claude' for blog activities. Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
json_output |
boolean | With --payload-stdin, end with the dry fire's result as one JSON object Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
message |
string | Send this text as one ad-hoc Slack message instead of posting from the content queue: no activity is read or marked done. Needs --platform slack, one profile named with --profile (not all, not a list) and --target ('#channel', '@user', a Slack channel link, or a Slack message link for a thread reply -- anything else is refused, never sent to #general); a profile from .blowhorn.yaml or BLOWHORN_PROFILE does not count as named. A link also picks the workspace's credentials. Refused with --check-queue or --amplify. --dry-run says what would be sent and sends nothing. |
pace |
string | Pace of the random delays between 'blowhorn post' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
payload_stdin |
boolean | Dry fire one order that is not saved: read one content item's values as JSON on stdin (the fields 'content upsert' takes, naming one Profile and one Platform) and walk it through the dry run as far as the platform's final click, printing each stage as it is reached. Needs --dry-run; --profile and --platform are not read. Writes nothing - no content item, schedule, ledger entry or run record. Hacker News is refused, because its dry run signs in. Default: |
platform |
string | Platform scope for 'blowhorn post'. One of: bluesky, github, hn, layer5, linkedin, meshery, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
target |
string | Ad-hoc target that overrides the activity's values for this run. Slack: '#channel', '@user', a Slack channel link, or a Slack message link (posts as a thread reply); a link also picks that workspace's credentials. Other platforms: generally leave unset and use the activity's 'Comment on' column for replies. Ignored for blog platforms (layer5/meshery). |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What a dry fire did: `post --dry-run --payload-stdin --json` read one order that is not saved (one content item's values, naming one profile and one platform) and walked it through the dry run as far as the platform's final click. `stages` lists each point the walk reached, in order, as it printed them. Nothing was posted and nothing was written to the store - no content item, schedule, ledger entry or run record. `post --json` reports nothing else: a queue run prints text. A refusal (Hacker News, a profile without the credential, a sign-in that failed) is the failure response, with the stages it reached.
| Name | Type | Description |
|---|---|---|
dry_run required |
any | |
ok required |
any | |
platform required |
string | The platform walked. Hacker News is refused, because its dry run signs in. Values: |
posted required |
any | Always false. A dry fire never posts. |
profile required |
string | The profile the order was rehearsed as, lowercased. |
rehearsal required |
any | |
stages required |
array of object | |
stages[].detail required |
string | The stage in words, as the line printed for it. |
stages[].stage required |
string | `checked` the profile may post there; `browser` its browser profile opened; `signin` the session check passed; `api` the platform posts over its API, with no browser; `stopped` the platform's own dry run returned, short of composing; `refused` the walk ended here, and `detail` says why. Values: |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
PostResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
any | |
ok required |
any | |
platform required |
string | The platform walked. Hacker News is refused, because its dry run signs in. Values: |
posted required |
any | Always false. A dry fire never posts. |
profile required |
string | The profile the order was rehearsed as, lowercased. |
rehearsal required |
any | |
stages required |
array of object | |
stages[].detail required |
string | The stage in words, as the line printed for it. |
stages[].stage required |
string | `checked` the profile may post there; `browser` its browser profile opened; `signin` the session check passed; `api` the platform posts over its API, with no browser; `stopped` the platform's own dry run returned, short of composing; `refused` the walk ended here, and `detail` says why. Values: |
POST
/cli/source
Collect people into the store from GitHub or LFX. GitHub lists one audience of one or more repositories (stargazers unless you name another) and stores one row per login. LFX reads one mentorship project's mentee list and upserts the mentees roster. One run names one platform: --repo and --audience belong to GitHub, --project and --start-page belong to LFX. A GitHub run lists the audience with the profile's stored GH_TOKEN. --dry-run lists that audience and writes nothing. Addresses are not printed. An LFX dry run names the list and writes nothing; a real LFX run reads each profile through Chrome. It does not follow, connect, or send mail.
Collect people into the store from GitHub or LFX. GitHub lists one audience of one or more repositories (stargazers unless you name another) and stores one row per login. LFX reads one mentorship project's mentee list and upserts the mentees roster. One run names one platform: --repo and --audience belong to GitHub, --project and --start-page belong to LFX. A GitHub run lists the audience with the profile's stored GH_TOKEN. --dry-run lists that audience and writes nothing. Addresses are not printed. An LFX dry run names the list and writes nothing; a real LFX run reads each profile through Chrome. It does not follow, connect, or send mail.
Request body required
The flags and arguments of `blowhorn source`, one property per parameter.
| Name | Type | Description |
|---|---|---|
audience |
string | GitHub only: which people to list (default stargazers). One of owner, contributors, forks, stargazers, watchers, subscribers, issues; all is their union and applies only when you type it |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
limit |
integer | Maximum people examined in the run (default: no limit) |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn source' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn source'. One of: github, lfx (any other platform, including 'all', is rejected) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
project |
string | LFX only, required: a project UUID or a mentees URL on mentorship.lfx.linuxfoundation.org |
repo |
array of string | GitHub only, repeatable: a repository as owner/name. At least one is required |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
start_page |
integer | LFX only: zero-based page to resume from (default 0) |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/star
Star GitHub repositories. From every selected profile, over the REST API with no browser: --platform github --repo owner/repo, repeatable. Already-starred repositories are counted apart, never re-credited. --dry-run reads each repository's star state for real and names exactly which would be starred, issuing no PUT. A live run exits 0 only when at least one repository was confirmed and every attempted repository was confirmed by at least one profile. Star cannot be scheduled: it stars only the repositories named on the command line.
Star GitHub repositories. From every selected profile, over the REST API with no browser: --platform github --repo owner/repo, repeatable. Already-starred repositories are counted apart, never re-credited. --dry-run reads each repository's star state for real and names exactly which would be starred, issuing no PUT. A live run exits 0 only when at least one repository was confirmed and every attempted repository was confirmed by at least one profile. Star cannot be scheduled: it stars only the repositories named on the command line.
Request body required
The flags and arguments of `blowhorn star`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn star' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn star'. One of: github (any other platform, including 'all', is rejected) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
repo |
array of string | GitHub only, repeatable: a repository as owner/name, or a github.com URL for one. At least one is required |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/unfollow
Unfollow one Bluesky account (--platform bluesky --target <handle>) through the Bluesky API. X unfollow does not run: it is refused before a browser opens until a follow-back badge and a confirmed landing are both read, so no X account is unfollowed or logged as unfollowed. Unfollow cannot be scheduled.
Unfollow one Bluesky account (--platform bluesky --target <handle>) through the Bluesky API. X unfollow does not run: it is refused before a browser opens until a follow-back badge and a confirmed landing are both read, so no X account is unfollowed or logged as unfollowed. Unfollow cannot be scheduled.
Request body required
The flags and arguments of `blowhorn unfollow`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
limit |
integer | Accepted and not read: a run unfollows the one --target from each profile Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn unfollow' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn unfollow'. One of: bluesky (any other platform, including 'all', is rejected) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
target |
string | Bluesky handle to unfollow |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/withdraw
Withdraw LinkedIn invitations that have been pending longer than --older-than days, freeing headroom under LinkedIn's cap on unaccepted invitations. People only, never Pages. Previews by default; clicks only with --apply.
Withdraw LinkedIn invitations that have been pending longer than --older-than days, freeing headroom under LinkedIn's cap on unaccepted invitations. People only, never Pages. Previews by default; clicks only with --apply.
Request body required
The flags and arguments of `blowhorn withdraw`, one property per parameter.
| Name | Type | Description |
|---|---|---|
apply |
boolean | Actually withdraw. Without it the run clicks nothing and only lists what it would withdraw. LinkedIn blocks resending to a withdrawn person for up to 3 weeks, so this is not reversible; --dry-run overrides this flag. Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
limit |
integer | Maximum withdrawals confirmed in one run. A click is not a withdrawal: only a card confirmed gone from the sent list counts. 0 means withdraw nothing. Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
older_than |
integer | Minimum days an invitation must have been pending to be eligible. LinkedIn renders only relative ages, and one worded in a way Blowhorn cannot read is always kept and reported under Unreadable. Default: |
pace |
string | Pace of the random delays between 'blowhorn withdraw' actions (page loads, typing, clicks, API calls): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn withdraw'. One of: linkedin, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What `blowhorn withdraw --json` prints on exit 0: the sent invitations the run examined as profile URLs, how many withdrawals it confirmed, and how many cards it left alone. A row never carries a display name: identity is the profile URL only, so no person's name lands in a committed artifact. `--limit` is spent only on confirmed withdrawals: a click that reads no confirmation neither consumes the budget nor counts as withdrawn, and a combined unconfirmed/failed streak halts the run. A card whose age cannot be parsed is always kept and counted under `unreadable`, never withdrawn. On a dry run `sent` is 0 and `candidates` are the eligible rows `--apply` would withdraw.
| Name | Type | Description |
|---|---|---|
candidates required |
array of object | The eligible sent invitations the run examined, as profile URLs, oldest first as the list renders them. |
candidates[].profile_url required |
string (uri) | The invitee's LinkedIn profile URL. Never a display name. |
dry_run required |
boolean | True when nothing was withdrawn and nothing was written. |
examined |
integer | Cards examined, including cards left alone. |
failed |
integer | Cards whose withdrawal failed after the control was clicked. |
held required |
array of object | Rows held as clicked with the outcome not read. Withdrawal records none of its own; the list is empty unless a held row was carried in. |
held[].profile_url required |
string (uri) | |
sent required |
integer | Withdrawals confirmed (the card provably left the sent list). Always 0 on a dry run. |
skipped required |
integer | Cards examined and left alone (too new to withdraw, or an age that could not be parsed). |
too_new |
integer | Cards newer than the `--older-than` cutoff, left alone. |
unconfirmed |
integer | Cards clicked whose withdrawal could not be confirmed from the list. |
unreadable |
integer | Cards whose pending age could not be parsed, kept and never withdrawn. |
withdrawn |
integer | Withdrawals confirmed, the same count as `sent`. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
WithdrawResponse
| Name | Type | Description |
|---|---|---|
candidates required |
array of object | The eligible sent invitations the run examined, as profile URLs, oldest first as the list renders them. |
candidates[].profile_url required |
string (uri) | The invitee's LinkedIn profile URL. Never a display name. |
dry_run required |
boolean | True when nothing was withdrawn and nothing was written. |
examined |
integer | Cards examined, including cards left alone. |
failed |
integer | Cards whose withdrawal failed after the control was clicked. |
held required |
array of object | Rows held as clicked with the outcome not read. Withdrawal records none of its own; the list is empty unless a held row was carried in. |
held[].profile_url required |
string (uri) | |
sent required |
integer | Withdrawals confirmed (the card provably left the sent list). Always 0 on a dry run. |
skipped required |
integer | Cards examined and left alone (too new to withdraw, or an age that could not be parsed). |
too_new |
integer | Cards newer than the `--older-than` cutoff, left alone. |
unconfirmed |
integer | Cards clicked whose withdrawal could not be confirmed from the list. |
unreadable |
integer | Cards whose pending age could not be parsed, kept and never withdrawn. |
withdrawn |
integer | Withdrawals confirmed, the same count as `sent`. |
Profile
POST
/cli/profile/accounts
List, map or unmap a profile's social accounts. An account is a Blowhorn
List, map or unmap a profile's social accounts. An account is a Blowhorn connection on one platform; a profile acts through the accounts mapped to it, one per platform, whoever owns them, so a person may keep several profiles and a brand profile reaches the accounts mapped to it. --map replaces the profile's account on that platform; --unmap leaves the connection itself intact. --page records the account's own page on the account already mapped for that platform, which is where the profile's handle is read from: Chrome profile discovery finds a sign-in, never a LinkedIn /in/ name.
Request body required
The flags and arguments of `blowhorn profile accounts`, one property per parameter.
| Name | Type | Description |
|---|---|---|
account_page |
string | Record this account page (x.com/<handle>, linkedin.com/in/<name>, github.com/<login>, reddit.com/user/<name> or bsky.app/profile/<handle>) on the account mapped for its platform |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
map_connection |
string | Map this connection id to the profile, replacing its account on that platform |
name required |
string | Profile name |
pace |
string | Pace of the random delays between 'blowhorn profile accounts' browser steps: a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
unmap_connection |
string | Unmap this connection id from the profile; the connection is left intact |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
A profile's social accounts: the connections in your Blowhorn account mapped to it (one per platform) and the organization's other platform connections it could map. `change` names what `--map`, `--unmap` or `--page` did, and is null for a plain listing. A `page` change carries the account page recorded (`url`) and whether anything was sent (`outcome`).
| Name | Type | Description |
|---|---|---|
available required |
array of ProfileAccount | |
available[].id required |
string | |
available[].name required |
string | null | |
available[].owner required |
string | null | |
available[].platform required |
string | |
available[].status required |
string | null | |
available[].url required |
string | null | |
change required |
one of null, object | |
mapped required |
array of ProfileAccount | |
mapped[].id required |
string | |
mapped[].name required |
string | null | |
mapped[].owner required |
string | null | |
mapped[].platform required |
string | |
mapped[].status required |
string | null | |
mapped[].url required |
string | null | |
ok required |
any | |
profile required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileAccountsResponse
| Name | Type | Description |
|---|---|---|
available required |
array of ProfileAccount | |
available[].id required |
string | |
available[].name required |
string | null | |
available[].owner required |
string | null | |
available[].platform required |
string | |
available[].status required |
string | null | |
available[].url required |
string | null | |
change required |
one of null, object | |
mapped required |
array of ProfileAccount | |
mapped[].id required |
string | |
mapped[].name required |
string | null | |
mapped[].owner required |
string | null | |
mapped[].platform required |
string | |
mapped[].status required |
string | null | |
mapped[].url required |
string | null | |
ok required |
any | |
profile required |
string |
POST
/cli/profile/auth
Refresh authentication/session state for one or more platforms.
Refresh authentication/session state for one or more platforms.
Supported platforms and authentication mechanisms:
linkedin - Persistent browser session. Automated login via LI_USERNAME / LI_PASSWORD in the store, with fallback to manual browser entry.
x - Persistent browser session. Automated login via X_USERNAME / X_PASSWORD in the store, with fallback to manual browser entry.
reddit - Persistent browser session. Automated login via RDDT_USERNAME / RDDT_PASSWORD in the store, with fallback to manual browser entry.
slack - Hybrid: session credentials (xoxc token + d cookie) captured through the run's browser driver (the extension by default), saved to the profile's Slack credential in the store, and reused for direct API calls. No Slack app install required. Use --workspace <host> to capture a new workspace; omit it to refresh every workspace listed under 'slack_workspaces' in that credential. Use --mode paste for the legacy OAuth-token flow.
bluesky - No browser. Logs in to Bluesky with a handle and app password -- the same login every post makes -- and stores the pair only when Bluesky accepts it, so a refused credential is never saved. For one named profile in a terminal it asks for both (the stored handle is the default; a blank app password keeps the stored one); --handle and --app-password-stdin supply them without a prompt. Otherwise the stored pair is logged in with and nothing is written.
Use --profile all to refresh every profile in your organization after applying any --exclude filters. The profiles are read from the store; a store that cannot be reached is the named cause and exit 3.
With --headless the browser still opens, as a background window (Chrome is never run headless); only automated credential login is attempted and the manual browser fallback is skipped.
Request body required
The flags and arguments of `blowhorn profile auth`, one property per parameter.
| Name | Type | Description |
|---|---|---|
all |
boolean | Refresh every platform 'blowhorn profile auth' can log in to (linkedin, x, reddit, slack). Supersedes --platform: when --all is set the platform is ignored, whether you typed it or inherited it from BLOWHORN_PLATFORM or defaults.platform Default: |
app_password_stdin |
boolean | Bluesky only, one named profile: read the app password from the first line of stdin, so it never reaches argv, the daily log or a prompt. A blank line is refused and nothing is written Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
handle |
string | Bluesky only, one named profile: the handle to log in as (alice.bsky.social; a leading @ is dropped). Stored with the app password only when Bluesky accepts the login; omit it to use the stored handle |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
mode |
string | Slack only: 'session' (default) captures xoxc/d via browser; 'paste' prompts for a User OAuth Token (xoxp-…) - requires app install. |
pace |
string | Pace of the random delays between 'blowhorn profile auth' browser steps: a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | Slack only: capture session credentials for this workspace domain (e.g. 'mesheryio.slack.com'). Opens a browser window; sign in to Slack, and the xoxc token + d cookie are extracted. If omitted, all workspaces listed under 'slack_workspaces' in the profile's Slack credential in the store are refreshed. |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints, per profile and platform, whether sign-in was refreshed (or would be, with `--dry-run`). `ok` is false when any row failed, and the same document is printed on that path.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
failed required |
array of ProfileAuthRow | |
failed[].detail |
string | What was read or stored, never a secret. |
failed[].platform required |
string | |
failed[].profile required |
string | |
failed[].status required |
string | A short outcome such as ok, would refresh or a failure with its reason in parentheses. |
ok required |
boolean | |
rows required |
array of ProfileAuthRow | |
rows[].detail |
string | What was read or stored, never a secret. |
rows[].platform required |
string | |
rows[].profile required |
string | |
rows[].status required |
string | A short outcome such as ok, would refresh or a failure with its reason in parentheses. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileAuthResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
failed required |
array of ProfileAuthRow | |
failed[].detail |
string | What was read or stored, never a secret. |
failed[].platform required |
string | |
failed[].profile required |
string | |
failed[].status required |
string | A short outcome such as ok, would refresh or a failure with its reason in parentheses. |
ok required |
boolean | |
rows required |
array of ProfileAuthRow | |
rows[].detail |
string | What was read or stored, never a secret. |
rows[].platform required |
string | |
rows[].profile required |
string | |
rows[].status required |
string | A short outcome such as ok, would refresh or a failure with its reason in parentheses. |
POST
/cli/profile/create
Create a profile in your organization on Blowhorn.
Create a profile in your organization on Blowhorn. It reads a JSON document on stdin (--payload-stdin) holding the fields a person sets: name (required), description and userId. userId is the organization member the profile represents, as `blowhorn org members` lists them; leave it out for a brand. Blowhorn adds the organization, and you own the profile. The name must be handle-shaped (lowercase letters, digits, '-' or '_') and not one another profile uses. Any other key is refused, every refusal comes before anything is sent, and --dry-run makes the same checks and sends nothing. No folder is created and no email is looked up.
Request body required
The JSON document `profile create --payload-stdin` and `profile update --payload-stdin` read: the v1beta3 `ProfilePayload` fields a person sets. Blowhorn adds `organizationId`; `owner` and `metadata` are never taken from this document. Any other key is refused. On an update the document is the whole of these fields: a `description` left out is cleared and a `userId` left out makes the profile a brand.
| Name | Type | Description |
|---|---|---|
description |
string | What the profile is for. Blank is the same as left out. |
name required |
string | The profile's name, handle-shaped: lowercase letters, digits, `-` or `_`, starting with a letter or digit, at most 64 characters, never `all`, and not a name another visible profile uses. Trimmed and lowercased before it is checked. |
userId |
string (uuid) | null | The member of the organization the profile represents, as `org members` lists them. Null or left out for a brand. |
Responses
200
What the command prints to stdout with --json when it exits 0.
What `profile create --payload-stdin` did: the person-set fields it sent (or, with --dry-run, would send) to `createProfile`, and the profile Blowhorn answered with. `profile` is null on a dry run, which sends nothing.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
any | |
payload required |
object | The document after it was trimmed and normalized; a blank description or userId is left out. |
payload.description |
string | |
payload.name required |
string | |
payload.userId |
string | |
profile required |
one of null, ProfileListItem |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileCreateResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
any | |
payload required |
object | The document after it was trimmed and normalized; a blank description or userId is left out. |
payload.description |
string | |
payload.name required |
string | |
payload.userId |
string | |
profile required |
one of null, ProfileListItem |
POST
/cli/profile/current
Show the active profile after resolving CLI, env, and config defaults.
Show the active profile after resolving CLI, env, and config defaults.
Request body required
The flags and arguments of `blowhorn profile current`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn profile current' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile current' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/profile/delete
Delete a profile in your organization, named by its name or its id: Blowhorn asks
Delete a profile in your organization, named by its name or its id: Blowhorn asks your Blowhorn account to delete it, its accounts are unlinked, and the accounts themselves are kept. Nothing on this computer is moved or removed. A profile your organization does not list is refused. Needs the store: unreachable is exit 3 and nothing is deleted. Requires --yes; --dry-run asks nothing and deletes nothing.
Request body required
The flags and arguments of `blowhorn profile delete`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | The profile's name, or its id as profile list prints it |
pace |
string | Accepted by 'blowhorn profile delete' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile delete' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
yes |
boolean | Confirm the delete; without it the command refuses and exits 1 Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints whether the profile was deleted in your organization (`deleteProfile`): its accounts are unlinked and the accounts themselves kept. `name` is the deleted profile's name, also when it was named by its id. Nothing on this computer is moved or removed. With `--dry-run` nothing is asked and nothing is deleted.
| Name | Type | Description |
|---|---|---|
deleted required |
boolean | |
dry_run required |
boolean | |
name required |
string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileDeleteResponse
| Name | Type | Description |
|---|---|---|
deleted required |
boolean | |
dry_run required |
boolean | |
name required |
string | |
ok required |
any |
POST
/cli/profile/get
Print one profile as your Blowhorn account holds it: its id, description and the
Print one profile as your Blowhorn account holds it: its id, description and the person it represents, the accounts mapped to it, and which fields each platform's credential holds, every one shown as {"set": true}. No credential value is ever printed, in JSON or in the table. A store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn profile get`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | Profile name |
pace |
string | Accepted by 'blowhorn profile get' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile get' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
One profile as Blowhorn holds it: the profile, the accounts mapped to it, and which fields each platform's credential holds. A credential field is only ever `{"set": true}`; its value is never printed.
| Name | Type | Description |
|---|---|---|
accounts required |
one of null, array of ProfileAccount | The accounts mapped to the profile; null when they could not be read. |
accounts_error required |
string | null | Why the accounts could not be read; null when they were. |
credentials required |
one of null, object | Platform to the credential fields it holds, each `{"set": true}`; null when the credentials could not be read. |
credentials_read required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
credentials_read.available required |
boolean | |
credentials_read.error required |
string | null | |
ok required |
any | |
profile required |
ProfileListItem | One v1beta3 profile as Blowhorn prints it: its identity, the fields a person sets, and the person it represents, read from the organization's members. `person` is null for a brand (no `userId`) and for a represented user who is not a member Blowhorn can read. |
profile.description required |
string | |
profile.id required |
string | The Cloud profile id, Blowhorn's identity for the profile. |
profile.name required |
string | The profile's name, what `--profile` takes. |
profile.owner required |
string | null | |
profile.person required |
one of null, object | |
profile.userId required |
string | null | The member the profile represents; null for a brand. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileGetResponse
| Name | Type | Description |
|---|---|---|
accounts required |
one of null, array of ProfileAccount | The accounts mapped to the profile; null when they could not be read. |
accounts_error required |
string | null | Why the accounts could not be read; null when they were. |
credentials required |
one of null, object | Platform to the credential fields it holds, each `{"set": true}`; null when the credentials could not be read. |
credentials_read required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
credentials_read.available required |
boolean | |
credentials_read.error required |
string | null | |
ok required |
any | |
profile required |
ProfileListItem | One v1beta3 profile as Blowhorn prints it: its identity, the fields a person sets, and the person it represents, read from the organization's members. `person` is null for a brand (no `userId`) and for a represented user who is not a member Blowhorn can read. |
profile.description required |
string | |
profile.id required |
string | The Cloud profile id, Blowhorn's identity for the profile. |
profile.name required |
string | The profile's name, what `--profile` takes. |
profile.owner required |
string | null | |
profile.person required |
one of null, object | |
profile.userId required |
string | null | The member the profile represents; null for a brand. |
POST
/cli/profile/list
List the profiles your organization holds in your Blowhorn account: each profile's name, description, and the person it represents (none for a brand). This is the list every command selects profiles from; a folder on this computer does not add a profile to it. One read of the profiles and one of your organization's members; a store that cannot be reached is the named cause and exit 3.
List the profiles your organization holds in your Blowhorn account: each profile's name, description, and the person it represents (none for a brand). This is the list every command selects profiles from; a folder on this computer does not add a profile to it. One read of the profiles and one of your organization's members; a store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn profile list`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn profile list' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile list' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The organization's profiles as Blowhorn lists them (`getProfiles`), the list every command selects profiles from. Each item is the v1beta3 profile's identity and the person it represents, read from the organization's members in one pass.
| Name | Type | Description |
|---|---|---|
count required |
integer | |
ok required |
any | |
profiles required |
array of ProfileListItem | |
profiles[].description required |
string | |
profiles[].id required |
string | The Cloud profile id, Blowhorn's identity for the profile. |
profiles[].name required |
string | The profile's name, what `--profile` takes. |
profiles[].owner required |
string | null | |
profiles[].person required |
one of null, object | |
profiles[].userId required |
string | null | The member the profile represents; null for a brand. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileListResponse
| Name | Type | Description |
|---|---|---|
count required |
integer | |
ok required |
any | |
profiles required |
array of ProfileListItem | |
profiles[].description required |
string | |
profiles[].id required |
string | The Cloud profile id, Blowhorn's identity for the profile. |
profiles[].name required |
string | The profile's name, what `--profile` takes. |
profiles[].owner required |
string | null | |
profiles[].person required |
one of null, object | |
profiles[].userId required |
string | null | The member the profile represents; null for a brand. |
POST
/cli/profile/set
Store KEY=VALUE pairs on a profile: its platform credentials and its run settings.
Store KEY=VALUE pairs on a profile: its platform credentials and its run settings. A platform key (LI_*, X_*, RDDT_*, HN_*, SLACK_*, BLUESKY_*, GH_*, YT_*, IG_*, FB_*, TIKTOK_*, slack_workspaces and the <WORKSPACE>_SLACK_* session keys) is stored in that platform's credential in the store, where runs read it. A run setting (Risk Profile, Risk Profile By Platform, Daily Caps, Quiet Hours, Quiet Hours Time Zone) is stored in the profile's settings in your Blowhorn account, and a value a run could not read is refused. A blank value clears a key. Any other key is refused, and every refusal comes before anything is sent. Nothing is written to a file. The output names the keys that changed and never echoes a value. Replacing a credential an API check judges (GH_TOKEN, the Bluesky handle or app password, a Slack token or captured session) sets that platform back to not checked in profile status until the next check, because each such check records which credential it judged. A browser platform's check is about its saved session and stays.
A profile is created with profile create, and who it represents changes with profile update.
Request body required
The flags and arguments of `blowhorn profile set`, one property per parameter.
| Name | Type | Description |
|---|---|---|
assignments |
array of string | KEY=VALUE pairs to write (slack_workspaces takes a comma-delimited list) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | Profile name |
pace |
string | Accepted by 'blowhorn profile set' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile set' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints which keys `profile set` stored (names only, never values): platform credential keys in the store, and run settings in the profile's `metadata`. With `--dry-run` it prints only the plan.
ProfileSetDryRun
| Name | Type | Description |
|---|---|---|
dry_run required |
any | |
keys required |
array of string | |
name required |
string | |
ok required |
any |
ProfileSetWritten
| Name | Type | Description |
|---|---|---|
changed_keys required |
array of string | The keys whose stored value changed. |
name required |
string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileSetResponse
POST
/cli/profile/status
Report what each profile in your organization is set up for: the platforms it holds
Report what each profile in your organization is set up for: the platforms it holds an account for (credential key names only -- values are never printed), the browser session directory and its last-modified time, whether another blowhorn process holds its lock, the last recorded session check, and whether the profile is excluded by default. The profiles are your organization's list in your Blowhorn account, read once; a store that cannot be reached is the named cause and exit 3. No browser and no platform network by default: accounts are read from the store's credentials, once for every profile, with the person each profile represents. A credential read the store fails leaves every account unknown and is named on a Credentials: line, not fatal, and a check made then skips every platform and keeps its last results. With --json each platform also carries account_status: the status of the profile's account in your Blowhorn account (connected, disconnected, ...), read once for every profile, or null when none is mapped.
--check-sessions runs a read-only probe per configured platform (LinkedIn, X and Reddit open the saved session in a background window and look for the signed-in page; GitHub, Bluesky and Slack call their APIs) and records the result in the profile's session_status.json under data/cache/profiles/, and records a LinkedIn, X or Reddit sign-in it finds (or one that ended) on the profile's account in your Blowhorn account. It never logs in, never types a credential, and reports 'busy' instead of waiting when a session is in use. --check-platform runs that same probe for one platform only and leaves every other platform's recorded result alone; --check-platform github checks the profile's GitHub token in the store against GitHub (the login it signs in as, its scopes and what they lack) with no browser at all. --roster additionally reads the profiles roster from the store to show which platforms each profile has a handle for; a store that cannot be reached is the named cause and exit 3, and a roster that reads empty or cannot be read is reported on a Roster: line, not fatal. --chrome additionally reads this machine's Chrome profile mappings from the store, once, and Chrome's own profile list, and adds a Chrome column: the mapped Chrome profile directory and whether the name and account recorded when it was mapped still match (drift, missing, unknown). A mapping is identity, never permission. A Chrome profile list that cannot be read is reported on a Chrome: line, not fatal.
Request body required
The flags and arguments of `blowhorn profile status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
check_platform |
string | Run the --check-sessions probe for this one platform only (implies --check-sessions) and leave the other platforms' recorded results as they are. One of: linkedin, x, reddit, hn, slack, bluesky, github; github, bluesky and slack are API calls and open no browser. Unlike --platform it is never read from BLOWHORN_PLATFORM or defaults.platform, so a default cannot narrow a check nobody asked to narrow |
check_sessions |
boolean | Probe each configured platform's saved session read-only (in a background browser window unless --headed) and record the result in the profile's session_status.json under data/cache/profiles/; never logs in, and reports 'busy' instead of waiting for a session another blowhorn process is using Default: |
chrome |
boolean | Read this machine's Chrome profile mappings from the store and Chrome's profile list, and add a Chrome column: the mapped directory and whether its recorded name and account still match. Opens no browser; a store that cannot be reached is the named cause and exit 3 Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the random delays between 'blowhorn profile status' browser steps: a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
roster |
boolean | Also read the profiles roster from the store and report which platforms each profile has a handle for. Needs the store: a store that cannot be reached is the named cause and exit 3; a roster that reads empty or cannot be read is reported on a Roster: line, not fatal Default: |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the status board: for each profile its identity, roster membership, per-platform credential, browser-session, last-check and Cloud account facts. A check records each LinkedIn, X and Reddit verdict on the profile's Cloud account (connected, or disconnected when the sign-in ended). With `--check-sessions` or `--check-platform` it probes first.
| Name | Type | Description |
|---|---|---|
account_errors required |
array of string | One sentence per checked sign-in the store refused to record; empty without a check. |
accounts required |
AvailabilityStatus | Whether each profile's accounts in your Blowhorn account were read; false with no error for a store that keeps no accounts. |
accounts.available required |
boolean | |
accounts.error required |
string | null | |
checked_platforms required |
array of string | The platforms this run probed; empty for a read without a check. |
checked_sessions required |
boolean | |
chrome required |
object | |
chrome.available required |
boolean | |
chrome.error required |
string | null | |
chrome.host required |
string | null | |
count required |
integer | |
credentials required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
credentials.available required |
boolean | |
credentials.error required |
string | null | |
excluded_by_default required |
array of string | |
generated_at required |
string | |
ok required |
any | |
people required |
AvailabilityStatus | Whether the people the profiles represent were read, one read for every profile. |
people.available required |
boolean | |
people.error required |
string | null | |
profiles required |
array of ProfileStatusEntry | |
profiles[].chrome required |
one of ProfileChromeEntry, null | The Chrome mapping, only with `--chrome`; null for an unmapped profile or when nothing was read. |
profiles[].display_name required |
string | The full name of the member the profile represents; empty for a brand, or when the people could not be read. |
profiles[].email required |
string | The email of the member the profile represents; empty for a brand, or when the people could not be read. |
profiles[].excluded_by_default required |
boolean | True when the default exclude list leaves the profile out of `--profile all`. |
profiles[].in_roster required |
boolean | null | Null when the roster could not be read. |
profiles[].last_check_at required |
string | null | |
profiles[].last_check_error required |
string | null | |
profiles[].name required |
string | |
profiles[].platforms required |
object | Platform name (bluesky, github, linkedin, reddit, slack, x) to its status. |
profiles[].roster_full_name required |
string | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileStatusResponse
| Name | Type | Description |
|---|---|---|
account_errors required |
array of string | One sentence per checked sign-in the store refused to record; empty without a check. |
accounts required |
AvailabilityStatus | Whether each profile's accounts in your Blowhorn account were read; false with no error for a store that keeps no accounts. |
accounts.available required |
boolean | |
accounts.error required |
string | null | |
checked_platforms required |
array of string | The platforms this run probed; empty for a read without a check. |
checked_sessions required |
boolean | |
chrome required |
object | |
chrome.available required |
boolean | |
chrome.error required |
string | null | |
chrome.host required |
string | null | |
count required |
integer | |
credentials required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
credentials.available required |
boolean | |
credentials.error required |
string | null | |
excluded_by_default required |
array of string | |
generated_at required |
string | |
ok required |
any | |
people required |
AvailabilityStatus | Whether the people the profiles represent were read, one read for every profile. |
people.available required |
boolean | |
people.error required |
string | null | |
profiles required |
array of ProfileStatusEntry | |
profiles[].chrome required |
one of ProfileChromeEntry, null | The Chrome mapping, only with `--chrome`; null for an unmapped profile or when nothing was read. |
profiles[].display_name required |
string | The full name of the member the profile represents; empty for a brand, or when the people could not be read. |
profiles[].email required |
string | The email of the member the profile represents; empty for a brand, or when the people could not be read. |
profiles[].excluded_by_default required |
boolean | True when the default exclude list leaves the profile out of `--profile all`. |
profiles[].in_roster required |
boolean | null | Null when the roster could not be read. |
profiles[].last_check_at required |
string | null | |
profiles[].last_check_error required |
string | null | |
profiles[].name required |
string | |
profiles[].platforms required |
object | Platform name (bluesky, github, linkedin, reddit, slack, x) to its status. |
profiles[].roster_full_name required |
string | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
POST
/cli/profile/switch
Set a profile as the default profile in global config. The profile must be in your organization's list, read from the store; a store that cannot be reached is the named cause and exit 3.
Set a profile as the default profile in global config. The profile must be in your organization's list, read from the store; a store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn profile switch`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | Profile name to set as default |
pace |
string | Accepted by 'blowhorn profile switch' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn profile'. One of: bluesky, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn profile switch' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/profile/update
Replace the fields a person sets on one profile.
Replace the fields a person sets on one profile. The profile is named by its name or its id, and the fields come from the JSON document on stdin (--payload-stdin): name (required), description and userId. The document is the whole of them: a description left out is cleared, and a userId left out makes the profile a brand that represents nobody. The profile's run settings (Risk Profile, caps, quiet hours) are read just before the write and sent back as they are, and its owner is unchanged. The same refusals as `profile create` apply, a rename onto another profile's name included; --dry-run sends nothing.
Request body required
The JSON document `profile create --payload-stdin` and `profile update --payload-stdin` read: the v1beta3 `ProfilePayload` fields a person sets. Blowhorn adds `organizationId`; `owner` and `metadata` are never taken from this document. Any other key is refused. On an update the document is the whole of these fields: a `description` left out is cleared and a `userId` left out makes the profile a brand.
| Name | Type | Description |
|---|---|---|
description |
string | What the profile is for. Blank is the same as left out. |
name required |
string | The profile's name, handle-shaped: lowercase letters, digits, `-` or `_`, starting with a letter or digit, at most 64 characters, never `all`, and not a name another visible profile uses. Trimmed and lowercased before it is checked. |
userId |
string (uuid) | null | The member of the organization the profile represents, as `org members` lists them. Null or left out for a brand. |
Responses
200
What the command prints to stdout with --json when it exits 0.
What `profile update --payload-stdin` did: the person-set fields it sent (or, with --dry-run, would send) to `updateProfile`, and the profile Blowhorn answered with. `profile` is null on a dry run, which sends nothing.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
any | |
payload required |
object | The document after it was trimmed and normalized; a blank description or userId is left out. |
payload.description |
string | |
payload.name required |
string | |
payload.userId |
string | |
profile required |
one of null, ProfileListItem |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ProfileUpdateResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
any | |
payload required |
object | The document after it was trimmed and normalized; a blank description or userId is left out. |
payload.description |
string | |
payload.name required |
string | |
payload.userId |
string | |
profile required |
one of null, ProfileListItem |
Org
POST
/cli/org/members
List the members of your organization on Blowhorn.
List the members of your organization on Blowhorn. Each one's user id, name and email, read once from the organization's member list. These are the people a profile can represent: the id is the userId `profile create` and `profile update` take.
Request body required
The flags and arguments of `blowhorn org members`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn org members' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn org': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn org members' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The members of the selected organization in your Blowhorn account (`getUsersForOrg`), the people a profile can represent, sorted by name and then email. `name` is the member's first and last name, empty when their account has neither.
| Name | Type | Description |
|---|---|---|
count required |
integer | |
members required |
array of object | |
members[].email required |
string | |
members[].id required |
string | The member's user id, the `userId` a profile document takes. |
members[].name required |
string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
OrgMembersResponse
| Name | Type | Description |
|---|---|---|
count required |
integer | |
members required |
array of object | |
members[].email required |
string | |
members[].id required |
string | The member's user id, the `userId` a profile document takes. |
members[].name required |
string | |
ok required |
any |
Content
POST
/cli/content/approve
Approve one activity by number, so a post run may publish it, and record who approved it and when in the activity's 'Approved by' column. This is the person's door under approval mode (defaults.approval.mode): activities an agent queued wait here. Approving is a person's act, so a caller marked as an agent (BLOWHORN_ACTOR=agent) or without a terminal is refused in every mode. An activity that is already approved is refused. --dry-run reports the activity and approves nothing.
Approve one activity by number, so a post run may publish it, and record who approved it and when in the activity's 'Approved by' column. This is the person's door under approval mode (defaults.approval.mode): activities an agent queued wait here. Approving is a person's act, so a caller marked as an agent (BLOWHORN_ACTOR=agent) or without a terminal is refused in every mode. An activity that is already approved is refused. --dry-run reports the activity and approves nothing.
Request body required
The flags and arguments of `blowhorn content approve`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number, as 'content list' prints it (stable for the life of the activity) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content approve' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content approve' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the content row that was approved, with its resulting state, fingerprint and the 'Approved by' record the approval wrote. `--dry-run` (the demo runs every command dry) prints the plan and approves nothing.
ContentApproveDryRun
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
dry_run required |
any | |
ok required |
any |
ContentApproveDone
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
fingerprint required |
string | |
ok required |
any | |
platform required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentApproveResponse
POST
/cli/content/delete
Delete one activity by number. Requires --yes; --dry-run reports the activity and touches nothing; --expected-fingerprint refuses the delete if the activity changed since it was read. The activity is removed from the store and every other activity keeps its number.
Delete one activity by number. Requires --yes; --dry-run reports the activity and touches nothing; --expected-fingerprint refuses the delete if the activity changed since it was read. The activity is removed from the store and every other activity keeps its number.
Request body required
The flags and arguments of `blowhorn content delete`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number, as 'content list' prints it (stable for the life of the activity) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
expected_fingerprint |
string | Refuse the write if the post's fingerprint (from 'content get' or 'content list') no longer matches |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content delete' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content delete' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
yes |
boolean | Confirm the delete; without it the command refuses and exits 1 (use --dry-run to preview) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the content activity that was deleted, with the values it held.
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
deleted required |
any | |
ok required |
any | |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentDeleteResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
deleted required |
any | |
ok required |
any | |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
POST
/cli/content/get
Print one post by number: every column, its state, its validation problems, and the fingerprint that 'content upsert' and 'content delete' take as --expected-fingerprint so a later write can refuse an activity edited in the store meanwhile.
Print one post by number: every column, its state, its validation problems, and the fingerprint that 'content upsert' and 'content delete' take as --expected-fingerprint so a later write can refuse an activity edited in the store meanwhile.
Request body required
The flags and arguments of `blowhorn content get`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number, as 'content list' prints it (stable for the life of the activity) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content get' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content get' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints one content row with its canonical column values, state, fingerprint and any validation errors. It carries no `ok` key.
| Name | Type | Description |
|---|---|---|
activity required |
integer | The stable row number, never a position. |
fingerprint required |
string | A hash of the values, to pass back as the expected fingerprint of an update. |
platform required |
string | The normalized platform name; empty when the row names none. |
preview required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
validation_errors required |
array of string | |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentGetResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | The stable row number, never a position. |
fingerprint required |
string | A hash of the values, to pass back as the expected fingerprint of an update. |
platform required |
string | The normalized platform name; empty when the row names none. |
preview required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
validation_errors required |
array of string | |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
POST
/cli/content/list
List the content queue's activities with their state and, for each, the profiles a post run would select it for right now ('Pending for' -- the same selection 'blowhorn post --check-queue' prints, read from the profiles roster). One read of the store; nothing is written and no browser opens.
List the content queue's activities with their state and, for each, the profiles a post run would select it for right now ('Pending for' -- the same selection 'blowhorn post --check-queue' prints, read from the profiles roster). One read of the store; nothing is written and no browser opens.
--profile keeps activities addressed to those profiles or to 'all'; --platform keeps one platform; --pending keeps only activities some profile would pick up now. --json is the shape the desktop app's Content screen reads, fingerprints included.
Request body required
The flags and arguments of `blowhorn content list`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content list' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
pending |
boolean | Only posts some profile would pick up right now: the posts 'blowhorn post --check-queue' lists Default: |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content list' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the content queue, optionally filtered, with each activity's state and the profiles it is still waiting on.
| Name | Type | Description |
|---|---|---|
activities required |
array of object | |
activities[].activity required |
integer | |
activities[].fingerprint required |
string | |
activities[].pending_for required |
array of string | Profiles that have not posted this activity yet. |
activities[].platform required |
string | |
activities[].preview required |
string | |
activities[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
activities[].values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
activities[].values.Amplify required |
string | |
activities[].values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
activities[].values.Approved? required |
string | |
activities[].values.Comment on required |
string | |
activities[].values.Date Promoted required |
string | |
activities[].values.Destination required |
string | |
activities[].values.Encoded Post required |
string | |
activities[].values.GDrive Link required |
string | |
activities[].values.Image URL required |
string | |
activities[].values.Message Text required |
string | |
activities[].values.Platform required |
string | |
activities[].values.Profile required |
string | |
activities[].values.Promote On required |
string | |
activities[].values.Promotion Link required |
string | |
activities[].values.Title required |
string | |
activities[].values.Video URL required |
string | |
count required |
integer | |
extra_columns required |
array of string | |
generated_at required |
string | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
missing_columns required |
array of string | |
missing_required_columns required |
array of string | |
ok required |
any | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentListResponse
| Name | Type | Description |
|---|---|---|
activities required |
array of object | |
activities[].activity required |
integer | |
activities[].fingerprint required |
string | |
activities[].pending_for required |
array of string | Profiles that have not posted this activity yet. |
activities[].platform required |
string | |
activities[].preview required |
string | |
activities[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
activities[].values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
activities[].values.Amplify required |
string | |
activities[].values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
activities[].values.Approved? required |
string | |
activities[].values.Comment on required |
string | |
activities[].values.Date Promoted required |
string | |
activities[].values.Destination required |
string | |
activities[].values.Encoded Post required |
string | |
activities[].values.GDrive Link required |
string | |
activities[].values.Image URL required |
string | |
activities[].values.Message Text required |
string | |
activities[].values.Platform required |
string | |
activities[].values.Profile required |
string | |
activities[].values.Promote On required |
string | |
activities[].values.Promotion Link required |
string | |
activities[].values.Title required |
string | |
activities[].values.Video URL required |
string | |
count required |
integer | |
extra_columns required |
array of string | |
generated_at required |
string | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
missing_columns required |
array of string | |
missing_required_columns required |
array of string | |
ok required |
any | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
POST
/cli/content/posts
List the posts as one list: every post in the content queue with its own schedule inline (its 'Promote On', or not scheduled), then the schedule's jobs as rows of their own, labelled by their command. A job does not point at a post: a 'post' job drains the queue at run time, selecting by profile, platform, approval and 'Promote On', so the jobs are shown unlinked rather than joined to posts they do not name.
List the posts as one list: every post in the content queue with its own schedule inline (its 'Promote On', or not scheduled), then the schedule's jobs as rows of their own, labelled by their command. A job does not point at a post: a 'post' job drains the queue at run time, selecting by profile, platform, approval and 'Promote On', so the jobs are shown unlinked rather than joined to posts they do not name.
--profile and --platform filter both halves; --pending keeps only the posts some profile would pick up now (the jobs are not filtered by it). --json is the shape the desktop app's posts list reads, each post carrying its schedule and the jobs unlinked. One read of each half of the store; nothing is written and no browser opens.
Request body required
The flags and arguments of `blowhorn content posts`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content posts' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
pending |
boolean | Only posts some profile would pick up right now: the posts 'blowhorn post --check-queue' lists Default: |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content posts' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the one posts list: every post with its schedule inline (its own `Promote On`), then the schedule's jobs as unlinked rows in their `schedule list` shape. `ok` is false when either read reports missing columns.
| Name | Type | Description |
|---|---|---|
count required |
integer | |
extra_columns required |
array of string | |
generated_at required |
string | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
job_count required |
integer | |
jobs required |
array of ScheduleActivity | The schedule's jobs, unlinked: no job names a post, because a `post` job selects its content at run time; each job is labelled by its own command. |
jobs[].activity required |
integer | |
jobs[].command required |
string | |
jobs[].connection |
object | null | The activity's Google Chrome connection, from the tick's gate (#1600): null when a run may start. `held` is a run that waits, not one that fails, with the plain reason and the one in-app action that clears it; `heal` is a connection Blowhorn reconnects on its own before the run; `open` is a mapped Google Chrome profile that is not open, which Blowhorn opens in the background before the run. Absent from older engines. |
jobs[].connection.action required |
string | null | The one thing the person can do: `turn_on_extension` (turn the Blowhorn extension on in that Google Chrome profile), `reconnect` (reconnect it now), `choose_chrome_profile` (choose or confirm the Google Chrome profile) or `restart_chrome` (restart Google Chrome, the one action left when a native host process from before an upgrade is still answering and ending it did not clear the hold); null when Blowhorn heals it alone. |
jobs[].connection.chrome_directory required |
string | null | The Google Chrome profile directory the activity's profile is mapped to; null when none is. |
jobs[].connection.profile required |
string | |
jobs[].connection.reason required |
string | null | Why it waits or reconnects, in plain words that name no command. |
jobs[].connection.state required |
string | Values: |
jobs[].driver_mode required |
string | |
jobs[].due required |
string | `yes` when the activity's time has come: it is enabled and valid, no machine holds a live claim on it, and its `next_run` has passed. Always `no` when `next_run` is empty. A paused activity still reads `yes`, and `paused` says the tick is holding it. Values: |
jobs[].enabled required |
string | Values: |
jobs[].errors required |
string | Validation errors joined by a semicolon; empty when the activity is valid. |
jobs[].held_here required |
boolean | The claim is this install's own: this macOS user on this computer. Never decided by the hostname alone, which every macOS user on a Mac shares. |
jobs[].last_error required |
string | |
jobs[].last_log_path required |
string | |
jobs[].last_run_at required |
string | |
jobs[].lock_stale required |
boolean | |
jobs[].locked required |
boolean | |
jobs[].locked_at required |
string | |
jobs[].locked_by required |
string | |
jobs[].max_retries required |
integer | The retry budget for each arming; see `Max Retries` in the activity's values. |
jobs[].next_run required |
string | When the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
jobs[].params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
jobs[].pause_reason required |
string | |
jobs[].paused required |
string | Values: |
jobs[].paused_at required |
string | |
jobs[].paused_by required |
string | |
jobs[].platform required |
string | The platform, or `all`. |
jobs[].profile required |
string | The profile, or a hyphen when the activity names none. |
jobs[].quiet_hours required |
string | `yes` when the activity is inside its quiet hours now (see `quiet_hours_window`): the tick holds it, unclaimed, and runs it when the window ends. Computed, never stored. Values: |
jobs[].quiet_hours_tz required |
string | The zone `quiet_hours_window` is read in: an IANA name, or this computer's zone; empty when no window is set. |
jobs[].quiet_hours_until required |
string | When the current window ends, in local time; empty when the activity is not in quiet hours now. |
jobs[].quiet_hours_window required |
string | The quiet-hours window in force for the activity's profile, `HH:MM-HH:MM` like `22:00-07:00`; empty when no window is set anywhere for it. |
jobs[].recurrence required |
string | |
jobs[].retry_count required |
integer | Failed attempts since the activity was last armed; see `Retry Count` in the activity's values. |
jobs[].retry_delay_minutes required |
integer | |
jobs[].risk_hold required |
string | Why a Run now would be held, including while the profile is warming up. Empty when the activity may run. A confirmation passes this hold except when `risk_hold_source` is `published`. |
jobs[].risk_hold_source required |
string | Where the hold comes from. `published` is a limit the platform states, which a confirmation does not pass. Empty when `risk_hold` is empty. |
jobs[].risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty for an activity without one profile and one platform, which names no single preset. |
jobs[].risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen; empty with `risk_profile`. |
jobs[].risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did, `builtin` when nothing chose one); empty with `risk_profile`. |
jobs[].risk_profile_warming required |
string | `yes` while the profile or this computer is warming up; empty with `risk_profile` (an activity on every platform names no single preset). Values: |
jobs[].run_at required |
string | |
jobs[].status required |
string | The activity's stored status, such as pending, scheduled, running, retrying or failed. Never a lock. |
missing_columns required |
array of string | Schedule columns the store is missing, as `schedule list` reports them. |
missing_required_columns required |
array of string | |
ok required |
boolean | |
pause required |
one of PauseState, null | The pause every machine honours, or null. |
posts required |
array of PostWithSchedule | |
posts[].activity required |
integer | |
posts[].fingerprint required |
string | |
posts[].pending_for required |
array of string | Profiles that have not posted this activity yet. |
posts[].platform required |
string | |
posts[].preview required |
string | |
posts[].schedule required |
PostSchedule | The schedule a post carries on itself: when it is set to go out, from its own `Promote On`, and whether that time is still ahead. A job never supplies it - no job points at a post - so an empty `promote_on` is the "not scheduled" case. |
posts[].schedule.promote_on required |
string | The post's `Promote On`, formatted as YYYY-MM-DD HH:MM:SS; empty when the post carries none. |
posts[].schedule.scheduled required |
boolean | `true` when the post is in the `scheduled` state: approved, not yet promoted, and its `Promote On` is still ahead. |
posts[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
posts[].values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
posts[].values.Amplify required |
string | |
posts[].values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
posts[].values.Approved? required |
string | |
posts[].values.Comment on required |
string | |
posts[].values.Date Promoted required |
string | |
posts[].values.Destination required |
string | |
posts[].values.Encoded Post required |
string | |
posts[].values.GDrive Link required |
string | |
posts[].values.Image URL required |
string | |
posts[].values.Message Text required |
string | |
posts[].values.Platform required |
string | |
posts[].values.Profile required |
string | |
posts[].values.Promote On required |
string | |
posts[].values.Promotion Link required |
string | |
posts[].values.Title required |
string | |
posts[].values.Video URL required |
string | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentPostsResponse
| Name | Type | Description |
|---|---|---|
count required |
integer | |
extra_columns required |
array of string | |
generated_at required |
string | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
job_count required |
integer | |
jobs required |
array of ScheduleActivity | The schedule's jobs, unlinked: no job names a post, because a `post` job selects its content at run time; each job is labelled by its own command. |
jobs[].activity required |
integer | |
jobs[].command required |
string | |
jobs[].connection |
object | null | The activity's Google Chrome connection, from the tick's gate (#1600): null when a run may start. `held` is a run that waits, not one that fails, with the plain reason and the one in-app action that clears it; `heal` is a connection Blowhorn reconnects on its own before the run; `open` is a mapped Google Chrome profile that is not open, which Blowhorn opens in the background before the run. Absent from older engines. |
jobs[].connection.action required |
string | null | The one thing the person can do: `turn_on_extension` (turn the Blowhorn extension on in that Google Chrome profile), `reconnect` (reconnect it now), `choose_chrome_profile` (choose or confirm the Google Chrome profile) or `restart_chrome` (restart Google Chrome, the one action left when a native host process from before an upgrade is still answering and ending it did not clear the hold); null when Blowhorn heals it alone. |
jobs[].connection.chrome_directory required |
string | null | The Google Chrome profile directory the activity's profile is mapped to; null when none is. |
jobs[].connection.profile required |
string | |
jobs[].connection.reason required |
string | null | Why it waits or reconnects, in plain words that name no command. |
jobs[].connection.state required |
string | Values: |
jobs[].driver_mode required |
string | |
jobs[].due required |
string | `yes` when the activity's time has come: it is enabled and valid, no machine holds a live claim on it, and its `next_run` has passed. Always `no` when `next_run` is empty. A paused activity still reads `yes`, and `paused` says the tick is holding it. Values: |
jobs[].enabled required |
string | Values: |
jobs[].errors required |
string | Validation errors joined by a semicolon; empty when the activity is valid. |
jobs[].held_here required |
boolean | The claim is this install's own: this macOS user on this computer. Never decided by the hostname alone, which every macOS user on a Mac shares. |
jobs[].last_error required |
string | |
jobs[].last_log_path required |
string | |
jobs[].last_run_at required |
string | |
jobs[].lock_stale required |
boolean | |
jobs[].locked required |
boolean | |
jobs[].locked_at required |
string | |
jobs[].locked_by required |
string | |
jobs[].max_retries required |
integer | The retry budget for each arming; see `Max Retries` in the activity's values. |
jobs[].next_run required |
string | When the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
jobs[].params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
jobs[].pause_reason required |
string | |
jobs[].paused required |
string | Values: |
jobs[].paused_at required |
string | |
jobs[].paused_by required |
string | |
jobs[].platform required |
string | The platform, or `all`. |
jobs[].profile required |
string | The profile, or a hyphen when the activity names none. |
jobs[].quiet_hours required |
string | `yes` when the activity is inside its quiet hours now (see `quiet_hours_window`): the tick holds it, unclaimed, and runs it when the window ends. Computed, never stored. Values: |
jobs[].quiet_hours_tz required |
string | The zone `quiet_hours_window` is read in: an IANA name, or this computer's zone; empty when no window is set. |
jobs[].quiet_hours_until required |
string | When the current window ends, in local time; empty when the activity is not in quiet hours now. |
jobs[].quiet_hours_window required |
string | The quiet-hours window in force for the activity's profile, `HH:MM-HH:MM` like `22:00-07:00`; empty when no window is set anywhere for it. |
jobs[].recurrence required |
string | |
jobs[].retry_count required |
integer | Failed attempts since the activity was last armed; see `Retry Count` in the activity's values. |
jobs[].retry_delay_minutes required |
integer | |
jobs[].risk_hold required |
string | Why a Run now would be held, including while the profile is warming up. Empty when the activity may run. A confirmation passes this hold except when `risk_hold_source` is `published`. |
jobs[].risk_hold_source required |
string | Where the hold comes from. `published` is a limit the platform states, which a confirmation does not pass. Empty when `risk_hold` is empty. |
jobs[].risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty for an activity without one profile and one platform, which names no single preset. |
jobs[].risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen; empty with `risk_profile`. |
jobs[].risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did, `builtin` when nothing chose one); empty with `risk_profile`. |
jobs[].risk_profile_warming required |
string | `yes` while the profile or this computer is warming up; empty with `risk_profile` (an activity on every platform names no single preset). Values: |
jobs[].run_at required |
string | |
jobs[].status required |
string | The activity's stored status, such as pending, scheduled, running, retrying or failed. Never a lock. |
missing_columns required |
array of string | Schedule columns the store is missing, as `schedule list` reports them. |
missing_required_columns required |
array of string | |
ok required |
boolean | |
pause required |
one of PauseState, null | The pause every machine honours, or null. |
posts required |
array of PostWithSchedule | |
posts[].activity required |
integer | |
posts[].fingerprint required |
string | |
posts[].pending_for required |
array of string | Profiles that have not posted this activity yet. |
posts[].platform required |
string | |
posts[].preview required |
string | |
posts[].schedule required |
PostSchedule | The schedule a post carries on itself: when it is set to go out, from its own `Promote On`, and whether that time is still ahead. A job never supplies it - no job points at a post - so an empty `promote_on` is the "not scheduled" case. |
posts[].schedule.promote_on required |
string | The post's `Promote On`, formatted as YYYY-MM-DD HH:MM:SS; empty when the post carries none. |
posts[].schedule.scheduled required |
boolean | `true` when the post is in the `scheduled` state: approved, not yet promoted, and its `Promote On` is still ahead. |
posts[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
posts[].values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
posts[].values.Amplify required |
string | |
posts[].values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
posts[].values.Approved? required |
string | |
posts[].values.Comment on required |
string | |
posts[].values.Date Promoted required |
string | |
posts[].values.Destination required |
string | |
posts[].values.Encoded Post required |
string | |
posts[].values.GDrive Link required |
string | |
posts[].values.Image URL required |
string | |
posts[].values.Message Text required |
string | |
posts[].values.Platform required |
string | |
posts[].values.Profile required |
string | |
posts[].values.Promote On required |
string | |
posts[].values.Promotion Link required |
string | |
posts[].values.Title required |
string | |
posts[].values.Video URL required |
string | |
roster required |
AvailabilityStatus | Whether a source a command reads (the store's credentials, the roster) could be read, and the one sentence why not. |
roster.available required |
boolean | |
roster.error required |
string | null |
POST
/cli/content/upsert
Create a post, or update one in place, from a JSON object of column: value pairs in the queue's own column names. Without --activity a new activity is created and given the next activity number; with --activity the payload is merged over that activity, and every column it does not name is left as it was.
Create a post, or update one in place, from a JSON object of column: value pairs in the queue's own column names. Without --activity a new activity is created and given the next activity number; with --activity the payload is merged over that activity, and every column it does not name is left as it was.
Platform must be one 'blowhorn post' publishes to (linkedin, x, reddit, slack, bluesky, github, layer5, meshery); Approved? takes yes or no (JSON true/false are written as yes/no); Promote On must parse (YYYY-MM-DD HH:MM:SS); Profile must name a profile the store knows, or 'all'. Unknown fields are refused rather than written. --expected-fingerprint refuses the write if the activity changed since 'content get' or 'content list' read it. --dry-run prints the payload and touches nothing.
Request body required
The flags and arguments of `blowhorn content upsert`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity |
integer | Activity number to update in place; omit to create a new activity |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
expected_fingerprint |
string | Refuse the write if the post's fingerprint (from 'content get' or 'content list') no longer matches |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output (the shape the desktop app's Content screen reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn content upsert' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
payload required |
string | JSON object of Content column: value pairs in the queue's own column names, for example '{"Platform":"x","Profile":"company","Message Text":"hello","Approved?":"yes"}' |
platform |
string | Platform filter for 'blowhorn content': 'content list' keeps the activities whose Platform column names this platform, and no other content subcommand reads it. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every activity is listed) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn content upsert' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the content row that was created or updated, with its resulting state and fingerprint.
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
created required |
boolean | |
fingerprint required |
string | |
ok required |
any | |
platform required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ContentUpsertResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
created required |
boolean | |
fingerprint required |
string | |
ok required |
any | |
platform required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
values required |
ContentValues | A content activity's columns, each a string (empty when unset): the post's platform, profile, text and media, whether it is approved, when it is promoted and where it landed. |
values.Amplify required |
string | |
values.Approved by required |
string | Who approved the activity and when ("<who> at <when>"), recorded by 'blowhorn content approve'; empty until then. A record, never set from a payload. |
values.Approved? required |
string | |
values.Comment on required |
string | |
values.Date Promoted required |
string | |
values.Destination required |
string | |
values.Encoded Post required |
string | |
values.GDrive Link required |
string | |
values.Image URL required |
string | |
values.Message Text required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Promote On required |
string | |
values.Promotion Link required |
string | |
values.Title required |
string | |
values.Video URL required |
string |
Schedule
POST
/cli/schedule/bootstrap
Preview or write a validated schedule starter set.
Preview or write a validated schedule starter set.
Request body required
The flags and arguments of `blowhorn schedule bootstrap`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule bootstrap' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
replace |
boolean | Replace the current scheduled actions before writing sample ones Default: |
risk_profile |
string | Accepted by 'blowhorn schedule bootstrap' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule bootstrap prints with --json on success: the sample rows it loaded into the schedule, or with --dry-run the rows it would load. Without --json a real run prints nothing.
| Name | Type | Description |
|---|---|---|
activities required |
array of object | The sample schedule rows, each keyed by schedule column name (Enabled, Command, Profile, Platform, Params, Run At, Recurrence, Status, ...). |
count required |
integer | |
dry_run |
boolean | Present and true only with --dry-run. |
ok required |
boolean | |
replace required |
boolean | Whether existing rows were (or would be) replaced. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleBootstrapResponse
| Name | Type | Description |
|---|---|---|
activities required |
array of object | The sample schedule rows, each keyed by schedule column name (Enabled, Command, Profile, Platform, Params, Run At, Recurrence, Status, ...). |
count required |
integer | |
dry_run |
boolean | Present and true only with --dry-run. |
ok required |
boolean | |
replace required |
boolean | Whether existing rows were (or would be) replaced. |
POST
/cli/schedule/delete
Delete a scheduled action by number.
Delete a scheduled action by number.
Request body required
The flags and arguments of `blowhorn schedule delete`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number to delete |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
expected_fingerprint |
string | Reject the delete if the current activity fingerprint no longer matches |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule delete' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule delete' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule delete prints with --json on success: the deleted activity, or with --dry-run the activity and fingerprint it would delete.
Deleted
| Name | Type | Description |
|---|---|---|
activity required |
integer | null | |
deleted required |
boolean | |
ok required |
boolean | |
values required |
object | The deleted activity's values keyed by schedule column name. |
DryRun
| Name | Type | Description |
|---|---|---|
activity required |
integer | null | |
dry_run required |
boolean | |
expected_fingerprint required |
string | null | |
ok required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleDeleteResponse
POST
/cli/schedule/destinations
List the post destinations that belong to an organization rather than to a person -- the organization blogs today -- and whether this organization has each one: a destination is set up when at least one of its profiles is opted in to it (a platform opt-in in the store, the same fact 'blowhorn post' reads to decide who may publish there). A destination no profile is opted in to can never be posted to, so the desktop app offers no new activity for it; activities already saved for it still show and run. Reads the store once; writes nothing.
List the post destinations that belong to an organization rather than to a person -- the organization blogs today -- and whether this organization has each one: a destination is set up when at least one of its profiles is opted in to it (a platform opt-in in the store, the same fact 'blowhorn post' reads to decide who may publish there). A destination no profile is opted in to can never be posted to, so the desktop app offers no new activity for it; activities already saved for it still show and run. Reads the store once; writes nothing.
Request body required
The flags and arguments of `blowhorn schedule destinations`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule destinations' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule destinations' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule destinations prints with --json: each post destination that belongs to an organization rather than to a person, and whether a profile of this organization is opted in to it. The desktop app offers a new activity for a destination only when it is set up.
| Name | Type | Description |
|---|---|---|
destinations required |
array of object | |
destinations[].configured required |
boolean | At least one profile is opted in to this destination. |
destinations[].platform required |
string | The platform name a content item or a scheduled activity uses (layer5, meshery). |
destinations[].profiles required |
array of string | The profiles opted in, lowercased and sorted. |
destinations[].site required |
string | The site the destination publishes to. |
ok required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleDestinationsResponse
| Name | Type | Description |
|---|---|---|
destinations required |
array of object | |
destinations[].configured required |
boolean | At least one profile is opted in to this destination. |
destinations[].platform required |
string | The platform name a content item or a scheduled activity uses (layer5, meshery). |
destinations[].profiles required |
array of string | The profiles opted in, lowercased and sorted. |
destinations[].site required |
string | The site the destination publishes to. |
ok required |
boolean |
POST
/cli/schedule/fleet
List every computer sharing your schedule whose background service has reported lately, and whether each is on duty, read from the last scheduler pass it recorded in the store: a pass that ran says on duty, a paused one says off duty, and one that failed says last pass failed. A computer with no pass in the reporting window (about half an hour) is left out, because nothing recent says what it is doing. Reads only -- it claims nothing and changes nothing.
List every computer sharing your schedule whose background service has reported lately, and whether each is on duty, read from the last scheduler pass it recorded in the store: a pass that ran says on duty, a paused one says off duty, and one that failed says last pass failed. A computer with no pass in the reporting window (about half an hour) is left out, because nothing recent says what it is doing. Reads only -- it claims nothing and changes nothing.
Request body required
The flags and arguments of `blowhorn schedule fleet`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule fleet' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule fleet' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule fleet prints with --json: each computer whose background service recorded a scheduler pass in the store within the reporting window, and the duty that pass shows. A computer with no pass in the window is not listed, so a reader that knows of it from elsewhere counts it as not reporting, never as on duty. Read-only.
| Name | Type | Description |
|---|---|---|
computers required |
array of object | One entry per computer, by host name. |
computers[].duty required |
string | `on_duty` when the pass ran (ok or cancelled at a safe point), `off_duty` when it was paused, `not_on_duty` when it failed or could not reach the store. Values: |
computers[].host required |
string | The computer's name, as its run records carry it. |
computers[].last_pass_at required |
string | When its latest scheduler pass was recorded, local `YYYY-MM-DD HH:MM:SS`. |
computers[].status required |
string | That pass's ledger status: `ok`, `paused`, `cancelled`, `error`, `store_unavailable` or `missing_columns`. |
ok required |
boolean | |
reporting_window_s required |
integer | How recent a computer's last scheduler pass must be for it to be listed, in seconds. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleFleetResponse
| Name | Type | Description |
|---|---|---|
computers required |
array of object | One entry per computer, by host name. |
computers[].duty required |
string | `on_duty` when the pass ran (ok or cancelled at a safe point), `off_duty` when it was paused, `not_on_duty` when it failed or could not reach the store. Values: |
computers[].host required |
string | The computer's name, as its run records carry it. |
computers[].last_pass_at required |
string | When its latest scheduler pass was recorded, local `YYYY-MM-DD HH:MM:SS`. |
computers[].status required |
string | That pass's ledger status: `ok`, `paused`, `cancelled`, `error`, `store_unavailable` or `missing_columns`. |
ok required |
boolean | |
reporting_window_s required |
integer | How recent a computer's last scheduler pass must be for it to be listed, in seconds. |
POST
/cli/schedule/get
Return a single scheduled action with all fields.
Return a single scheduled action with all fields.
Request body required
The flags and arguments of `blowhorn schedule get`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number to inspect |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule get' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule get' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints one schedule activity with its column values, fingerprint, parsed params, next scheduled time, lock state, validation errors, the Risk Profile it resolves to, and the limits the platform publishes on its action. It carries no `ok` key.
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
fingerprint required |
string | A hash of the values, to pass back as the expected fingerprint of an update. |
lock_stale required |
boolean | |
locked required |
boolean | |
params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
published_limits required |
array of PublishedLimit | The limits the platform publishes on this activity's action, each naming the presets that would go past it. Empty without one profile and one platform, or where the platform publishes none. |
published_limits[].above required |
array of string | The presets whose cap for this activity's profile, once warm, would go past this limit and is cut to it. Empty when every preset stays within it. |
published_limits[].actions required |
array of string | The gate actions the one count covers; several when the platform counts them together. |
published_limits[].as_of required |
string | The date the page was read. |
published_limits[].count required |
integer | |
published_limits[].label required |
string | The provenance label of the reading (`UNVERIFIED - docs`). |
published_limits[].per required |
string | Values: |
published_limits[].platform required |
string | |
published_limits[].platform_label required |
string | The platform's display name (`X`, `Bluesky`). |
published_limits[].source required |
string | The platform's page the limit was read from. |
published_limits[].what required |
string | The platform's own unit in plain words (`follows`, `original posts`). |
risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty without one profile and one platform. |
risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen. |
risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did). |
risk_profile_warming required |
string | Values: |
scheduled_for required |
string | The same instant `schedule list` prints as `next_run`: when the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
validation_errors required |
array of string | |
values required |
ScheduleValues | A schedule row's columns, each a string (empty when unset): the command and its params as `key=value;key=value`, when it runs and how it repeats, its retry budget, its status and the lock a running machine holds. |
values.Command required |
string | |
values.Driver Mode required |
string | |
values.Enabled required |
string | |
values.Last Error required |
string | |
values.Last Log Path required |
string | |
values.Last Run At required |
string | |
values.Locked At required |
string | |
values.Locked By required |
string | |
values.Max Retries required |
string | The retry budget for each arming: how many times a failed run is retried before the row is failed. Raising it alone does not schedule a failed row again. |
values.Next Run At required |
string | When the row is next due, in local time. Empty on a completed or failed row means never due again. An update that gives a completed or failed row a Next Run At, or changes its Run At, schedules it again in the same write: Status scheduled and Retry Count 0, with Last Run At, Last Error and Last Log Path kept, unless the update sets Status itself. |
values.Params required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Recurrence required |
string | |
values.Retry Count required |
string | Failed attempts since the row was last armed. A success sets it to 0, and so does an update that schedules a completed or failed row again. |
values.Retry Delay Minutes required |
string | |
values.Risk Profile required |
string | The activity's own Risk Profile (#1009): `cautious`, `standard` or `brisk`, or empty to inherit the preset its profile and platform resolve to. |
values.Run At required |
string | |
values.Status required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleGetResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
fingerprint required |
string | A hash of the values, to pass back as the expected fingerprint of an update. |
lock_stale required |
boolean | |
locked required |
boolean | |
params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
published_limits required |
array of PublishedLimit | The limits the platform publishes on this activity's action, each naming the presets that would go past it. Empty without one profile and one platform, or where the platform publishes none. |
published_limits[].above required |
array of string | The presets whose cap for this activity's profile, once warm, would go past this limit and is cut to it. Empty when every preset stays within it. |
published_limits[].actions required |
array of string | The gate actions the one count covers; several when the platform counts them together. |
published_limits[].as_of required |
string | The date the page was read. |
published_limits[].count required |
integer | |
published_limits[].label required |
string | The provenance label of the reading (`UNVERIFIED - docs`). |
published_limits[].per required |
string | Values: |
published_limits[].platform required |
string | |
published_limits[].platform_label required |
string | The platform's display name (`X`, `Bluesky`). |
published_limits[].source required |
string | The platform's page the limit was read from. |
published_limits[].what required |
string | The platform's own unit in plain words (`follows`, `original posts`). |
risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty without one profile and one platform. |
risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen. |
risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did). |
risk_profile_warming required |
string | Values: |
scheduled_for required |
string | The same instant `schedule list` prints as `next_run`: when the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
validation_errors required |
array of string | |
values required |
ScheduleValues | A schedule row's columns, each a string (empty when unset): the command and its params as `key=value;key=value`, when it runs and how it repeats, its retry budget, its status and the lock a running machine holds. |
values.Command required |
string | |
values.Driver Mode required |
string | |
values.Enabled required |
string | |
values.Last Error required |
string | |
values.Last Log Path required |
string | |
values.Last Run At required |
string | |
values.Locked At required |
string | |
values.Locked By required |
string | |
values.Max Retries required |
string | The retry budget for each arming: how many times a failed run is retried before the row is failed. Raising it alone does not schedule a failed row again. |
values.Next Run At required |
string | When the row is next due, in local time. Empty on a completed or failed row means never due again. An update that gives a completed or failed row a Next Run At, or changes its Run At, schedules it again in the same write: Status scheduled and Retry Count 0, with Last Run At, Last Error and Last Log Path kept, unless the update sets Status itself. |
values.Params required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Recurrence required |
string | |
values.Retry Count required |
string | Failed attempts since the row was last armed. A success sets it to 0, and so does an update that schedules a completed or failed row again. |
values.Retry Delay Minutes required |
string | |
values.Risk Profile required |
string | The activity's own Risk Profile (#1009): `cautious`, `standard` or `brisk`, or empty to inherit the preset its profile and platform resolve to. |
values.Run At required |
string | |
values.Status required |
string |
POST
/cli/schedule/list
List scheduled actions and whether they are currently due. With several machines sharing the store the Locked By column names the holder and marks this machine's own claims; a paused action reads paused: yes beside its due state, because due keeps meaning its time has passed.
List scheduled actions and whether they are currently due. With several machines sharing the store the Locked By column names the holder and marks this machine's own claims; a paused action reads paused: yes beside its due state, because due keeps meaning its time has passed.
Request body required
The flags and arguments of `blowhorn schedule list`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
due_only |
boolean | Show only activities that are currently due Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule list' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule list' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the schedule rows with their due, retry, lock and pause state, the all-machines pause if one is set, and the organization-wide stop if one is in force. `ok` is false when required columns are missing.
| Name | Type | Description |
|---|---|---|
activities required |
array of ScheduleActivity | |
activities[].activity required |
integer | |
activities[].command required |
string | |
activities[].connection |
object | null | The activity's Google Chrome connection, from the tick's gate (#1600): null when a run may start. `held` is a run that waits, not one that fails, with the plain reason and the one in-app action that clears it; `heal` is a connection Blowhorn reconnects on its own before the run; `open` is a mapped Google Chrome profile that is not open, which Blowhorn opens in the background before the run. Absent from older engines. |
activities[].connection.action required |
string | null | The one thing the person can do: `turn_on_extension` (turn the Blowhorn extension on in that Google Chrome profile), `reconnect` (reconnect it now), `choose_chrome_profile` (choose or confirm the Google Chrome profile) or `restart_chrome` (restart Google Chrome, the one action left when a native host process from before an upgrade is still answering and ending it did not clear the hold); null when Blowhorn heals it alone. |
activities[].connection.chrome_directory required |
string | null | The Google Chrome profile directory the activity's profile is mapped to; null when none is. |
activities[].connection.profile required |
string | |
activities[].connection.reason required |
string | null | Why it waits or reconnects, in plain words that name no command. |
activities[].connection.state required |
string | Values: |
activities[].driver_mode required |
string | |
activities[].due required |
string | `yes` when the activity's time has come: it is enabled and valid, no machine holds a live claim on it, and its `next_run` has passed. Always `no` when `next_run` is empty. A paused activity still reads `yes`, and `paused` says the tick is holding it. Values: |
activities[].enabled required |
string | Values: |
activities[].errors required |
string | Validation errors joined by a semicolon; empty when the activity is valid. |
activities[].held_here required |
boolean | The claim is this install's own: this macOS user on this computer. Never decided by the hostname alone, which every macOS user on a Mac shares. |
activities[].last_error required |
string | |
activities[].last_log_path required |
string | |
activities[].last_run_at required |
string | |
activities[].lock_stale required |
boolean | |
activities[].locked required |
boolean | |
activities[].locked_at required |
string | |
activities[].locked_by required |
string | |
activities[].max_retries required |
integer | The retry budget for each arming; see `Max Retries` in the activity's values. |
activities[].next_run required |
string | When the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
activities[].params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
activities[].pause_reason required |
string | |
activities[].paused required |
string | Values: |
activities[].paused_at required |
string | |
activities[].paused_by required |
string | |
activities[].platform required |
string | The platform, or `all`. |
activities[].profile required |
string | The profile, or a hyphen when the activity names none. |
activities[].quiet_hours required |
string | `yes` when the activity is inside its quiet hours now (see `quiet_hours_window`): the tick holds it, unclaimed, and runs it when the window ends. Computed, never stored. Values: |
activities[].quiet_hours_tz required |
string | The zone `quiet_hours_window` is read in: an IANA name, or this computer's zone; empty when no window is set. |
activities[].quiet_hours_until required |
string | When the current window ends, in local time; empty when the activity is not in quiet hours now. |
activities[].quiet_hours_window required |
string | The quiet-hours window in force for the activity's profile, `HH:MM-HH:MM` like `22:00-07:00`; empty when no window is set anywhere for it. |
activities[].recurrence required |
string | |
activities[].retry_count required |
integer | Failed attempts since the activity was last armed; see `Retry Count` in the activity's values. |
activities[].retry_delay_minutes required |
integer | |
activities[].risk_hold required |
string | Why a Run now would be held, including while the profile is warming up. Empty when the activity may run. A confirmation passes this hold except when `risk_hold_source` is `published`. |
activities[].risk_hold_source required |
string | Where the hold comes from. `published` is a limit the platform states, which a confirmation does not pass. Empty when `risk_hold` is empty. |
activities[].risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty for an activity without one profile and one platform, which names no single preset. |
activities[].risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen; empty with `risk_profile`. |
activities[].risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did, `builtin` when nothing chose one); empty with `risk_profile`. |
activities[].risk_profile_warming required |
string | `yes` while the profile or this computer is warming up; empty with `risk_profile` (an activity on every platform names no single preset). Values: |
activities[].run_at required |
string | |
activities[].status required |
string | The activity's stored status, such as pending, scheduled, running, retrying or failed. Never a lock. |
count required |
integer | |
missing_columns required |
array of string | |
ok required |
boolean | |
pause required |
one of PauseState, null | The pause every machine honours, or null. |
stop required |
one of StopState, null | The organization-wide stop in force, or null. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleListResponse
| Name | Type | Description |
|---|---|---|
activities required |
array of ScheduleActivity | |
activities[].activity required |
integer | |
activities[].command required |
string | |
activities[].connection |
object | null | The activity's Google Chrome connection, from the tick's gate (#1600): null when a run may start. `held` is a run that waits, not one that fails, with the plain reason and the one in-app action that clears it; `heal` is a connection Blowhorn reconnects on its own before the run; `open` is a mapped Google Chrome profile that is not open, which Blowhorn opens in the background before the run. Absent from older engines. |
activities[].connection.action required |
string | null | The one thing the person can do: `turn_on_extension` (turn the Blowhorn extension on in that Google Chrome profile), `reconnect` (reconnect it now), `choose_chrome_profile` (choose or confirm the Google Chrome profile) or `restart_chrome` (restart Google Chrome, the one action left when a native host process from before an upgrade is still answering and ending it did not clear the hold); null when Blowhorn heals it alone. |
activities[].connection.chrome_directory required |
string | null | The Google Chrome profile directory the activity's profile is mapped to; null when none is. |
activities[].connection.profile required |
string | |
activities[].connection.reason required |
string | null | Why it waits or reconnects, in plain words that name no command. |
activities[].connection.state required |
string | Values: |
activities[].driver_mode required |
string | |
activities[].due required |
string | `yes` when the activity's time has come: it is enabled and valid, no machine holds a live claim on it, and its `next_run` has passed. Always `no` when `next_run` is empty. A paused activity still reads `yes`, and `paused` says the tick is holding it. Values: |
activities[].enabled required |
string | Values: |
activities[].errors required |
string | Validation errors joined by a semicolon; empty when the activity is valid. |
activities[].held_here required |
boolean | The claim is this install's own: this macOS user on this computer. Never decided by the hostname alone, which every macOS user on a Mac shares. |
activities[].last_error required |
string | |
activities[].last_log_path required |
string | |
activities[].last_run_at required |
string | |
activities[].lock_stale required |
boolean | |
activities[].locked required |
boolean | |
activities[].locked_at required |
string | |
activities[].locked_by required |
string | |
activities[].max_retries required |
integer | The retry budget for each arming; see `Max Retries` in the activity's values. |
activities[].next_run required |
string | When the tick next takes the activity, in local time. Empty when it never will: a completed once-only activity, or a completed or failed activity with no Next Run At. Never the activity's old Run At. |
activities[].params required |
ScheduleParams | The parsed Params of a schedule activity, keyed by the command's own parameter names. A value is a string, a number or a boolean as the activity's text parses. |
activities[].pause_reason required |
string | |
activities[].paused required |
string | Values: |
activities[].paused_at required |
string | |
activities[].paused_by required |
string | |
activities[].platform required |
string | The platform, or `all`. |
activities[].profile required |
string | The profile, or a hyphen when the activity names none. |
activities[].quiet_hours required |
string | `yes` when the activity is inside its quiet hours now (see `quiet_hours_window`): the tick holds it, unclaimed, and runs it when the window ends. Computed, never stored. Values: |
activities[].quiet_hours_tz required |
string | The zone `quiet_hours_window` is read in: an IANA name, or this computer's zone; empty when no window is set. |
activities[].quiet_hours_until required |
string | When the current window ends, in local time; empty when the activity is not in quiet hours now. |
activities[].quiet_hours_window required |
string | The quiet-hours window in force for the activity's profile, `HH:MM-HH:MM` like `22:00-07:00`; empty when no window is set anywhere for it. |
activities[].recurrence required |
string | |
activities[].retry_count required |
integer | Failed attempts since the activity was last armed; see `Retry Count` in the activity's values. |
activities[].retry_delay_minutes required |
integer | |
activities[].risk_hold required |
string | Why a Run now would be held, including while the profile is warming up. Empty when the activity may run. A confirmation passes this hold except when `risk_hold_source` is `published`. |
activities[].risk_hold_source required |
string | Where the hold comes from. `published` is a limit the platform states, which a confirmation does not pass. Empty when `risk_hold` is empty. |
activities[].risk_profile required |
string | The preset the activity resolves to, from the tick's gate; empty for an activity without one profile and one platform, which names no single preset. |
activities[].risk_profile_effective required |
string | What the tick enforces: cautious while warming, whatever was chosen; empty with `risk_profile`. |
activities[].risk_profile_source required |
string | The layer that chose the preset (`schedule` when the activity's own value did, `builtin` when nothing chose one); empty with `risk_profile`. |
activities[].risk_profile_warming required |
string | `yes` while the profile or this computer is warming up; empty with `risk_profile` (an activity on every platform names no single preset). Values: |
activities[].run_at required |
string | |
activities[].status required |
string | The activity's stored status, such as pending, scheduled, running, retrying or failed. Never a lock. |
count required |
integer | |
missing_columns required |
array of string | |
ok required |
boolean | |
pause required |
one of PauseState, null | The pause every machine honours, or null. |
stop required |
one of StopState, null | The organization-wide stop in force, or null. |
POST
/cli/schedule/pause
Pause all scheduled processing: every tick keeps running but claims no activities until 'blowhorn schedule resume', and due times carry forward. A job already running finishes first. Pausing has three scopes and an activity is claimed only when none of them says paused: without a flag it is this machine only (a local file, never the store), --all is every machine sharing the store, and --activity N is one scheduled activity on every machine.
Pause all scheduled processing: every tick keeps running but claims no activities until 'blowhorn schedule resume', and due times carry forward. A job already running finishes first. Pausing has three scopes and an activity is claimed only when none of them says paused: without a flag it is this machine only (a local file, never the store), --all is every machine sharing the store, and --activity N is one scheduled activity on every machine.
Request body required
The flags and arguments of `blowhorn schedule pause`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity |
integer | Pause one scheduled activity instead of a whole machine: that job is claimed by nobody until 'blowhorn schedule resume --activity N', and every other activity keeps running everywhere. Distinct from setting the activity's Enabled to no, which is an editorial state with no reason and no intent to resume |
all_machines |
boolean | Pause every machine sharing this store, not just this one: no machine claims an activity until 'blowhorn schedule resume --all'. Recorded in the store with who paused it and why, so every machine reports the same reason. Without this flag the pause is this machine only Default: |
by |
string | Tool name recorded as the pause owner, as <tool>@<host> (default: cli) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule pause' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
reason |
string | Short note stored with the pause and shown by 'blowhorn schedule status' |
risk_profile |
string | Accepted by 'blowhorn schedule pause' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the pause `schedule pause` set: on this machine (the pause file), on every machine sharing the store (`--all`) or on one activity (`--activity`). Pausing an existing pause changes nothing and says so in `was_paused`. `--dry-run` prints the plan.
SchedulePauseDryRun
| Name | Type | Description |
|---|---|---|
activity |
integer | |
by required |
string | |
dry_run required |
any | |
ok required |
any | |
path |
string | The pause file (machine scope). |
reason required |
string | |
scope |
string | Values: |
SchedulePauseSet
| Name | Type | Description |
|---|---|---|
activity |
integer | |
by required |
string | null | |
corrupt |
boolean | |
error |
string | null | |
ok required |
any | |
path |
string | null | The pause file (machine scope). |
paused required |
any | |
reason required |
string | |
scope |
string | Values: |
since required |
string | null | |
was_paused required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SchedulePauseResponse
POST
/cli/schedule/resume
Resume scheduled processing after 'blowhorn schedule pause'; activities that fell due meanwhile run once, as soon as the service takes them up. The same three scopes: without a flag this machine's own pause is lifted, --all lifts the pause every machine sharing the store honours, and --activity N lifts one paused action. Lifting one scope leaves the other two as they were.
Resume scheduled processing after 'blowhorn schedule pause'; activities that fell due meanwhile run once, as soon as the service takes them up. The same three scopes: without a flag this machine's own pause is lifted, --all lifts the pause every machine sharing the store honours, and --activity N lifts one paused action. Lifting one scope leaves the other two as they were.
Request body required
The flags and arguments of `blowhorn schedule resume`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity |
integer | Let one held scheduled activity be claimed again, on every machine, the one 'blowhorn schedule pause --activity N' held. Every other scope stays as it is |
all_machines |
boolean | Lift the pause every machine sharing this store honours, the one 'blowhorn schedule pause --all' set. This machine's own pause and any paused action stay as they are Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule resume' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule resume' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints what `schedule resume` lifted - this machine's pause file, the all-machines pause (`--all`) or one activity's hold (`--activity`) - and how long it lasted. Resuming something not paused changes nothing and says so in `was_paused`. `--dry-run` prints the plan.
ScheduleResumeDryRun
| Name | Type | Description |
|---|---|---|
activity |
integer | |
dry_run required |
any | |
ok required |
any | |
path |
string | |
scope |
string | Values: |
ScheduleResumeLifted
| Name | Type | Description |
|---|---|---|
activity |
integer | |
ok required |
any | |
path |
string | The pause file (machine scope). |
paused required |
any | |
paused_by |
string | null | |
paused_for_s |
integer | null | |
paused_since |
string | null | |
reason |
string | Why it had been paused (store scopes). |
resumed_at |
string | |
scope |
string | Values: |
was_corrupt |
boolean | |
was_paused required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleResumeResponse
POST
/cli/schedule/status
Show whether processing is paused, the last pass's heartbeat, whether passes are still running, today's run counts from the run ledger, and the background service's state.
Show whether processing is paused, the last pass's heartbeat, whether passes are still running, today's run counts from the run ledger, and the background service's state.
Request body required
The flags and arguments of `blowhorn schedule status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the scheduler at a glance: whether this machine is paused, the last pass's heartbeat and whether passes are still running (`stalled` is true when none has run lately), today's run counts, the store as the last pass saw it (never a probe) and the background service's state. There is no countdown: the service runs when something is due, and re-scans at least every minute.
| Name | Type | Description |
|---|---|---|
heartbeat_path required |
string | null | |
last_tick required |
one of ScheduleHeartbeat, null | The last pass's heartbeat; null when no pass has run. |
log_dir required |
string | null | |
ok required |
any | |
pause required |
PauseState | A scheduler pause in the shape every scope shares. The machine scope (the pause file) carries `path`; the all-machines scope carries `scope: all` and a null `path`; a corrupt pause file adds `corrupt` and `error`. |
pause.by required |
string | null | |
pause.corrupt |
boolean | |
pause.error |
string | null | |
pause.path |
string | null | |
pause.paused required |
boolean | |
pause.reason required |
string | |
pause.scope |
string | Values: |
pause.since required |
string | null | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
paused required |
boolean | |
runs_dir required |
string | |
service |
any of ServiceState, Problem | The background service, on macOS. When its state cannot be read (any platform but macOS, or a launchd read that failed) the command still exits 0 and this is a Problem naming why. |
stalled required |
boolean | True when the last heartbeat is too old for a service that re-scans every minute, so no pass has run lately on this machine. |
store required |
object | |
store.available required |
boolean | null | Null when no pass has said either way. |
store.cause required |
string | null | The named cause beside `error` (schema_behind, tunnel_refused and so on); null when the last pass reached the store or no pass has said either way. |
store.error required |
string | null | |
store.ticks_skipped required |
integer | |
store.unavailable_since required |
string | null | |
today required |
one of null, object | Today's pass counts from the run ledger; null without a log directory. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleStatusResponse
| Name | Type | Description |
|---|---|---|
heartbeat_path required |
string | null | |
last_tick required |
one of ScheduleHeartbeat, null | The last pass's heartbeat; null when no pass has run. |
log_dir required |
string | null | |
ok required |
any | |
pause required |
PauseState | A scheduler pause in the shape every scope shares. The machine scope (the pause file) carries `path`; the all-machines scope carries `scope: all` and a null `path`; a corrupt pause file adds `corrupt` and `error`. |
pause.by required |
string | null | |
pause.corrupt |
boolean | |
pause.error |
string | null | |
pause.path |
string | null | |
pause.paused required |
boolean | |
pause.reason required |
string | |
pause.scope |
string | Values: |
pause.since required |
string | null | Local time, formatted as YYYY-MM-DD HH:MM:SS. |
paused required |
boolean | |
runs_dir required |
string | |
service |
any of ServiceState, Problem | The background service, on macOS. When its state cannot be read (any platform but macOS, or a launchd read that failed) the command still exits 0 and this is a Problem naming why. |
stalled required |
boolean | True when the last heartbeat is too old for a service that re-scans every minute, so no pass has run lately on this machine. |
store required |
object | |
store.available required |
boolean | null | Null when no pass has said either way. |
store.cause required |
string | null | The named cause beside `error` (schema_behind, tunnel_refused and so on); null when the last pass reached the store or no pass has said either way. |
store.error required |
string | null | |
store.ticks_skipped required |
integer | |
store.unavailable_since required |
string | null | |
today required |
one of null, object | Today's pass counts from the run ledger; null without a log directory. |
POST
/cli/schedule/tick
Run one scheduler polling pass against due scheduled actions, executing each through the same command routing the CLI uses and writing a per-job log under <log-dir>/scheduler/. While 'blowhorn schedule pause' is in effect the pass claims nothing unless --ignore-pause is passed; a real pass also records its heartbeat in <log-dir>/scheduler/last-tick.json and appends one tick record to the run ledger, <log-dir>/runs/<YYYY-MM>.jsonl. Browser visibility comes from each job: its own headless=/headed= params, then the environment, then Settings.
Run one scheduler polling pass against due scheduled actions, executing each through the same command routing the CLI uses and writing a per-job log under <log-dir>/scheduler/. While 'blowhorn schedule pause' is in effect the pass claims nothing unless --ignore-pause is passed; a real pass also records its heartbeat in <log-dir>/scheduler/last-tick.json and appends one tick record to the run ledger, <log-dir>/runs/<YYYY-MM>.jsonl. Browser visibility comes from each job: its own headless=/headed= params, then the environment, then Settings.
Request body required
The flags and arguments of `blowhorn schedule tick`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_pause |
boolean | Run even while 'blowhorn schedule pause' is in effect; without it a paused scheduler claims nothing Default: |
ignore_quiet_hours |
boolean | Run even inside the quiet hours (defaults.quiet_hours, or a profile's own Quiet Hours setting); without it a due activity inside the window is held until it ends Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
limit |
integer | Maximum number of due activities to process; 0 means no limit Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace handed to every action 'blowhorn schedule tick' runs in-process this pass -- the random delays between that action's browser or API steps: a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule tick prints with --json: the pass's counts and one entry per activity it handled (or would handle with --dry-run).
| Name | Type | Description |
|---|---|---|
cancelled required |
boolean | |
candidates required |
integer | |
checked required |
integer | |
heartbeat_path required |
string | null | |
missing_columns required |
array of string | |
ok required |
boolean | False when the schedule is missing required columns, or when this install is already running a tick. |
pause required |
object | null | The pause that held the pass, with its scope (machine, all or activity); null when not paused. |
paused required |
boolean | |
processed required |
integer | |
record_path required |
string | null | |
refused required |
string | null | The sentence Blowhorn prints when this install is already running a tick. The command then exits 1. Null on a pass that ran. |
results required |
array of object | One entry per activity the pass handled. Entries always carry activity, command, profile and status; a pass adds further keys for some statuses. |
results[].activity required |
integer | |
results[].command required |
string | |
results[].profile required |
string | |
results[].reason |
string | Why a preview blocked the activity, in words. Set when status is blocked, and on a reclaim skip. |
results[].status required |
string | For example would-run, blocked, held, invalid, done, failed or cancelled. |
skipped_cancelled required |
array | |
skipped_paused required |
array | Due activities left waiting because of a pause. |
stop required |
one of StopState, null | The organization-wide stop in force, or null. |
stopped required |
boolean | True when the organization-wide stop ended the pass at a safe point; a stopped pass is also cancelled, with the stop named in `stop`. |
store_error required |
string | null | Set on a degraded pass when the store was unreachable (the command then exits 3). |
store_unavailable_since required |
string | null | |
store_unavailable_ticks required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleTickResponse
| Name | Type | Description |
|---|---|---|
cancelled required |
boolean | |
candidates required |
integer | |
checked required |
integer | |
heartbeat_path required |
string | null | |
missing_columns required |
array of string | |
ok required |
boolean | False when the schedule is missing required columns, or when this install is already running a tick. |
pause required |
object | null | The pause that held the pass, with its scope (machine, all or activity); null when not paused. |
paused required |
boolean | |
processed required |
integer | |
record_path required |
string | null | |
refused required |
string | null | The sentence Blowhorn prints when this install is already running a tick. The command then exits 1. Null on a pass that ran. |
results required |
array of object | One entry per activity the pass handled. Entries always carry activity, command, profile and status; a pass adds further keys for some statuses. |
results[].activity required |
integer | |
results[].command required |
string | |
results[].profile required |
string | |
results[].reason |
string | Why a preview blocked the activity, in words. Set when status is blocked, and on a reclaim skip. |
results[].status required |
string | For example would-run, blocked, held, invalid, done, failed or cancelled. |
skipped_cancelled required |
array | |
skipped_paused required |
array | Due activities left waiting because of a pause. |
stop required |
one of StopState, null | The organization-wide stop in force, or null. |
stopped required |
boolean | True when the organization-wide stop ended the pass at a safe point; a stopped pass is also cancelled, with the stop named in `stop`. |
store_error required |
string | null | Set on a degraded pass when the store was unreachable (the command then exits 3). |
store_unavailable_since required |
string | null | |
store_unavailable_ticks required |
integer |
POST
/cli/schedule/trigger-now
Force a specific scheduled action to run immediately. While 'blowhorn schedule pause' is in effect the activity is not run and the command exits 1, unless --ignore-pause is passed. A Risk Profile hold, including while a profile is warming up, also keeps it from running unless you confirm at the prompt or pass --ignore-risk. A limit the platform publishes still applies. Browser visibility comes from the job: its own headless=/headed= params, then the environment, then Settings.
Force a specific scheduled action to run immediately. While 'blowhorn schedule pause' is in effect the activity is not run and the command exits 1, unless --ignore-pause is passed. A Risk Profile hold, including while a profile is warming up, also keeps it from running unless you confirm at the prompt or pass --ignore-risk. A limit the platform publishes still applies. Browser visibility comes from the job: its own headless=/headed= params, then the environment, then Settings.
Request body required
The flags and arguments of `blowhorn schedule trigger-now`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number to execute |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
ignore_pause |
boolean | Run even while 'blowhorn schedule pause' is in effect; without it a paused scheduler claims nothing Default: |
ignore_quiet_hours |
boolean | Run even inside the quiet hours (defaults.quiet_hours, or a profile's own Quiet Hours setting); without it a due activity inside the window is held until it ends Default: |
ignore_risk |
boolean | Run this once even if your Risk Profile would hold it (a daily cap, quiet hours, outreach turned off or a pause after a sign-in challenge). Only this run; your Risk Profile stays as it is. A limit the platform itself publishes still applies Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace handed to every action 'blowhorn schedule trigger-now' runs in-process this pass -- the random delays between that action's browser or API steps: a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule trigger-now prints with --json: the pass's counts and one entry per activity it handled (or would handle with --dry-run).
| Name | Type | Description |
|---|---|---|
cancelled required |
boolean | |
candidates required |
integer | |
checked required |
integer | |
heartbeat_path required |
string | null | |
missing_columns required |
array of string | |
ok required |
boolean | False when the schedule is missing required columns, or when this install is already running a tick. |
pause required |
object | null | The pause that held the pass, with its scope (machine, all or activity); null when not paused. |
paused required |
boolean | |
processed required |
integer | |
record_path required |
string | null | |
refused required |
string | null | The sentence Blowhorn prints when this install is already running a tick. The command then exits 1. Null on a pass that ran. |
results required |
array of object | One entry per activity the pass handled. Entries always carry activity, command, profile and status; a pass adds further keys for some statuses. |
results[].activity required |
integer | |
results[].command required |
string | |
results[].profile required |
string | |
results[].status required |
string | 'For example would-run, held, invalid, done, failed, cancelled.' |
skipped_cancelled required |
array | |
skipped_paused required |
array | Due activities left waiting because of a pause. |
store_error required |
string | null | Set on a degraded pass when the store was unreachable (the command then exits 3). |
store_unavailable_since required |
string | null | |
store_unavailable_ticks required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleTriggerNowResponse
| Name | Type | Description |
|---|---|---|
cancelled required |
boolean | |
candidates required |
integer | |
checked required |
integer | |
heartbeat_path required |
string | null | |
missing_columns required |
array of string | |
ok required |
boolean | False when the schedule is missing required columns, or when this install is already running a tick. |
pause required |
object | null | The pause that held the pass, with its scope (machine, all or activity); null when not paused. |
paused required |
boolean | |
processed required |
integer | |
record_path required |
string | null | |
refused required |
string | null | The sentence Blowhorn prints when this install is already running a tick. The command then exits 1. Null on a pass that ran. |
results required |
array of object | One entry per activity the pass handled. Entries always carry activity, command, profile and status; a pass adds further keys for some statuses. |
results[].activity required |
integer | |
results[].command required |
string | |
results[].profile required |
string | |
results[].status required |
string | 'For example would-run, held, invalid, done, failed, cancelled.' |
skipped_cancelled required |
array | |
skipped_paused required |
array | Due activities left waiting because of a pause. |
store_error required |
string | null | Set on a degraded pass when the store was unreachable (the command then exits 3). |
store_unavailable_since required |
string | null | |
store_unavailable_ticks required |
integer |
POST
/cli/schedule/upsert
Create or update a scheduled action from a JSON payload. Giving a completed or failed activity a new Next Run At, or a Run At that changes, schedules it again in place with a fresh retry budget (Status scheduled, Retry Count 0; its last run and last error are kept); raising Max Retries alone does not.
Create or update a scheduled action from a JSON payload. Giving a completed or failed activity a new Next Run At, or a Run At that changes, schedules it again in place with a fresh retry budget (Status scheduled, Retry Count 0; its last run and last error are kept); raising Max Retries alone does not.
Request body required
The flags and arguments of `blowhorn schedule upsert`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity |
integer | Activity number to update; omit to create a new activity |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
expected_fingerprint |
string | Reject the write if the current activity fingerprint no longer matches |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule upsert' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
payload required |
string | JSON object representing scheduled action fields |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule upsert' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the schedule activity that was created or updated, with the values and fingerprint it now has: the activity as written, including what the write decided on its own, such as a Next Run At that followed a changed Run At, or a completed or failed activity given a new time and scheduled again with Status scheduled and Retry Count 0, or an activity stored on all for a command that runs in one place (accept on all) stored as that place when the write names no platform.
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
created required |
boolean | |
fingerprint required |
string | |
ok required |
any | |
platform_rewritten |
object | Present only when the write named no platform and the activity's stored platform was replaced by the one place its command runs (accept stored on all becomes linkedin). |
platform_rewritten.from required |
string | |
platform_rewritten.to required |
string | |
values required |
ScheduleValues | A schedule row's columns, each a string (empty when unset): the command and its params as `key=value;key=value`, when it runs and how it repeats, its retry budget, its status and the lock a running machine holds. |
values.Command required |
string | |
values.Driver Mode required |
string | |
values.Enabled required |
string | |
values.Last Error required |
string | |
values.Last Log Path required |
string | |
values.Last Run At required |
string | |
values.Locked At required |
string | |
values.Locked By required |
string | |
values.Max Retries required |
string | The retry budget for each arming: how many times a failed run is retried before the row is failed. Raising it alone does not schedule a failed row again. |
values.Next Run At required |
string | When the row is next due, in local time. Empty on a completed or failed row means never due again. An update that gives a completed or failed row a Next Run At, or changes its Run At, schedules it again in the same write: Status scheduled and Retry Count 0, with Last Run At, Last Error and Last Log Path kept, unless the update sets Status itself. |
values.Params required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Recurrence required |
string | |
values.Retry Count required |
string | Failed attempts since the row was last armed. A success sets it to 0, and so does an update that schedules a completed or failed row again. |
values.Retry Delay Minutes required |
string | |
values.Risk Profile required |
string | The activity's own Risk Profile (#1009): `cautious`, `standard` or `brisk`, or empty to inherit the preset its profile and platform resolve to. |
values.Run At required |
string | |
values.Status required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleUpsertResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
created required |
boolean | |
fingerprint required |
string | |
ok required |
any | |
platform_rewritten |
object | Present only when the write named no platform and the activity's stored platform was replaced by the one place its command runs (accept stored on all becomes linkedin). |
platform_rewritten.from required |
string | |
platform_rewritten.to required |
string | |
values required |
ScheduleValues | A schedule row's columns, each a string (empty when unset): the command and its params as `key=value;key=value`, when it runs and how it repeats, its retry budget, its status and the lock a running machine holds. |
values.Command required |
string | |
values.Driver Mode required |
string | |
values.Enabled required |
string | |
values.Last Error required |
string | |
values.Last Log Path required |
string | |
values.Last Run At required |
string | |
values.Locked At required |
string | |
values.Locked By required |
string | |
values.Max Retries required |
string | The retry budget for each arming: how many times a failed run is retried before the row is failed. Raising it alone does not schedule a failed row again. |
values.Next Run At required |
string | When the row is next due, in local time. Empty on a completed or failed row means never due again. An update that gives a completed or failed row a Next Run At, or changes its Run At, schedules it again in the same write: Status scheduled and Retry Count 0, with Last Run At, Last Error and Last Log Path kept, unless the update sets Status itself. |
values.Params required |
string | |
values.Platform required |
string | |
values.Profile required |
string | |
values.Recurrence required |
string | |
values.Retry Count required |
string | Failed attempts since the row was last armed. A success sets it to 0, and so does an update that schedules a completed or failed row again. |
values.Retry Delay Minutes required |
string | |
values.Risk Profile required |
string | The activity's own Risk Profile (#1009): `cautious`, `standard` or `brisk`, or empty to inherit the preset its profile and platform resolve to. |
values.Run At required |
string | |
values.Status required |
string |
POST
/cli/schedule/validate
Validate the schedule's actions against the supported schema.
Validate the schedule's actions against the supported schema.
Request body required
The flags and arguments of `blowhorn schedule validate`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule validate' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule validate' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the activities whose command, profile or params fail validation. `ok` is false when any activity is invalid or a required column is missing; the same document is printed then.
| Name | Type | Description |
|---|---|---|
invalid_activities required |
array of object | |
invalid_activities[].activity required |
integer | |
invalid_activities[].command required |
string | |
invalid_activities[].errors required |
array of string | |
invalid_activities[].profile required |
string | |
missing_columns required |
array of string | |
ok required |
boolean | |
total_activities required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleValidateResponse
| Name | Type | Description |
|---|---|---|
invalid_activities required |
array of object | |
invalid_activities[].activity required |
integer | |
invalid_activities[].command required |
string | |
invalid_activities[].errors required |
array of string | |
invalid_activities[].profile required |
string | |
missing_columns required |
array of string | |
ok required |
boolean | |
total_activities required |
integer |
POST
/cli/schedule/why
Explain why a scheduled activity has or has not run on this machine, in the order the scheduler decides it: whether the activity is enabled and valid, whether anything is pausing it (the activity itself, this machine, or every machine), whether it is due, whether another machine holds the claim and when that lease expires, whether this machine can reach the store and when it last ticked, and what the run history says happened the last time it ran. Reads only -- it claims nothing, runs nothing and changes nothing, so it is safe against an activity that is mid-flight.
Explain why a scheduled activity has or has not run on this machine, in the order the scheduler decides it: whether the activity is enabled and valid, whether anything is pausing it (the activity itself, this machine, or every machine), whether it is due, whether another machine holds the claim and when that lease expires, whether this machine can reach the store and when it last ticked, and what the run history says happened the last time it ran. Reads only -- it claims nothing, runs nothing and changes nothing, so it is safe against an activity that is mid-flight.
Request body required
The flags and arguments of `blowhorn schedule why`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | The scheduled action's number to explain (the same number 'blowhorn schedule list' prints) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn schedule why' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform filter for 'blowhorn schedule': it selects which scheduled activities this command reads or acts on, and is not a platform blowhorn posts to directly. One of: bluesky, github, hn, layer5, lfx, linkedin, meshery, reddit, slack, x, all ('all' applies no filter, so every scheduled activity is included) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn schedule why' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What schedule why prints with --json: the diagnosis ladder for one schedule activity (enabled, valid, paused, quiet hours, due, claimed, this machine, last run) and a one-sentence verdict. Read-only.
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
claim required |
object | The activity's lease - claimed, by, at, lease_expires_at, stale, this_machine. |
command required |
string | |
due required |
boolean | Whether the activity's time has come, read from the same instant schedule list prints as next_run. The claim and the pauses are rungs of their own. |
due_at required |
string | null | That instant in local time. Empty when the activity is never due again (a completed once-only activity, or a completed or failed activity with no Next Run At) or has no Run At. |
due_for_s required |
integer | null | |
enabled required |
boolean | |
in_quiet_hours required |
boolean | Whether the activity is inside its quiet hours now. |
last_run required |
object | null | |
not_due_reason required |
string | null | |
ok required |
boolean | |
pause required |
object | null | The pause holding the activity, with its scope; null when none. |
pause_scopes required |
object or array | null | Each pause scope (machine, all, activity) and whether it holds the activity. |
paused required |
boolean | |
platform required |
string | |
profile required |
string | |
quiet_hours required |
object | null | The quiet hours in force for the activity's profile; null when no window is set anywhere for it. |
quiet_hours.held required |
boolean | Whether the activity is inside the window now. |
quiet_hours.source required |
string | Whether the window came from the profile's own Quiet Hours setting or the defaults. Values: |
quiet_hours.tz required |
string | The zone the window is read in: an IANA name, or this computer's zone. |
quiet_hours.until required |
string | null | When the window ends, as an instant in its zone; null when not held. |
quiet_hours.until_clock required |
string | null | That ending as the zone's wall clock, `HH:MM`; null when not held. |
quiet_hours.window required |
string | The window, `HH:MM-HH:MM` like `22:00-07:00`. |
risk required |
object | The activity's Risk Profile (#1009): its own setting (empty when it inherits), the preset it effectively runs, and where that preset came from (`schedule` for its own setting). |
risk.effective required |
string | |
risk.profile required |
string | |
risk.source required |
string | |
status required |
string | |
this_machine required |
object | This machine's host, last tick (the heartbeat's name for its last pass), store state, and whether passes are still running (passing). |
valid required |
boolean | |
validation_errors required |
array of string | |
verdict required |
object | |
verdict.rung required |
string | The first rung that stopped the activity. Values: |
verdict.sentence required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ScheduleWhyResponse
| Name | Type | Description |
|---|---|---|
activity required |
integer | |
claim required |
object | The activity's lease - claimed, by, at, lease_expires_at, stale, this_machine. |
command required |
string | |
due required |
boolean | Whether the activity's time has come, read from the same instant schedule list prints as next_run. The claim and the pauses are rungs of their own. |
due_at required |
string | null | That instant in local time. Empty when the activity is never due again (a completed once-only activity, or a completed or failed activity with no Next Run At) or has no Run At. |
due_for_s required |
integer | null | |
enabled required |
boolean | |
in_quiet_hours required |
boolean | Whether the activity is inside its quiet hours now. |
last_run required |
object | null | |
not_due_reason required |
string | null | |
ok required |
boolean | |
pause required |
object | null | The pause holding the activity, with its scope; null when none. |
pause_scopes required |
object or array | null | Each pause scope (machine, all, activity) and whether it holds the activity. |
paused required |
boolean | |
platform required |
string | |
profile required |
string | |
quiet_hours required |
object | null | The quiet hours in force for the activity's profile; null when no window is set anywhere for it. |
quiet_hours.held required |
boolean | Whether the activity is inside the window now. |
quiet_hours.source required |
string | Whether the window came from the profile's own Quiet Hours setting or the defaults. Values: |
quiet_hours.tz required |
string | The zone the window is read in: an IANA name, or this computer's zone. |
quiet_hours.until required |
string | null | When the window ends, as an instant in its zone; null when not held. |
quiet_hours.until_clock required |
string | null | That ending as the zone's wall clock, `HH:MM`; null when not held. |
quiet_hours.window required |
string | The window, `HH:MM-HH:MM` like `22:00-07:00`. |
risk required |
object | The activity's Risk Profile (#1009): its own setting (empty when it inherits), the preset it effectively runs, and where that preset came from (`schedule` for its own setting). |
risk.effective required |
string | |
risk.profile required |
string | |
risk.source required |
string | |
status required |
string | |
this_machine required |
object | This machine's host, last tick (the heartbeat's name for its last pass), store state, and whether passes are still running (passing). |
valid required |
boolean | |
validation_errors required |
array of string | |
verdict required |
object | |
verdict.rung required |
string | The first rung that stopped the activity. Values: |
verdict.sentence required |
string |
POST
/cli/stop
Stop everything, right now: on this machine, or on every machine sharing the store with --all. A stop does what a pause does -- nothing new is claimed anywhere it covers -- and one thing more: work already running stops at its next safe point, never mid-post, so a pass mid-flight on another machine, or a manual or agent run there, ends the way a cancelled one does and its work returns to the queue. 'blowhorn stop --resume' lifts a stop, and lifts only the pause the stop itself set: a pause that was already in place when the stop landed stays in place.
Stop everything, right now: on this machine, or on every machine sharing the store with --all. A stop does what a pause does -- nothing new is claimed anywhere it covers -- and one thing more: work already running stops at its next safe point, never mid-post, so a pass mid-flight on another machine, or a manual or agent run there, ends the way a cancelled one does and its work returns to the queue. 'blowhorn stop --resume' lifts a stop, and lifts only the pause the stop itself set: a pause that was already in place when the stop landed stays in place.
Request body required
The flags and arguments of `blowhorn stop`, one property per parameter.
| Name | Type | Description |
|---|---|---|
all_machines |
boolean | Stop every machine sharing this store, not just this one: no machine claims an activity, and work already running on any of them stops at its next safe point. Recorded in the store with who stopped it and why, so every machine reports the same reason. Without this flag the stop is this machine only Default: |
by |
string | Tool name recorded as who stopped, as <tool>@<host> (default: cli) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn stop' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn stop': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
reason |
string | Short note stored with the stop and shown wherever the stop is reported |
resume |
boolean | Lift a stop instead of setting one: this machine's, or every machine's with --all. Lifting a stop also lifts the pause the stop itself set, and no other pause Default: |
risk_profile |
string | Accepted by 'blowhorn stop' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What `blowhorn stop` prints with `--json`: a stop set on this machine or on every machine sharing the store (`--all`), or a stop lifted with `--resume`. Setting a stop that already stands changes nothing and says so in `was_stopped`. `flag_carried` is false when the backend cannot carry the stop record (your Blowhorn account, until the wire construct grows a member for it): the organization-wide pause the stop raises still lands, and `was_stopped` is then null, because no record exists to ask. `pass_cancelled` is what asking this machine's background service to stop its pass answered: true when a pass is stopping, false when none is in flight, null when no service answers here. `--dry-run` prints the plan.
StopSet
| Name | Type | Description |
|---|---|---|
by required |
string | null | |
flag_carried required |
boolean | |
ok required |
any | |
pass_cancelled required |
boolean | null | |
path |
string | The local stop record (machine scope). |
raised_pause required |
boolean | |
reason required |
string | |
scope required |
string | Values: |
since required |
string | null | |
stopped required |
any | |
was_stopped required |
boolean | null |
StopResumed
| Name | Type | Description |
|---|---|---|
flag_carried |
boolean | |
ok required |
any | |
path |
string | The local stop record (machine scope). |
pause_lifted required |
boolean | |
raised_pause |
boolean | |
reason |
string | |
resumed_at |
string | |
scope required |
string | Values: |
stopped required |
any | |
stopped_by |
string | null | |
stopped_for_s |
integer | null | |
stopped_since |
string | null | |
was_stopped required |
boolean | null |
StopDryRun
| Name | Type | Description |
|---|---|---|
by |
string | |
dry_run required |
any | |
ok required |
any | |
reason |
string | |
resume |
any | |
scope required |
string | Values: |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
StopResponse
Risk
POST
/cli/risk/grid
Show the Risk Profile of every profile on every platform at once: the choice for everyone, each platform's choice for every profile (and whether it is kept for every computer or on this computer only), each profile's own choice, and for each profile on each platform the preset in effect, the level that chose it, and whether it is still warming up and running cautious. Reads the store once.
Show the Risk Profile of every profile on every platform at once: the choice for everyone, each platform's choice for every profile (and whether it is kept for every computer or on this computer only), each profile's own choice, and for each profile on each platform the preset in effect, the level that chose it, and whether it is still warming up and running cautious. Reads the store once.
Request body required
The flags and arguments of `blowhorn risk grid`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn risk grid' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn risk'. One of: bluesky, github, hn, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn risk grid' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The Risk Profile of every profile on every platform in one answer - the choice for everyone, each platform's choice for every profile, each profile's own choice, and each cell's preset with the level that chose it - read from the store once. What the desktop Safety page draws its grid from.
| Name | Type | Description |
|---|---|---|
customized_dials required |
array of string | The dials this computer's file or environment set outside the preset for everyone; any makes the profile custom. |
everyone required |
object | The choice for every profile on every platform, the slider's own answer. |
everyone.risk_profile required |
string | Values: |
everyone.source required |
string | The environment variable, your account's choice, this computer's .blowhorn.yaml, or the built-in standard. Values: |
ok required |
boolean | |
platform_defaults required |
object | Whether the organization's choices for each platform could be read. |
platform_defaults.error required |
string | null | Why they could not be read; null when they were. Until they can be, every cell resolves as the risk gate does, with each platform read as cautious. |
platform_defaults.shared required |
boolean | |
platforms required |
array of object | One entry per platform a Risk Profile can be chosen for, in the order the grid draws them. |
platforms[].platform required |
string | Values: |
platforms[].risk_profile required |
string | null | The platform's choice for every profile; null when it has none and its profiles inherit. Values: |
platforms[].scope required |
string | null | shared when every computer reads it, this_computer when it is kept in this computer's .blowhorn.yaml only. Values: |
platforms[].set_at required |
string | null | When a shared choice was set, ISO 8601. |
platforms[].set_by required |
string | null | Who set a shared choice, as the store recorded it. |
platforms[].source required |
string | null | Values: |
profiles required |
array of object | |
profiles[].cells required |
array of object | |
profiles[].cells[].effective required |
string | What a scheduled run uses now - cautious while warming up, else the chosen preset. Values: |
profiles[].cells[].level required |
string | Which level chose it: set for this cell, inherited from the platform's or the profile's choice (the more careful one when both choose), from everyone, or built in. Values: |
profiles[].cells[].platform required |
string | Values: |
profiles[].cells[].risk_profile required |
string | The preset chosen for this profile on this platform. Values: |
profiles[].cells[].set_here required |
boolean | True when the choice was made for this profile on this platform. |
profiles[].cells[].source required |
string | |
profiles[].cells[].warming required |
boolean | |
profiles[].name required |
string | The profile's display name when it has one, else its name. |
profiles[].profile required |
string | The profile's name, lowercase. |
profiles[].risk_profile required |
string | null | The profile's own choice for every platform; null when it has none. Values: |
profiles[].source required |
string | null | Values: |
profiles[].warming required |
boolean | True while any of its platforms is still warming up. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
RiskGridResponse
| Name | Type | Description |
|---|---|---|
customized_dials required |
array of string | The dials this computer's file or environment set outside the preset for everyone; any makes the profile custom. |
everyone required |
object | The choice for every profile on every platform, the slider's own answer. |
everyone.risk_profile required |
string | Values: |
everyone.source required |
string | The environment variable, your account's choice, this computer's .blowhorn.yaml, or the built-in standard. Values: |
ok required |
boolean | |
platform_defaults required |
object | Whether the organization's choices for each platform could be read. |
platform_defaults.error required |
string | null | Why they could not be read; null when they were. Until they can be, every cell resolves as the risk gate does, with each platform read as cautious. |
platform_defaults.shared required |
boolean | |
platforms required |
array of object | One entry per platform a Risk Profile can be chosen for, in the order the grid draws them. |
platforms[].platform required |
string | Values: |
platforms[].risk_profile required |
string | null | The platform's choice for every profile; null when it has none and its profiles inherit. Values: |
platforms[].scope required |
string | null | shared when every computer reads it, this_computer when it is kept in this computer's .blowhorn.yaml only. Values: |
platforms[].set_at required |
string | null | When a shared choice was set, ISO 8601. |
platforms[].set_by required |
string | null | Who set a shared choice, as the store recorded it. |
platforms[].source required |
string | null | Values: |
profiles required |
array of object | |
profiles[].cells required |
array of object | |
profiles[].cells[].effective required |
string | What a scheduled run uses now - cautious while warming up, else the chosen preset. Values: |
profiles[].cells[].level required |
string | Which level chose it: set for this cell, inherited from the platform's or the profile's choice (the more careful one when both choose), from everyone, or built in. Values: |
profiles[].cells[].platform required |
string | Values: |
profiles[].cells[].risk_profile required |
string | The preset chosen for this profile on this platform. Values: |
profiles[].cells[].set_here required |
boolean | True when the choice was made for this profile on this platform. |
profiles[].cells[].source required |
string | |
profiles[].cells[].warming required |
boolean | |
profiles[].name required |
string | The profile's display name when it has one, else its name. |
profiles[].profile required |
string | The profile's name, lowercase. |
profiles[].risk_profile required |
string | null | The profile's own choice for every platform; null when it has none. Values: |
profiles[].source required |
string | null | Values: |
profiles[].warming required |
boolean | True while any of its platforms is still warming up. |
POST
/cli/risk/set
Choose a Risk Profile: cautious, standard (the built-in default) or brisk. With no --profile and no --platform it is written to .blowhorn.yaml (defaults.risk_profile). With --platform alone it is the choice for every profile on that platform, saved for your whole organization so every computer reads it; when it cannot be saved there (your account does not keep it yet, you are not an organization admin, or the store cannot be reached) it is kept on this computer in .blowhorn.yaml (defaults.risk_profile_by_platform) and the command says so. With --profile it is stored on that profile in the store (Risk Profile, or Risk Profile By Platform with --platform). When a platform's choice and a profile's own choice differ, the more careful one applies. default clears the choice at that scope (for a platform, on this computer too). --dry-run says what would change and writes nothing.
Choose a Risk Profile: cautious, standard (the built-in default) or brisk. With no --profile and no --platform it is written to .blowhorn.yaml (defaults.risk_profile). With --platform alone it is the choice for every profile on that platform, saved for your whole organization so every computer reads it; when it cannot be saved there (your account does not keep it yet, you are not an organization admin, or the store cannot be reached) it is kept on this computer in .blowhorn.yaml (defaults.risk_profile_by_platform) and the command says so. With --profile it is stored on that profile in the store (Risk Profile, or Risk Profile By Platform with --platform). When a platform's choice and a profile's own choice differ, the more careful one applies. default clears the choice at that scope (for a platform, on this computer too). --dry-run says what would change and writes nothing.
Request body required
The flags and arguments of `blowhorn risk set`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn risk set' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn risk'. One of: bluesky, github, hn, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
preset required |
string | cautious, standard or brisk; default clears the choice at this scope |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn risk set' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Where the risk profile was written (or, with --dry-run, would be) and the value stored. Clearing a choice with "default" stores an empty value.
| Name | Type | Description |
|---|---|---|
cleared_this_computer required |
boolean | Whether clearing a platform's shared choice also cleared this computer's own choice for that platform in .blowhorn.yaml, so the platform inherits again everywhere. |
dry_run required |
boolean | |
fallback_reason required |
string | null | Why a platform's choice for every profile could not be saved for the whole organization and was kept on this computer instead; null when it was saved where it was asked for. |
key required |
string | The config key or store profile attribute written; organization: <platform> for a platform's choice saved for the whole organization. |
ok required |
boolean | |
platform required |
string | null | |
profile required |
string | null | |
risk_profile required |
string | null | The preset set, null when the choice was cleared. Values: |
scope required |
string | Where the choice lives: everyone (this computer's .blowhorn.yaml, every platform), shared (one platform for every profile, saved for the whole organization so every computer reads it), this_computer (one platform for every profile, kept in this computer's .blowhorn.yaml because the shared choice could not be saved), or profile (a profile's own choice in the store). Values: |
value required |
string | null | The value written. A per-platform store attribute holds a comma-joined platform:preset list; null or empty when cleared. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
RiskSetResponse
| Name | Type | Description |
|---|---|---|
cleared_this_computer required |
boolean | Whether clearing a platform's shared choice also cleared this computer's own choice for that platform in .blowhorn.yaml, so the platform inherits again everywhere. |
dry_run required |
boolean | |
fallback_reason required |
string | null | Why a platform's choice for every profile could not be saved for the whole organization and was kept on this computer instead; null when it was saved where it was asked for. |
key required |
string | The config key or store profile attribute written; organization: <platform> for a platform's choice saved for the whole organization. |
ok required |
boolean | |
platform required |
string | null | |
profile required |
string | null | |
risk_profile required |
string | null | The preset set, null when the choice was cleared. Values: |
scope required |
string | Where the choice lives: everyone (this computer's .blowhorn.yaml, every platform), shared (one platform for every profile, saved for the whole organization so every computer reads it), this_computer (one platform for every profile, kept in this computer's .blowhorn.yaml because the shared choice could not be saved), or profile (a profile's own choice in the store). Values: |
value required |
string | null | The value written. A per-platform store attribute holds a comma-joined platform:preset list; null or empty when cleared. |
POST
/cli/risk/show
Show the Risk Profile in effect for each platform: the one chosen, the level and the source that chose it (--profile reads that profile's choices from the store; every run also reads the organization's choice for each platform, .blowhorn.yaml, BLOWHORN_RISK_PROFILE, your account's choice and the built-in standard) and what the preset sets. When a platform's choice and a profile's own choice differ, the more careful one applies. A new profile or computer still runs cautious until it is warm, whatever is chosen.
Show the Risk Profile in effect for each platform: the one chosen, the level and the source that chose it (--profile reads that profile's choices from the store; every run also reads the organization's choice for each platform, .blowhorn.yaml, BLOWHORN_RISK_PROFILE, your account's choice and the built-in standard) and what the preset sets. When a platform's choice and a profile's own choice differ, the more careful one applies. A new profile or computer still runs cautious until it is warm, whatever is chosen.
Request body required
The flags and arguments of `blowhorn risk show`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn risk show' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn risk'. One of: bluesky, github, hn, linkedin, reddit, slack, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn risk show' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The effective risk profile for each platform (or the one named), with the layer that chose it, the dials it sets, and the dials set outside it.
| Name | Type | Description |
|---|---|---|
customized_dials required |
array of string | The dials this machine's file or environment set outside the chosen preset, to a value the preset would not have set; a profile with any is a custom profile. Machine-wide, the same for every platform row. |
ok required |
boolean | |
platform_defaults required |
object | Whether the organization's choices for each platform could be read for this answer. |
platform_defaults.error required |
string | null | Why the organization's platform choices could not be read; null when they were. Until they can be read, scheduled runs treat each platform as cautious. |
platform_defaults.shared required |
boolean | True when they were read (or no store is set up, so there are none); false when the read failed. |
platforms required |
array of object | |
platforms[].concurrency required |
object | How much may run at once under this platform's preset, each value with the layer that supplied it (the preset itself, the file, or the environment). |
platforms[].concurrency.max_runs required |
integer | null | Most runs at once across everything; null for no global limit. |
platforms[].concurrency.max_runs_source required |
string | Which layer supplied max_runs (risk_profile, config or env). |
platforms[].concurrency.per_platform required |
string | platform - one profile's run at a time on a platform; profile - runs for different profiles may share a platform. Values: |
platforms[].concurrency.per_platform_source required |
string | Which layer supplied per_platform (risk_profile, config or env). |
platforms[].level required |
string | The scope of the layer that chose it, most specific first: one run, one activity, one profile on one platform (cell), the platform's choice for every profile, the profile's own choice for every platform, everyone, or the built-in standard. When a platform and a profile both choose, the more careful one is the one named. Values: |
platforms[].outreach required |
string | Whether outreach (connect, invite, follow) may run - off, established or on. |
platforms[].pace required |
string | The preset's pace (slow, normal or fast). |
platforms[].platform required |
string | Values: |
platforms[].platform_choice required |
string | null | What the platform's own choice for every profile says, whether or not it won; null when the platform has none. Values: |
platforms[].platform_source required |
string | null | Where the platform's own choice comes from (store:platform for the organization's, config:platform for this computer's file); null when there is none. |
platforms[].profile_choice required |
string | null | What the profile's own choice for every platform says, whether or not it won; null for no profile or no choice. Values: |
platforms[].profile_source required |
string | null | Where the profile's own choice comes from (store:profile or config:profile); null when there is none. |
platforms[].quiet_hours required |
array of integer | null | [start_hour, end_hour] local time during which nothing runs; null when there are none. |
platforms[].risk_profile required |
string | Values: |
platforms[].source required |
string | Which layer chose the profile (for example a command-line flag, the store, the organization's choice for the platform, the config file or the built-in default). |
platforms[].warmup_days required |
integer | |
profile required |
string | null | The Blowhorn profile asked about, null for the machine-wide answer. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
RiskShowResponse
| Name | Type | Description |
|---|---|---|
customized_dials required |
array of string | The dials this machine's file or environment set outside the chosen preset, to a value the preset would not have set; a profile with any is a custom profile. Machine-wide, the same for every platform row. |
ok required |
boolean | |
platform_defaults required |
object | Whether the organization's choices for each platform could be read for this answer. |
platform_defaults.error required |
string | null | Why the organization's platform choices could not be read; null when they were. Until they can be read, scheduled runs treat each platform as cautious. |
platform_defaults.shared required |
boolean | True when they were read (or no store is set up, so there are none); false when the read failed. |
platforms required |
array of object | |
platforms[].concurrency required |
object | How much may run at once under this platform's preset, each value with the layer that supplied it (the preset itself, the file, or the environment). |
platforms[].concurrency.max_runs required |
integer | null | Most runs at once across everything; null for no global limit. |
platforms[].concurrency.max_runs_source required |
string | Which layer supplied max_runs (risk_profile, config or env). |
platforms[].concurrency.per_platform required |
string | platform - one profile's run at a time on a platform; profile - runs for different profiles may share a platform. Values: |
platforms[].concurrency.per_platform_source required |
string | Which layer supplied per_platform (risk_profile, config or env). |
platforms[].level required |
string | The scope of the layer that chose it, most specific first: one run, one activity, one profile on one platform (cell), the platform's choice for every profile, the profile's own choice for every platform, everyone, or the built-in standard. When a platform and a profile both choose, the more careful one is the one named. Values: |
platforms[].outreach required |
string | Whether outreach (connect, invite, follow) may run - off, established or on. |
platforms[].pace required |
string | The preset's pace (slow, normal or fast). |
platforms[].platform required |
string | Values: |
platforms[].platform_choice required |
string | null | What the platform's own choice for every profile says, whether or not it won; null when the platform has none. Values: |
platforms[].platform_source required |
string | null | Where the platform's own choice comes from (store:platform for the organization's, config:platform for this computer's file); null when there is none. |
platforms[].profile_choice required |
string | null | What the profile's own choice for every platform says, whether or not it won; null for no profile or no choice. Values: |
platforms[].profile_source required |
string | null | Where the profile's own choice comes from (store:profile or config:profile); null when there is none. |
platforms[].quiet_hours required |
array of integer | null | [start_hour, end_hour] local time during which nothing runs; null when there are none. |
platforms[].risk_profile required |
string | Values: |
platforms[].source required |
string | Which layer chose the profile (for example a command-line flag, the store, the organization's choice for the platform, the config file or the built-in default). |
platforms[].warmup_days required |
integer | |
profile required |
string | null | The Blowhorn profile asked about, null for the machine-wide answer. |
Config
POST
/cli/config/get
Print one option's effective value and which layer supplied it (a flag, the environment, the file or the built-in). With --json, the same row 'config show --json' carries for the option.
Print one option's effective value and which layer supplied it (a flag, the environment, the file or the built-in). With --json, the same row 'config show --json' carries for the option.
Request body required
The flags and arguments of `blowhorn config get`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the option report as machine-readable JSON Default: |
key required |
string | Dotted option key, defaults.<name>, as 'blowhorn config show --json' lists them |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn config get' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn config': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn config get' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints one option as `config show` lists it, plus the path of the `.blowhorn.yaml` that holds it.
| Name | Type | Description |
|---|---|---|
accepts_number required |
boolean | |
builtin required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
by_platform |
array of object | The `pace` row only: the effective pace for every platform and the layer that supplied it (`pace_by_platform`, `defaults.pace`, `env`, `risk_profile` or `builtin`). |
by_platform[].pace required |
string | |
by_platform[].platform required |
string | |
by_platform[].source required |
string | |
choices required |
array of string | The accepted values of an `enum` option; empty otherwise. |
cli_flag required |
string | The flag that sets it for one run, or an empty string when it has none. |
document |
string | Which document holds the option. Values: |
document_path required |
string | The path of that document (`config get` only). |
editable required |
boolean | Whether `config set` can store it. |
effective required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
env required |
string | The environment variable that overrides it. |
env_value required |
one of string, null, object | The raw override string when the variable is set, otherwise null. |
hint required |
string | |
key required |
string | The option's short name, also its `env_overrides` key. |
kind required |
string | Values: |
label required |
string | |
masked |
boolean | True for a secret option, whose values are {set: boolean}. |
ok required |
any | |
path required |
string | null | The dotted path in the document (`defaults.headless`), or null for an option with no stored form. |
source required |
string | The layer that supplied effective, such as config, env, builtin or risk_profile. |
stored required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ConfigGetResponse
| Name | Type | Description |
|---|---|---|
accepts_number required |
boolean | |
builtin required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
by_platform |
array of object | The `pace` row only: the effective pace for every platform and the layer that supplied it (`pace_by_platform`, `defaults.pace`, `env`, `risk_profile` or `builtin`). |
by_platform[].pace required |
string | |
by_platform[].platform required |
string | |
by_platform[].source required |
string | |
choices required |
array of string | The accepted values of an `enum` option; empty otherwise. |
cli_flag required |
string | The flag that sets it for one run, or an empty string when it has none. |
document |
string | Which document holds the option. Values: |
document_path required |
string | The path of that document (`config get` only). |
editable required |
boolean | Whether `config set` can store it. |
effective required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
env required |
string | The environment variable that overrides it. |
env_value required |
one of string, null, object | The raw override string when the variable is set, otherwise null. |
hint required |
string | |
key required |
string | The option's short name, also its `env_overrides` key. |
kind required |
string | Values: |
label required |
string | |
masked |
boolean | True for a secret option, whose values are {set: boolean}. |
ok required |
any | |
path required |
string | null | The dotted path in the document (`defaults.headless`), or null for an option with no stored form. |
source required |
string | The layer that supplied effective, such as config, env, builtin or risk_profile. |
stored required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
POST
/cli/config/path
Print the effective global config file path.
Print the effective global config file path.
Request body required
The flags and arguments of `blowhorn config path`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn config path' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn config': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn config path' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
POST
/cli/config/set
Set a dotted key in the global config file, keeping the file's comments, key order and unknown keys as written. A key the engine reads is stored in its own type (defaults.headless becomes a boolean, defaults.exclude a list even for one name, an empty value clears the key); for any other key, true/false become booleans and comma-delimited values become lists. A value that starts with a dash is read as an option unless you end the options first: write "blowhorn config set <key> -- <value>", which is also how a refused value gets the sentence saying what a valid one is.
Set a dotted key in the global config file, keeping the file's comments, key order and unknown keys as written. A key the engine reads is stored in its own type (defaults.headless becomes a boolean, defaults.exclude a list even for one name, an empty value clears the key); for any other key, true/false become booleans and comma-delimited values become lists. A value that starts with a dash is read as an option unless you end the options first: write "blowhorn config set <key> -- <value>", which is also how a refused value gets the sentence saying what a valid one is.
Request body required
The flags and arguments of `blowhorn config set`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the option report as machine-readable JSON Default: |
key required |
string | Dotted option key, defaults.<name>, as 'blowhorn config show --json' lists them |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn config set' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn config': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn config set' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
value |
string | Value to assign to the key; pass an explicit '' to clear a stored value |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the result of storing one option. A real write prints the whole document as `config show` does with the key written; a `--dry-run` prints only the key, the value that would be stored and the path.
ConfigSetDryRun
| Name | Type | Description |
|---|---|---|
dry_run required |
any | |
key required |
string | |
ok required |
any | |
path required |
string | |
value required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
ConfigSetWritten
| Name | Type | Description |
|---|---|---|
caps |
array of ConfigCapRow | The same daily-cap report `config show` prints. |
caps[].action required |
string | The gate's action name; a user's `follow_per_day` is reported here as outreach. Values: |
caps[].ceiling_per_day required |
integer | The built-in per-day ceiling for this platform and action. |
caps[].effective_per_day required |
integer | The lower of the ceiling and the user's value (the ceiling when unset). |
caps[].error |
string | Present only when `source` is `invalid`; names the unreadable `defaults.caps` entry. |
caps[].platform required |
string | The platform's canonical name (x, linkedin, hn, ...). |
caps[].source required |
string | `config` when the file sets the user's value, `none` when only the ceiling stands, `invalid` when `defaults.caps` cannot be read (the row then shows the ceiling and `error` says why; scheduled jobs are held until it is fixed). Values: |
caps[].user_per_day required |
integer | null | The user's own per-day cap from `defaults.caps`, or null when unset. |
changed_keys required |
array of string | |
created required |
boolean | Whether the write created the file. |
dry_run required |
any | |
env_overrides |
object | |
exists required |
boolean | |
key required |
string | |
ok required |
any | |
options required |
array of ConfigOptionRow | |
options[].accepts_number required |
boolean | |
options[].builtin required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].by_platform |
array of object | The `pace` row only: the effective pace for every platform and the layer that supplied it (`pace_by_platform`, `defaults.pace`, `env`, `risk_profile` or `builtin`). |
options[].by_platform[].pace required |
string | |
options[].by_platform[].platform required |
string | |
options[].by_platform[].source required |
string | |
options[].choices required |
array of string | The accepted values of an `enum` option; empty otherwise. |
options[].cli_flag required |
string | The flag that sets it for one run, or an empty string when it has none. |
options[].document |
string | Which document holds the option. Values: |
options[].document_path |
string | The path of that document (`config get` only). |
options[].editable required |
boolean | Whether `config set` can store it. |
options[].effective required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].env required |
string | The environment variable that overrides it. |
options[].env_value required |
one of string, null, object | The raw override string when the variable is set, otherwise null. |
options[].hint required |
string | |
options[].key required |
string | The option's short name, also its `env_overrides` key. |
options[].kind required |
string | Values: |
options[].label required |
string | |
options[].masked |
boolean | True for a secret option, whose values are {set: boolean}. |
options[].ok |
any | |
options[].path required |
string | null | The dotted path in the document (`defaults.headless`), or null for an option with no stored form. |
options[].source required |
string | The layer that supplied effective, such as config, env, builtin or risk_profile. |
options[].stored required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
path required |
string | |
unknown_keys |
array of string | |
value required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
version |
integer | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ConfigSetResponse
POST
/cli/config/show
Display the current global config file contents. With --json, report every option .blowhorn.yaml honours instead: the value stored in the file, the environment override in force, the effective value and which of them supplied it, plus any keys under defaults the engine does not read.
Display the current global config file contents. With --json, report every option .blowhorn.yaml honours instead: the value stored in the file, the environment override in force, the effective value and which of them supplied it, plus any keys under defaults the engine does not read.
Request body required
The flags and arguments of `blowhorn config show`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the option report as machine-readable JSON Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn config show' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn config': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn config show' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the global config document and every option in it: its stored, built-in and effective value, which layer supplied it, and the environment variable that overrides it.
| Name | Type | Description |
|---|---|---|
caps required |
array of ConfigCapRow | Every platform action's daily cap: the built-in ceiling, the user's own value from `defaults.caps` when set, and the effective (lower) number. |
caps[].action required |
string | The gate's action name; a user's `follow_per_day` is reported here as outreach. Values: |
caps[].ceiling_per_day required |
integer | The built-in per-day ceiling for this platform and action. |
caps[].effective_per_day required |
integer | The lower of the ceiling and the user's value (the ceiling when unset). |
caps[].error |
string | Present only when `source` is `invalid`; names the unreadable `defaults.caps` entry. |
caps[].platform required |
string | The platform's canonical name (x, linkedin, hn, ...). |
caps[].source required |
string | `config` when the file sets the user's value, `none` when only the ceiling stands, `invalid` when `defaults.caps` cannot be read (the row then shows the ceiling and `error` says why; scheduled jobs are held until it is fixed). Values: |
caps[].user_per_day required |
integer | null | The user's own per-day cap from `defaults.caps`, or null when unset. |
env_overrides required |
object | Option key to the environment variable that overrides it. |
exists required |
boolean | |
ok required |
any | |
options required |
array of ConfigOptionRow | |
options[].accepts_number required |
boolean | |
options[].builtin required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].by_platform |
array of object | The `pace` row only: the effective pace for every platform and the layer that supplied it (`pace_by_platform`, `defaults.pace`, `env`, `risk_profile` or `builtin`). |
options[].by_platform[].pace required |
string | |
options[].by_platform[].platform required |
string | |
options[].by_platform[].source required |
string | |
options[].choices required |
array of string | The accepted values of an `enum` option; empty otherwise. |
options[].cli_flag required |
string | The flag that sets it for one run, or an empty string when it has none. |
options[].document |
string | Which document holds the option. Values: |
options[].document_path |
string | The path of that document (`config get` only). |
options[].editable required |
boolean | Whether `config set` can store it. |
options[].effective required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].env required |
string | The environment variable that overrides it. |
options[].env_value required |
one of string, null, object | The raw override string when the variable is set, otherwise null. |
options[].hint required |
string | |
options[].key required |
string | The option's short name, also its `env_overrides` key. |
options[].kind required |
string | Values: |
options[].label required |
string | |
options[].masked |
boolean | True for a secret option, whose values are {set: boolean}. |
options[].ok |
any | |
options[].path required |
string | null | The dotted path in the document (`defaults.headless`), or null for an option with no stored form. |
options[].source required |
string | The layer that supplied effective, such as config, env, builtin or risk_profile. |
options[].stored required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
path required |
string | The config file read. |
unknown_keys required |
array of string | Dotted `defaults.*` keys the document holds that no option describes. |
version required |
integer | null | The document's `version`, or null when it has none (or does not exist). |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ConfigShowResponse
| Name | Type | Description |
|---|---|---|
caps required |
array of ConfigCapRow | Every platform action's daily cap: the built-in ceiling, the user's own value from `defaults.caps` when set, and the effective (lower) number. |
caps[].action required |
string | The gate's action name; a user's `follow_per_day` is reported here as outreach. Values: |
caps[].ceiling_per_day required |
integer | The built-in per-day ceiling for this platform and action. |
caps[].effective_per_day required |
integer | The lower of the ceiling and the user's value (the ceiling when unset). |
caps[].error |
string | Present only when `source` is `invalid`; names the unreadable `defaults.caps` entry. |
caps[].platform required |
string | The platform's canonical name (x, linkedin, hn, ...). |
caps[].source required |
string | `config` when the file sets the user's value, `none` when only the ceiling stands, `invalid` when `defaults.caps` cannot be read (the row then shows the ceiling and `error` says why; scheduled jobs are held until it is fixed). Values: |
caps[].user_per_day required |
integer | null | The user's own per-day cap from `defaults.caps`, or null when unset. |
env_overrides required |
object | Option key to the environment variable that overrides it. |
exists required |
boolean | |
ok required |
any | |
options required |
array of ConfigOptionRow | |
options[].accepts_number required |
boolean | |
options[].builtin required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].by_platform |
array of object | The `pace` row only: the effective pace for every platform and the layer that supplied it (`pace_by_platform`, `defaults.pace`, `env`, `risk_profile` or `builtin`). |
options[].by_platform[].pace required |
string | |
options[].by_platform[].platform required |
string | |
options[].by_platform[].source required |
string | |
options[].choices required |
array of string | The accepted values of an `enum` option; empty otherwise. |
options[].cli_flag required |
string | The flag that sets it for one run, or an empty string when it has none. |
options[].document |
string | Which document holds the option. Values: |
options[].document_path |
string | The path of that document (`config get` only). |
options[].editable required |
boolean | Whether `config set` can store it. |
options[].effective required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
options[].env required |
string | The environment variable that overrides it. |
options[].env_value required |
one of string, null, object | The raw override string when the variable is set, otherwise null. |
options[].hint required |
string | |
options[].key required |
string | The option's short name, also its `env_overrides` key. |
options[].kind required |
string | Values: |
options[].label required |
string | |
options[].masked |
boolean | True for a secret option, whose values are {set: boolean}. |
options[].ok |
any | |
options[].path required |
string | null | The dotted path in the document (`defaults.headless`), or null for an option with no stored form. |
options[].source required |
string | The layer that supplied effective, such as config, env, builtin or risk_profile. |
options[].stored required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
path required |
string | The config file read. |
unknown_keys required |
array of string | Dotted `defaults.*` keys the document holds that no option describes. |
version required |
integer | null | The document's `version`, or null when it has none (or does not exist). |
Github
POST
/cli/github/squatters
Sweep a repository's assigned-but-stalled issues through GitHub's GraphQL API.
Sweep a repository's assigned-but-stalled issues through GitHub's GraphQL API.
For each assignee whose silence -- measured from the later of their assignment date and their last activity (a comment, a linked PR, a status change) -- reaches --nudge-after-days (default 14), the action posts a fixed-template nudge; --warn-after-days (default 7) later with still no reply it posts a final warning; --unassign-after-days (default 7) after that it removes the assignee and posts a closing comment. Issues with an open linked PR from the assignee, blocked or waiting-on-maintainer labels, security labels, or an assignee who is the issue's own author are never bumped. A dry run is the default: without --apply the sweep prints exactly what it would do and writes nothing. Immediately before any write the issue is re-read, so a run never unassigns someone who just became active. No CLI is required: the sweep talks to GitHub's GraphQL API over HTTPS with the store's GitHub token, and a token that is missing or unauthenticated is a hard error, never a silent fallback.
Request body required
The flags and arguments of `blowhorn github squatters`, one property per parameter.
| Name | Type | Description |
|---|---|---|
apply |
boolean | Perform the writes: post the nudge, warning, or closing comment and remove the assignment. Without --apply the run is a dry-run preview and writes nothing Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
limit |
integer | Maximum open issues examined in the run (default 100) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
nudge_after_days |
integer | Days of assignee silence before a nudge is posted (default 14) Default: |
pace |
string | Accepted by 'blowhorn github squatters' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn github'. One of: github, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
repo |
string | Repository to sweep, as owner/name |
risk_profile |
string | Accepted by 'blowhorn github squatters' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
unassign_after_days |
integer | Days after the final warning with still no reply before the assignment is released (default 7) Default: |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
warn_after_days |
integer | Days after a nudge with still no reply before the final warning (default 7) Default: |
Responses
200
The command prints human-readable text; it has no --json contract.
Human-readable stdout from a command that has no --json flag. Its wording is not a contract; parse nothing out of it.
Schema: CliTextOutput
Chrome
POST
/cli/chrome/active-tab
Ask the Chrome extension for the front tab's address and title, through the bridge, and print it. The popover's New order asks here first: it retries briefly and never uses Apple Events, so macOS never prompts for Chrome. If the live read has no tab, a reachable extension may return its last remembered normal-window HTTP(S) tab with stale and age fields. This command navigates nowhere, probes no tab's page, and contacts no platform: it reads only the front tab's own address and title, from the Chrome profile whose bridge answers first. No reachable extension or no live or remembered tab answers with no tab, and --json carries connected and a detail saying why. That read writes nothing but the bridge lock, the same lock `chrome extension-status` writes. --json emits the answer as one object, for a script or the desktop app.
Ask the Chrome extension for the front tab's address and title, through the bridge, and print it. The popover's New order asks here first: it retries briefly and never uses Apple Events, so macOS never prompts for Chrome. If the live read has no tab, a reachable extension may return its last remembered normal-window HTTP(S) tab with stale and age fields. This command navigates nowhere, probes no tab's page, and contacts no platform: it reads only the front tab's own address and title, from the Chrome profile whose bridge answers first. No reachable extension or no live or remembered tab answers with no tab, and --json carries connected and a detail saying why. That read writes nothing but the bridge lock, the same lock `chrome extension-status` writes. --json emits the answer as one object, for a script or the desktop app.
Request body required
The flags and arguments of `blowhorn chrome active-tab`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome active-tab' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome active-tab' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The front Chrome tab's address and title, as the Blowhorn extension answers them through the bridge. `tab` is null when no Chrome with the extension answered a front tab; `connected` says whether any bridge socket answered at all, and `detail` names the failure, so a caller can tell "no Chrome with the extension" from "Chrome is up but no tab is front".
| Name | Type | Description |
|---|---|---|
age_ms required |
integer | null | Age of the remembered tab in milliseconds, or null when the tab is live or unavailable. |
connected required |
boolean | |
detail required |
string | null | |
ok required |
any | |
stale required |
boolean | True when the extension returned its last remembered normal-window HTTP(S) tab because the live read had no answer. |
tab required |
one of null, object |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeActiveTabResponse
| Name | Type | Description |
|---|---|---|
age_ms required |
integer | null | Age of the remembered tab in milliseconds, or null when the tab is live or unavailable. |
connected required |
boolean | |
detail required |
string | null | |
ok required |
any | |
stale required |
boolean | True when the extension returned its last remembered normal-window HTTP(S) tab because the live read had no answer. |
tab required |
one of null, object |
POST
/cli/chrome/attach-chrome
Start or report a dedicated Chrome that is open to remote debugging with no Allow dialog, so '--chrome-launch cdp' can attach to a real Chrome unattended. Chrome 136 and later ignore '--remote-debugging-port' on the default data directory, which is why this is a second Chrome on a tree of its own (~/.blowhorn/attach-chrome, on a port Chrome picks and records, bound to 127.0.0.1) and never your daily one. That port has no password and asks for no Allow, and loopback is shared by every macOS user on this Mac, so any other user signed in here can control that Chrome, its cookies and signed-in sessions included. That is why it is off by default: 'start' and 'install-agent' refuse without --allow-other-users. Sign it in only to the accounts Blowhorn posts from.
Start or report a dedicated Chrome that is open to remote debugging with no Allow dialog, so '--chrome-launch cdp' can attach to a real Chrome unattended. Chrome 136 and later ignore '--remote-debugging-port' on the default data directory, which is why this is a second Chrome on a tree of its own (~/.blowhorn/attach-chrome, on a port Chrome picks and records, bound to 127.0.0.1) and never your daily one. That port has no password and asks for no Allow, and loopback is shared by every macOS user on this Mac, so any other user signed in here can control that Chrome, its cookies and signed-in sessions included. That is why it is off by default: 'start' and 'install-agent' refuse without --allow-other-users. Sign it in only to the accounts Blowhorn posts from.
'status' (the default) reads that tree's 'Local State', 'DevToolsActivePort' and 'SingletonLock' and nothing else -- no browser opens, nothing connects -- and its last line says what to do next. 'signin' opens the tree with no remote debugging at all, for the one-time sign-in to each account Blowhorn posts from, because some sites (Google among them) refuse to sign in inside a browser started with debugging on; quit that Chrome when you are done and the sign-ins carry over. 'stop' quits the Chrome holding the tree the way Cmd-Q does (DevTools Browser.close, so fresh sign-ins are saved), waits, then sends SIGTERM; it signals only that Chrome and never SIGKILLs. 'install-agent' adds a per-user LaunchAgent (ai.blowhorn.attach-chrome) that runs 'start' once at each login, so scheduler ticks find it up, and 'uninstall-agent' removes it (the Chrome keeps running). 'start' launches Chrome on the tree, detached, with the port open, and prints the steps that finish the setup: point chrome.data_dir at the tree, map your profiles, then run with '--chrome-launch cdp'. It writes no configuration of its own. The tree is fixed and Chrome picks the port, so its one option of its own is the opt-in. A recorded port is evidence a Chrome is up on that tree, not proof a connect would succeed, and it counts only while a live Chrome holds the tree. Starting is refused when the tree is Google Chrome's own default data directory (never the one chrome.data_dir names, which step 1 above points at this tree), when a live Chrome already has a port recorded there, and while the tree is open without debugging (the signin window): quit that Chrome first. A port file a crashed Chrome left behind is cleared, not refused. --dry-run prints the command line and starts nothing.
Request body required
The flags and arguments of `blowhorn chrome attach-chrome`, one property per parameter.
| Name | Type | Description |
|---|---|---|
action |
string | 'status' (the default) reports the tree without starting anything; 'signin' opens it with no remote debugging for the one-time sign-in; 'start' launches Chrome on it with the port open; 'stop' quits that Chrome cleanly; 'install-agent' / 'uninstall-agent' manage the LaunchAgent that starts it at login Values: Default: |
allow_other_users |
boolean | Required by 'start' and 'install-agent': accept that the debugging port they open has no password and that any other macOS user signed in on this Mac can control that Chrome through it Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome attach-chrome' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome attach-chrome' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the dedicated attach-chrome tree (a second Chrome with the debugging port open, never the daily one) or what an action did to it. `status`, `start`, `signin` and `stop --dry-run` report the tree; a real `stop` reports the Chrome it quit; `install-agent` and `uninstall-agent` report the login LaunchAgent.
ChromeAttachChromeTree
| Name | Type | Description |
|---|---|---|
app_path required |
string | null | |
argv |
array of string | The command line that starts (or would start) Chrome; present on `start` and `signin`. |
data_dir required |
string | |
debugging |
any | Present on `signin`, which opens the tree with no debugging port. |
devtools_port required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
devtools_port.detail required |
string | null | Why the file is not `ok`; null when it is. |
devtools_port.path required |
string | |
devtools_port.state required |
string | Values: |
dry_run |
boolean | Present on `start`, `signin` and `stop`. |
exists required |
boolean | |
held_without_debugging required |
boolean | True when a live Chrome holds the tree with no debugging port open. |
holder_pid required |
integer | null | The live process holding the tree's SingletonLock, if any. |
installed required |
boolean | Whether Google Chrome is installed. |
last_used required |
string | null | |
listening required |
boolean | |
local_state required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
local_state.detail required |
string | null | Why the file is not `ok`; null when it is. |
local_state.path required |
string | |
local_state.state required |
string | Values: |
minimum_version required |
integer | The oldest Chrome major Blowhorn supports. |
next_step required |
string | null | |
next_steps |
array of string | What to do after a real `start`, or after `signin`. |
ok required |
any | |
plan |
string | What a real `stop` would do; present on `stop --dry-run`. |
profile_count required |
integer | |
recorded_port required |
integer | null | |
remote_debugging required |
ChromeRemoteDebugging | Whether Chrome's remote-debugging server is up, read from `DevToolsActivePort`. Every field is null when the file said nothing. |
remote_debugging.browser_path required |
string | null | |
remote_debugging.enabled required |
boolean | null | |
remote_debugging.port required |
integer | null | |
stale_port required |
boolean | True when a port is recorded but no live Chrome holds the tree. |
started |
boolean | Present on `status`, `start` and `signin`; true only after a real `start` or `signin`. |
stopped |
any | Present on `stop --dry-run`, which signals nothing. |
supported required |
boolean | null | Whether version is at least minimum_version; null when the version is unknown. |
version required |
string | null | The Chrome app bundle's CFBundleShortVersionString; null when it could not be read. |
ChromeAttachChromeStopped
| Name | Type | Description |
|---|---|---|
data_dir required |
string | |
detail required |
string | |
dry_run required |
any | |
method required |
enum | Values: |
ok required |
any | |
pid required |
integer | null | The Chrome that was asked to quit; null when nothing held the tree. |
stopped required |
any |
ChromeAttachChromeAgentInstalled
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
installed required |
boolean | True only after a real `install-agent`. |
next_steps required |
array of string | |
ok required |
any | |
path required |
string | |
plist required |
object | The LaunchAgent written (or that would be written) to `path`. |
plist.AbandonProcessGroup required |
boolean | |
plist.AssociatedBundleIdentifiers required |
array of string | |
plist.EnvironmentVariables required |
object | |
plist.Label required |
string | |
plist.ProgramArguments required |
array of string | |
plist.RunAtLoad required |
boolean | |
plist.StandardErrorPath required |
string | |
plist.StandardOutPath required |
string | |
plist.WorkingDirectory required |
string |
ChromeAttachChromeAgentRemoved
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
any | |
path required |
string | |
removed required |
boolean | True when a LaunchAgent file was removed. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeAttachChromeResponse
POST
/cli/chrome/confirm
Apply the confirm screen's confirmed choices: turn Chrome profiles into Blowhorn profiles, attach the discovered platform accounts, and map each Chrome profile -- the one write onboarding makes, and the person's confirm is the consent it records. The choices arrive as one JSON document on stdin (--payload-stdin), so a GitHub token or a Bluesky app password in it reaches neither argv nor the daily log:
Apply the confirm screen's confirmed choices: turn Chrome profiles into Blowhorn profiles, attach the discovered platform accounts, and map each Chrome profile -- the one write onboarding makes, and the person's confirm is the consent it records. The choices arrive as one JSON document on stdin (--payload-stdin), so a GitHub token or a Bluesky app password in it reaches neither argv nor the daily log:
{"assignments": [{"directory": "Profile 3", "profile": "ada-example", "create": true, "userId": "<the member's uuid, as 'org members' lists them; omit it for a brand>", "platforms": {"linkedin": {"attach": true, "session": true}, "x": {"attach": true}, "github": {"attach": true, "token": "..."}, "bluesky": {"attach": true, "app_password": "..."}, "slack": {"attach": true}}}]}
Per assignment, in order: the Blowhorn profile (an existing one the payload names by 'profileId' or by 'profile', the one the directory is already mapped to, or, with "create": true, a new one created in your Blowhorn account representing the member 'userId' names, or nobody for a brand; a name the payload omits is proposed from the Google name, and a profile the organization does not have is never created without "create"); then the attached platforms' credentials, stored in your Blowhorn account and nowhere on this computer; then the mapping, exactly as 'chrome map' records it and, when the extension is already on in that Chrome profile, paired the same way, opening the extension's pairing page there, since a run refuses a mapping the extension cannot prove; a profile without the extension on yet is paired once it is (the desktop's setup does this, or 'chrome map'); then each attached platform's username as the profile's account there: a connection registered and linked to the profile, named for the username and pointing at its account page when the username is a handle, which is where the profile's handles are read from. A platform row the payload marks with "session": true (LinkedIn, Hacker News) stores a confirmed-session credential naming this Chrome profile directory instead of a password: it satisfies eligibility the way a session-capture platform's identity key does, because the run checks that Chrome session before it reaches for a password. GitHub stores GH_USERNAME and still waits for GH_TOKEN; Bluesky stores the handle and still waits for the app password; both may ride in the same payload. Slack stores nothing: its session is captured afterwards by 'profile auth --platform slack', and the result says so.
A profile the document marks with "in_use": false is recorded not in use: this computer's mapping for that directory is removed, the Blowhorn profile is added to defaults.exclude when that name is not already there, and the profile and its credentials are kept. A platform row with "attach": false is recorded not in use for that profile in .blowhorn.chrome-not-in-use.json beside the config file; a platform left out of the assignment is left as it was recorded. The credential is kept, and a later run on this computer skips a platform still recorded not in use. Marking a platform in use clears that skip for the Blowhorn profile on every Chrome directory in the record, so a later choice in the same document wins. Marking the profile in use again removes it from defaults.exclude only when this screen added that name; a name already in the list is left as written, spelling and notes included. Omitting "in_use" applies only the attached rows, as before.
Each assignment answers for itself in the result -- profile, whether it was created, the keys written per platform, the mapping, the accounts recorded, or the one-sentence refusal -- so a partial confirm is a successful confirm: what landed stays landed, and what refused says why, by name. The exit code is 0 when at least one assignment landed and 1 when none did; a store that cannot be reached is the named cause and exit 3. With --dry-run every check runs and nothing is written, created or mapped.
Request body required
The JSON document `chrome confirm --payload-stdin` reads. One assignment per Chrome profile the person decided about. `in_use: false` records that Chrome profile as not in use. A platform object with `attach: false` records that platform as not in use on the Blowhorn profile. Neither choice deletes a profile or a credential. A GitHub token and a Bluesky app password travel in this request and are never echoed. Only `assignments` and, in each assignment, `directory` are required: the handler defaults every other field when it is omitted, and it ignores fields it does not name, at every level of the document. The one exception is an assignment's `email` or `subject`, which the handler refuses by name: they named a profile's person before `userId` did, and read as absent they would create a brand where the caller meant a person.
| Name | Type | Description |
|---|---|---|
assignments required |
array of object | |
assignments[].create |
boolean | Create the profile `profile` names (or the one proposed from the Chrome profile's name) in your Blowhorn account. A profile the organization does not have is never created without it. |
assignments[].directory required |
string | The Chrome profile directory, as `chrome profiles` prints it. |
assignments[].in_use |
boolean | False records this Chrome profile as not in use. Omitted means in use, so an older document that only lists chosen rows still applies those rows. |
assignments[].platforms |
object | Platform id to the choice for that platform. A platform left out is not changed. Omitted means no platform is changed. |
assignments[].profile |
string | An existing Blowhorn profile by name, or the name of the profile to create when `create` is set. |
assignments[].profileId |
string (uuid) | An existing Blowhorn profile, by its id as `profile list` prints it. Never with `create`. |
assignments[].userId |
string (uuid) | Create only. The member of the organization the new profile represents, as `org members` lists them. Omitted creates a brand. |
Responses
200
What the command prints to stdout with --json when it exits 0.
One result per assignment `chrome confirm` was given on stdin, in the document's order: the Blowhorn profile each Chrome profile now posts as, whether confirm created it, the credential keys it wrote per platform (key names only, never a value), and how the Chrome profile mapping stands. `ok` is true when at least one assignment landed; a refused assignment carries its reason in its own row and does not stop the others. The assignments document itself, typed secrets included, arrives on stdin and is never echoed back.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
host required |
string | The machine host the mappings are keyed by. |
ok required |
boolean | True when at least one assignment landed. |
results required |
array of object | |
results[].accounts required |
array of SessionAccount | The accounts this assignment recorded in your Blowhorn account - each attached platform's username, then the browser sign-ins it attached; empty for a dry run, a refusal, or a store that keeps no accounts. |
results[].accounts[].account required |
one of ProfileAccount, null | |
results[].accounts[].outcome required |
enum | none - not signed in and no account is mapped, so nothing was written. Values: |
results[].accounts[].platform required |
enum | Values: |
results[].accounts[].profile required |
string | |
results[].created required |
boolean | Confirm created the profile in your Blowhorn account in this run. |
results[].directory required |
string | The Chrome profile directory the assignment named. |
results[].error required |
string | null | Why this assignment was refused, or null when it landed. |
results[].mapped required |
one of enum, null | mapped - the mapping was written now; already - it already named this profile; would map - a dry run's answer; null when the assignment was refused before mapping, or when the Chrome profile was recorded not in use and no mapping was written. |
results[].notes required |
array of string | Follow-ups the write could not finish itself, such as capturing a Slack session interactively. |
results[].ok required |
boolean | |
results[].pairing required |
one of object, null | The pairing a mapping starts, or a mapping confirmed again re-starts, as 'chrome map' reports it, when the extension is already on in that Chrome profile; null when nothing was mapped (including a dry run) or the extension is not on there yet, which the desktop's setup pairs once it is (or 'chrome map'). When the page could not open, a note says how to pair. |
results[].profile required |
string | null | The Blowhorn profile the Chrome profile posts as; null when the assignment was refused before a profile was settled. |
results[].written required |
object | Credential key names written, per platform - names only, never a value. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeConfirmResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
host required |
string | The machine host the mappings are keyed by. |
ok required |
boolean | True when at least one assignment landed. |
results required |
array of object | |
results[].accounts required |
array of SessionAccount | The accounts this assignment recorded in your Blowhorn account - each attached platform's username, then the browser sign-ins it attached; empty for a dry run, a refusal, or a store that keeps no accounts. |
results[].accounts[].account required |
one of ProfileAccount, null | |
results[].accounts[].outcome required |
enum | none - not signed in and no account is mapped, so nothing was written. Values: |
results[].accounts[].platform required |
enum | Values: |
results[].accounts[].profile required |
string | |
results[].created required |
boolean | Confirm created the profile in your Blowhorn account in this run. |
results[].directory required |
string | The Chrome profile directory the assignment named. |
results[].error required |
string | null | Why this assignment was refused, or null when it landed. |
results[].mapped required |
one of enum, null | mapped - the mapping was written now; already - it already named this profile; would map - a dry run's answer; null when the assignment was refused before mapping, or when the Chrome profile was recorded not in use and no mapping was written. |
results[].notes required |
array of string | Follow-ups the write could not finish itself, such as capturing a Slack session interactively. |
results[].ok required |
boolean | |
results[].pairing required |
one of object, null | The pairing a mapping starts, or a mapping confirmed again re-starts, as 'chrome map' reports it, when the extension is already on in that Chrome profile; null when nothing was mapped (including a dry run) or the extension is not on there yet, which the desktop's setup pairs once it is (or 'chrome map'). When the page could not open, a note says how to pair. |
results[].profile required |
string | null | The Blowhorn profile the Chrome profile posts as; null when the assignment was refused before a profile was settled. |
results[].written required |
object | Credential key names written, per platform - names only, never a value. |
POST
/cli/chrome/create-profile
Give Blowhorn a Google Chrome profile of its own, for a Chrome that has only its main profile. Asks Chrome to open a new window in the profile directory 'Blowhorn'. Chrome creates that profile the first time and opens it again every time after, so running this twice makes no second one. Chrome gives the new profile a default name such as 'Person 1', which Blowhorn shows as Blowhorn's profile. Sign in there to LinkedIn, X and the other sites Blowhorn should use, then 'blowhorn chrome discover' finds the profile and its accounts. Reads Local State only to say whether the profile was already listed; writes nothing, maps nothing, and drives nothing in the window. If no Chrome is running, Chrome starts to open the window and is left running. Your own profiles, windows and tabs are left as they are.
Give Blowhorn a Google Chrome profile of its own, for a Chrome that has only its main profile. Asks Chrome to open a new window in the profile directory 'Blowhorn'. Chrome creates that profile the first time and opens it again every time after, so running this twice makes no second one. Chrome gives the new profile a default name such as 'Person 1', which Blowhorn shows as Blowhorn's profile. Sign in there to LinkedIn, X and the other sites Blowhorn should use, then 'blowhorn chrome discover' finds the profile and its accounts. Reads Local State only to say whether the profile was already listed; writes nothing, maps nothing, and drives nothing in the window. If no Chrome is running, Chrome starts to open the window and is left running. Your own profiles, windows and tabs are left as they are.
Request body required
The flags and arguments of `blowhorn chrome create-profile`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome create-profile' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome create-profile' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What chrome create-profile prints with --json: the Chrome profile directory Blowhorn asked Google Chrome to open a window in, whether Local State already listed it, and whether Chrome was asked to open it. Chrome creates a new profile for a directory it does not know and opens the existing one otherwise, so a repeat never makes a second profile; that was probed live on 2026-10-28 (Google Chrome 156 on macOS). Nothing after the open is read back, so whether Chrome created the profile on a given run is not claimed. A refusal (Google Chrome not installed) prints a Problem instead.
| Name | Type | Description |
|---|---|---|
already_listed required |
boolean | null | Local State listed the directory before this call; null when Local State could not be read. |
detail required |
string | null | Why the window did not open ('chrome could not open blowhorn's chrome profile: <reason>'), or the dry-run note; null when it opened. |
directory required |
string | The Chrome profile directory Blowhorn works in, as chrome profiles prints it once Chrome has created it. |
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the window (always true on a dry run); false with exit 1 when it could not be asked. |
opened required |
boolean | Chrome was asked to open the window in this profile. |
url required |
string | The page the window opened on. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeCreateProfileResponse
| Name | Type | Description |
|---|---|---|
already_listed required |
boolean | null | Local State listed the directory before this call; null when Local State could not be read. |
detail required |
string | null | Why the window did not open ('chrome could not open blowhorn's chrome profile: <reason>'), or the dry-run note; null when it opened. |
directory required |
string | The Chrome profile directory Blowhorn works in, as chrome profiles prints it once Chrome has created it. |
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the window (always true on a dry run); false with exit 1 when it could not be asked. |
opened required |
boolean | Chrome was asked to open the window in this profile. |
url required |
string | The page the window opened on. |
POST
/cli/chrome/discover
Report, for every Chrome profile, the platform accounts discovery can see there -- the read behind onboarding's confirm screen. For each profile: its directory, display name and signed-in Google account (from 'Local State', as 'chrome profiles' reads them), the Blowhorn profile it is mapped to, the platform rows discovery found, a proposed Blowhorn profile slug, whether that slug is free, and how the Google email resolves against the organization's Blowhorn users.
Report, for every Chrome profile, the platform accounts discovery can see there -- the read behind onboarding's confirm screen. For each profile: its directory, display name and signed-in Google account (from 'Local State', as 'chrome profiles' reads them), the Blowhorn profile it is mapped to, the platform rows discovery found, a proposed Blowhorn profile slug, whether that slug is free, and how the Google email resolves against the organization's Blowhorn users.
A platform row is a saved-login username and whether a session is live. The usernames come from the profile's 'Login Data' and 'Login Data For Account', for an allowlist of hosts only (linkedin.com, x.com and twitter.com, reddit.com, news.ycombinator.com, github.com, bsky.app and bsky.social, slack.com): only the realm and the username are ever selected -- the password column is a blob Chrome encrypts, and it is never selected, decrypted or copied. Whether a session is live comes from cookie names in 'Cookies' or 'Network/Cookies': presence only, never a value, except GitHub's 'dotcom_user', whose value is the login name itself and so is the GitHub username when a session is live. Hacker News's 'user' cookie is not read at all: its value bundles the username with a session secret. History and LevelDB are never opened -- a visit is not an identity. No browser opens, and a file Chrome holds locked reads as 'not found', never as an error. Nothing is written to Chrome. For each Chrome profile mapped here and in use, whose identity holds, a LinkedIn, X or Reddit sign-in it is picked for is recorded as that profile's account in your Blowhorn account, and a sign-in that ended marks the account disconnected; nothing is deleted, and --local and --dry-run record nothing. A not-in-use row for a directory Chrome no longer has is dropped from the record beside the config file.
The resolution answers the create rule without creating: 'one' (the email names exactly one Blowhorn user in the organization; that user's Cloud social links ride along, shown on the confirm screen but never treated as permission to post), 'several' (the member candidates, for a picker), 'none' (no member holds the email), or 'no-email' (nobody is signed in to that Chrome profile). --json emits the whole survey with a stable shape, for the desktop app. A store that cannot be reached is the named cause and exit 3; an unreadable profile list is one sentence and exit 1.
--local reads Chrome's files only and never opens the store, so it works before sign-in: every row's mapping is null, its resolution is 'unresolved' (or 'no-email') and its slug state 'unchecked'. The desktop app runs it when it first opens, then merges the full survey once you are signed in. Chrome's Guest and System profiles are never listed.
Request body required
The flags and arguments of `blowhorn chrome discover`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
local |
boolean | Read Chrome's files only, without the store: works before sign-in, resolves nobody Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome discover' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome discover' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The `chrome profiles` rows, plus what setup's confirm step needs for each: the platform accounts discovery found in that Chrome profile (a saved-login username, or the live GitHub username; a signed-in flag from a session cookie's presence, never its value), how the profile's Google email resolves under the profile create rule, and the profile name a create would propose. Nothing is written to Chrome. For each Chrome profile mapped here, in use, whose identity holds, the LinkedIn, X and Reddit sign-ins it is picked for are recorded as the profile's accounts in your Blowhorn account (`accounts`); `--local` and `--dry-run` record nothing. A not-in-use row whose directory Chrome no longer has is dropped. Account names are real people's; never commit a real output.
| Name | Type | Description |
|---|---|---|
account_errors |
array of string | One sentence per account the store refused to record, naming the profile and platform. |
accounts |
array of SessionAccount | What this survey recorded in your Blowhorn account, one entry per profile and browser platform it checked. |
accounts[].account required |
one of ProfileAccount, null | |
accounts[].outcome required |
enum | none - not signed in and no account is mapped, so nothing was written. Values: |
accounts[].platform required |
enum | Values: |
accounts[].profile required |
string | |
data_dir required |
string | |
host required |
string | The machine host the mappings are keyed by. |
last_used required |
string | null | Directory of the profile Chrome last used. |
local |
boolean | True for `--local`, which reads Chrome's files only, never the store, so nobody is resolved or mapped. |
local_state required |
object | |
local_state.detail required |
string | null | |
local_state.path required |
string | |
local_state.state required |
string | |
ok required |
any | |
profile_count required |
integer | |
profiles required |
array of object | |
profiles[].account required |
string | Signed-in Google account email, empty when nobody is signed in. |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].gaia_name required |
string | |
profiles[].identity required |
string | null | Whether the recorded identity of the mapping still holds (for example ok or drift); null when unmapped. |
profiles[].in_use |
boolean | False when this Chrome profile was recorded not in use. True only when a record says it is in use. Absent when no choice was recorded. |
profiles[].last_used required |
boolean | |
profiles[].mapped_to required |
string | null | The Blowhorn profile mapped to this Chrome profile on this host. |
profiles[].platforms required |
object | One entry per platform discovery looks for; a username is a saved login's username or, for GitHub, the live account cookie's value when signed in. |
profiles[].platforms.bluesky required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.github required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.hn required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.linkedin required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.reddit required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.slack required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.x required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].proposed_slug required |
string | null | The profile name a create would propose, from the Chrome profile's display name; null when nothing usable can be proposed. |
profiles[].resolution required |
one of object, object, object, object, object | The create rule's answer for the profile's Google email, without creating anything. |
profiles[].slug_state required |
enum | Whether the proposed name is free, already the resolved user's own profile, taken by another profile, or not a usable name; unchecked under `--local`. Values: |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeDiscoverResponse
| Name | Type | Description |
|---|---|---|
account_errors |
array of string | One sentence per account the store refused to record, naming the profile and platform. |
accounts |
array of SessionAccount | What this survey recorded in your Blowhorn account, one entry per profile and browser platform it checked. |
accounts[].account required |
one of ProfileAccount, null | |
accounts[].outcome required |
enum | none - not signed in and no account is mapped, so nothing was written. Values: |
accounts[].platform required |
enum | Values: |
accounts[].profile required |
string | |
data_dir required |
string | |
host required |
string | The machine host the mappings are keyed by. |
last_used required |
string | null | Directory of the profile Chrome last used. |
local |
boolean | True for `--local`, which reads Chrome's files only, never the store, so nobody is resolved or mapped. |
local_state required |
object | |
local_state.detail required |
string | null | |
local_state.path required |
string | |
local_state.state required |
string | |
ok required |
any | |
profile_count required |
integer | |
profiles required |
array of object | |
profiles[].account required |
string | Signed-in Google account email, empty when nobody is signed in. |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].gaia_name required |
string | |
profiles[].identity required |
string | null | Whether the recorded identity of the mapping still holds (for example ok or drift); null when unmapped. |
profiles[].in_use |
boolean | False when this Chrome profile was recorded not in use. True only when a record says it is in use. Absent when no choice was recorded. |
profiles[].last_used required |
boolean | |
profiles[].mapped_to required |
string | null | The Blowhorn profile mapped to this Chrome profile on this host. |
profiles[].platforms required |
object | One entry per platform discovery looks for; a username is a saved login's username or, for GitHub, the live account cookie's value when signed in. |
profiles[].platforms.bluesky required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.github required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.hn required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.linkedin required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.reddit required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.slack required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].platforms.x required |
ChromeDiscoveredPlatform | What discovery found for one platform in one Chrome profile: the account name from a saved login (or, for GitHub, the live account cookie's value when signed in), and whether a live session is present - read from a session cookie's presence, never its value. No password and no cookie value is ever read. |
profiles[].proposed_slug required |
string | null | The profile name a create would propose, from the Chrome profile's display name; null when nothing usable can be proposed. |
profiles[].resolution required |
one of object, object, object, object, object | The create rule's answer for the profile's Google email, without creating anything. |
profiles[].slug_state required |
enum | Whether the proposed name is free, already the resolved user's own profile, taken by another profile, or not a usable name; unchecked under `--local`. Values: |
POST
/cli/chrome/extension-enable
Open the Blowhorn extension's Chrome Web Store page in each Chrome profile you name, so you can turn the extension on there with one click. When 'extension-install --all-profiles --external' has written Chrome's external-extensions file, Chrome adds the extension to each profile turned off, and the store page shows Enable now; a profile Chrome has not reopened since then shows Add to Chrome. Name a profile with --chrome-profile, the directory 'blowhorn chrome profiles' prints; repeat it for more than one. Reads Local State to check each directory; writes nothing. If no Chrome is running, Chrome starts to open the page and is left running; Blowhorn drives nothing in it. 'blowhorn chrome extension-status' then says whether each profile has it on (profile_states in --json).
Open the Blowhorn extension's Chrome Web Store page in each Chrome profile you name, so you can turn the extension on there with one click. When 'extension-install --all-profiles --external' has written Chrome's external-extensions file, Chrome adds the extension to each profile turned off, and the store page shows Enable now; a profile Chrome has not reopened since then shows Add to Chrome. Name a profile with --chrome-profile, the directory 'blowhorn chrome profiles' prints; repeat it for more than one. Reads Local State to check each directory; writes nothing. If no Chrome is running, Chrome starts to open the page and is left running; Blowhorn drives nothing in it. 'blowhorn chrome extension-status' then says whether each profile has it on (profile_states in --json).
Request body required
The flags and arguments of `blowhorn chrome extension-enable`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
chrome_profiles |
array of string | A Chrome profile directory, exactly as 'blowhorn chrome profiles' prints it ('Profile 12'): open the store page there. Repeatable |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome extension-enable' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome extension-enable' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What chrome extension-enable prints with --json: the store page it asked Chrome to open and, for each Chrome profile named, whether Chrome was asked. Whether you then turned the extension on is read by chrome extension-status (profile_states), never by this command.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the store page in at least one profile (always true on a dry run); false with exit 1 when it was asked in none. |
profiles required |
array of object | |
profiles[].detail required |
string | null | Why the page did not open in this profile ('chrome could not open the Chrome Web Store page: <reason>'); null when it did. |
profiles[].directory required |
string | The Chrome profile directory, as chrome profiles prints it. |
profiles[].display_name required |
string | |
profiles[].opened required |
boolean | Chrome was asked to open the store page in this profile. |
url required |
string | The extension's Chrome Web Store page. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeExtensionEnableResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the store page in at least one profile (always true on a dry run); false with exit 1 when it was asked in none. |
profiles required |
array of object | |
profiles[].detail required |
string | null | Why the page did not open in this profile ('chrome could not open the Chrome Web Store page: <reason>'); null when it did. |
profiles[].directory required |
string | The Chrome profile directory, as chrome profiles prints it. |
profiles[].display_name required |
string | |
profiles[].opened required |
boolean | Chrome was asked to open the store page in this profile. |
url required |
string | The extension's Chrome Web Store page. |
POST
/cli/chrome/extension-install
Write the native-messaging host manifest Chrome launches for the Blowhorn Chrome extension, and print where the unpacked extension lives so you can load it. The manifest is com.blowhorn.chrome_extension.json under Chrome's NativeMessagingHosts directory (~/Library/Application Support/Google/Chrome/NativeMessagingHosts on macOS, ~/.config/google-chrome/NativeMessagingHosts on Linux), named com.blowhorn.chrome_extension, type stdio, path an executable launcher chrome_extension-host in Blowhorn's directory (~/Library/Application Support/Blowhorn on macOS, ~/.local/share/blowhorn on Linux) that runs the Python native-host program by default, or the Rust host with --native-host rust, and allowed_origins naming the extension id from the key in extension/manifest.json, the Chrome Web Store id and the older unpacked id. If you already installed the host, run this command again and reload the extension once. A missing key or an unreadable extension manifest is one sentence and nothing is written; --dry-run prints both paths and the manifest and writes nothing. This command does not launch Chrome and does not load the extension.
Write the native-messaging host manifest Chrome launches for the Blowhorn Chrome extension, and print where the unpacked extension lives so you can load it. The manifest is com.blowhorn.chrome_extension.json under Chrome's NativeMessagingHosts directory (~/Library/Application Support/Google/Chrome/NativeMessagingHosts on macOS, ~/.config/google-chrome/NativeMessagingHosts on Linux), named com.blowhorn.chrome_extension, type stdio, path an executable launcher chrome_extension-host in Blowhorn's directory (~/Library/Application Support/Blowhorn on macOS, ~/.local/share/blowhorn on Linux) that runs the Python native-host program by default, or the Rust host with --native-host rust, and allowed_origins naming the extension id from the key in extension/manifest.json, the Chrome Web Store id and the older unpacked id. If you already installed the host, run this command again and reload the extension once. A missing key or an unreadable extension manifest is one sentence and nothing is written; --dry-run prints both paths and the manifest and writes nothing. This command does not launch Chrome and does not load the extension.
--profile <name> names the Blowhorn profile whose mapped Chrome profile should get the extension (resolved through 'blowhorn chrome map' on this machine); --chrome-profile '<directory>' names the Chrome profile directory itself, as 'blowhorn chrome profiles' prints it, and can be given more than once. Either one writes the native host as above, checks the directory is in Chrome's Local State, and prints the steps to load the extension in that profile alone: Chrome has no file this command can write that installs an extension in one profile only, since the policy and the external-extensions file both reach every profile. A profile with no mapping, a directory Chrome does not have, and either flag with --all-profiles are one sentence and nothing is written; a store that cannot be reached is the named cause and exit 3. A --profile inherited from BLOWHORN_PROFILE or defaults.profile does not count.
--all-profiles also asks Chrome to install that extension in every Chrome profile on this machine. It is every Chrome profile, not --profile all. The default route writes ExtensionInstallForcelist: silent, every profile, Chrome shows Managed by your organization, and macOS may ask for an administrator password once. On macOS that policy is your own user's, never the computer's, so other macOS users' Chrome is left alone. On Linux the policy file (/etc/opt/chrome/policies/managed/blowhorn.json) and the external file (/usr/share/google-chrome/extensions/<id>.json) need root, Blowhorn never asks for it, and a write this user cannot make is refused with the file, its exact content and the sudo command that writes that one file. Never run this command under sudo: the host and launcher would land under root's home, which your Chrome never reads, so a run under sudo is refused by name. --external writes Chrome's external-extensions file instead, so Chrome offers the install in each profile, one click there, and does not show that banner. --external without --all-profiles is refused and writes nothing. Switching route removes the other route's file for this extension id. Both routes name the extension id from that same key and the Chrome Web Store update url; Chrome can fetch the extension only once that id is published. A policy file that exists and cannot be read is one sentence and nothing is written. Per-profile lines say what Chrome was asked to do, never that a profile's copy was confirmed. Those lines cover the configured Chrome data directory. The policy route applies the extension to every Chrome profile on this machine. --json appends one trailing object (manifest, launcher, extension_dir, removed), for a script or the desktop app. With --all-profiles that object also carries route, extension_id, profiles, profiles_error and profiles_scope; with --profile or --chrome-profile it carries targets. --ensure, with exactly one --profile or --chrome-profile, ensures the extension for that profile the way a run does on demand: it repairs the native host when missing or stale, registers the extension for that profile's data directory when nothing is there yet, and re-probes hello once. A profile already carrying a Chrome Web Store install is left alone, and a store install too old for this Blowhorn is reported with Chrome's own update step, never forced. --ensure refuses --all-profiles and --external, writes nothing with --dry-run, and never starts, restarts, or kills Chrome.
--native-host rust points the same launcher at the Rust native host instead of the Python one: the launcher execs engine/rust/target/release/blowhorn-host in this checkout (built with 'cargo build --release -p blowhorn-host' in engine/rust), or the binary --rust-host names. It is off by default. A Rust host that is missing or not executable is one sentence, nothing is written, and the installed launcher stays as it is. The choice is kept in the launcher itself: a later install or --ensure with no --native-host, the desktop app's automatic one included, keeps the Rust host while its binary is still executable, and puts the Python launcher back, saying so, once it is not. --native-host python switches back. --json carries native_host, python or rust, and native_host_fallback, the sentence when the Rust host was replaced.
Request body required
The flags and arguments of `blowhorn chrome extension-install`, one property per parameter.
| Name | Type | Description |
|---|---|---|
all_profiles |
boolean | Also ask Chrome to install the Chrome extension in every Chrome profile on this machine. The default is the policy route (silent, every profile, Managed by your organization). This is every Chrome profile, not --profile all. Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
chrome_profiles |
array of string | A Chrome profile directory, exactly as 'blowhorn chrome profiles' prints it ('Profile 12'): write the host and print the steps to load the extension in that profile alone. Repeatable. Refused with --all-profiles |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
ensure |
boolean | Ensure the extension for one named Chrome profile, the way a run does when it finds the extension missing, stale, or outdated: repair the native host, register the extension for that profile's data directory when nothing is there yet, and re-probe hello once. Needs exactly one --profile or --chrome-profile, and refuses --all-profiles and --external. A profile already carrying a Chrome Web Store install is left alone: Chrome's updater owns it, and this never forces a store update. Writes nothing with --dry-run. Never starts, restarts, or kills Chrome. Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
external |
boolean | With --all-profiles, write Chrome's external-extensions file instead of the policy, so Chrome offers the install in each profile and does not show Managed by your organization. Refused without --all-profiles; nothing is written. Default: |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
native_host |
string | Which native host the launcher runs: python (the default) or rust, the Rust host from engine/rust built with 'cargo build --release -p blowhorn-host'. Unset keeps what is installed. A Rust host that is missing or not executable is refused and nothing is written. Values: |
pace |
string | Accepted by 'blowhorn chrome extension-install' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome extension-install' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
rust_host |
string | With --native-host rust, the Rust host binary the launcher runs instead of this checkout's engine/rust/target/release/blowhorn-host. Refused without --native-host rust |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What chrome extension-install prints with --json. With --json, stdout is exactly this one JSON document; the human instruction lines are written to stderr instead, where they still reach a terminal and the desktop console's log. The object carries profile detail only for --profile or --all-profiles runs, and an ensure report only for --ensure runs.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ensure |
object | Present for an --ensure run. The on-demand ensure report - what was repaired, what is registered, whether hello re-probed connected, and the one remaining person step when it did not. Never claims a store update - a store install too old for this Blowhorn is reported with Chrome's own update step in person_step. |
ensure.bridge_held required |
boolean | Another run may hold the bridge; no ensure ran. |
ensure.connected required |
boolean | Hello re-probed connected after the ensure. |
ensure.connection_detail required |
string | null | |
ensure.data_dir required |
string | |
ensure.dry_run required |
boolean | |
ensure.external_path required |
string | |
ensure.external_present required |
boolean | |
ensure.external_present_before required |
boolean | The external-extensions file was already there before the ensure. |
ensure.external_refusal required |
string | null | Why the external-extensions file could not be written, when it could not. |
ensure.external_written required |
boolean | |
ensure.failure_sentence required |
string | Empty when connected; otherwise what was repaired plus the person step. |
ensure.host_refusal required |
string | null | Why the native host could not be written, when it could not. |
ensure.launcher_present_before required |
boolean | |
ensure.launcher_repaired required |
boolean | |
ensure.manifest_repaired required |
boolean | |
ensure.manifest_state required |
string | The native host manifest state before repair - current, stale, missing, or unreadable. |
ensure.native_host required |
string | Which native host the launcher runs after the ensure. Values: |
ensure.native_host_fallback required |
string | null | Set when the installed Rust host was no longer executable and the launcher was (or, in a dry run, would be) put back to the Python host. |
ensure.ok required |
boolean | Connected, or a dry run (which reports only). |
ensure.person_step required |
string | The one remaining person step when the run did not connect. |
ensure.policy_present required |
boolean | |
ensure.profile_directory required |
string | |
ensure.skew_detail required |
string | null | The re-probe's skew sentence, when the socket answered but the version or verbs are short of this Blowhorn. |
ensure.store_outdated required |
boolean | |
ensure.store_version required |
string | null | The mapped profile's Chrome Web Store install version, when it has one. |
ensure.unpacked required |
string | null | The mapped profile's loaded-unpacked install version, when it has one. |
ensure.unpacked_manifest_present required |
boolean | |
ensure.unpacked_present required |
boolean | |
extension_dir required |
string | Path of the unpacked extension to load. |
extension_id |
string | |
launcher required |
string | Path of the native host launcher. |
manifest required |
string | Path of the native messaging host manifest. |
native_host required |
string | Which native host the launcher runs (or, in a dry run, would run). python is the default; rust only after --native-host rust, kept while its binary is still executable. Values: |
native_host_fallback required |
string | null | The sentence saying the installed Rust host is missing or not executable and the launcher runs the Python host again; null otherwise. |
ok required |
boolean | |
profiles |
array of object | Per Chrome profile outcome of an all-profiles install. |
profiles[].account required |
string | |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].result required |
string | |
profiles_error |
string | null | |
profiles_scope |
string | |
route |
string | Present for an all-profiles install. Values: |
targets |
array of object | Present for a one-profile install (--profile or --chrome-profile). |
targets[].account required |
string | |
targets[].directory required |
string | |
targets[].display_name required |
string | |
targets[].profile required |
string | null | |
targets[].result required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeExtensionInstallResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
ensure |
object | Present for an --ensure run. The on-demand ensure report - what was repaired, what is registered, whether hello re-probed connected, and the one remaining person step when it did not. Never claims a store update - a store install too old for this Blowhorn is reported with Chrome's own update step in person_step. |
ensure.bridge_held required |
boolean | Another run may hold the bridge; no ensure ran. |
ensure.connected required |
boolean | Hello re-probed connected after the ensure. |
ensure.connection_detail required |
string | null | |
ensure.data_dir required |
string | |
ensure.dry_run required |
boolean | |
ensure.external_path required |
string | |
ensure.external_present required |
boolean | |
ensure.external_present_before required |
boolean | The external-extensions file was already there before the ensure. |
ensure.external_refusal required |
string | null | Why the external-extensions file could not be written, when it could not. |
ensure.external_written required |
boolean | |
ensure.failure_sentence required |
string | Empty when connected; otherwise what was repaired plus the person step. |
ensure.host_refusal required |
string | null | Why the native host could not be written, when it could not. |
ensure.launcher_present_before required |
boolean | |
ensure.launcher_repaired required |
boolean | |
ensure.manifest_repaired required |
boolean | |
ensure.manifest_state required |
string | The native host manifest state before repair - current, stale, missing, or unreadable. |
ensure.native_host required |
string | Which native host the launcher runs after the ensure. Values: |
ensure.native_host_fallback required |
string | null | Set when the installed Rust host was no longer executable and the launcher was (or, in a dry run, would be) put back to the Python host. |
ensure.ok required |
boolean | Connected, or a dry run (which reports only). |
ensure.person_step required |
string | The one remaining person step when the run did not connect. |
ensure.policy_present required |
boolean | |
ensure.profile_directory required |
string | |
ensure.skew_detail required |
string | null | The re-probe's skew sentence, when the socket answered but the version or verbs are short of this Blowhorn. |
ensure.store_outdated required |
boolean | |
ensure.store_version required |
string | null | The mapped profile's Chrome Web Store install version, when it has one. |
ensure.unpacked required |
string | null | The mapped profile's loaded-unpacked install version, when it has one. |
ensure.unpacked_manifest_present required |
boolean | |
ensure.unpacked_present required |
boolean | |
extension_dir required |
string | Path of the unpacked extension to load. |
extension_id |
string | |
launcher required |
string | Path of the native host launcher. |
manifest required |
string | Path of the native messaging host manifest. |
native_host required |
string | Which native host the launcher runs (or, in a dry run, would run). python is the default; rust only after --native-host rust, kept while its binary is still executable. Values: |
native_host_fallback required |
string | null | The sentence saying the installed Rust host is missing or not executable and the launcher runs the Python host again; null otherwise. |
ok required |
boolean | |
profiles |
array of object | Per Chrome profile outcome of an all-profiles install. |
profiles[].account required |
string | |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].result required |
string | |
profiles_error |
string | null | |
profiles_scope |
string | |
route |
string | Present for an all-profiles install. Values: |
targets |
array of object | Present for a one-profile install (--profile or --chrome-profile). |
targets[].account required |
string | |
targets[].directory required |
string | |
targets[].display_name required |
string | |
targets[].profile required |
string | null | |
targets[].result required |
string |
POST
/cli/chrome/extension-mode
Show or set how runs drive the Blowhorn Chrome extension. background is the default. Both modes open Blowhorn's tab inactive beside the tab you are on (or in its own window with --window own-window), in a Blowhorn group that is collapsed by default, and take a screenshot through the debugger without selecting the tab. Nothing brings the tab forward. classic keeps focus emulation on only while an action holds the debugger and sends input through the page. background turns focus emulation on from the first page and sends trusted input. With no MODE it reports the mode a run started now would use and where it comes from: the BLOWHORN_EXTENSION_ROLLBACK override, your Blowhorn account setting, or the background default. A job whose driver mode is extension-classic or extension-background outranks the account setting for that job. Setting MODE writes extensionMode to your account's Blowhorn preferences in your Blowhorn account (sign in first with 'blowhorn auth login'); default clears it. --group collapsed or open writes chromeGroup the same way: how the Blowhorn tab group sits beside your tabs between steps. Collapsed is the default. --window side-tab or own-window writes chromeWindow the same way: where Blowhorn works. side-tab is an inactive tab beside your tabs, the default. own-window is one window of its own, mostly behind yours with an edge showing, opened without focus and reused for the browser session; on a Mac keep that edge showing, since a window fully covered, minimized, hidden or on another desktop reads like a background tab. --json emits the report as one object, for a script or the desktop app.
Show or set how runs drive the Blowhorn Chrome extension. background is the default. Both modes open Blowhorn's tab inactive beside the tab you are on (or in its own window with --window own-window), in a Blowhorn group that is collapsed by default, and take a screenshot through the debugger without selecting the tab. Nothing brings the tab forward. classic keeps focus emulation on only while an action holds the debugger and sends input through the page. background turns focus emulation on from the first page and sends trusted input. With no MODE it reports the mode a run started now would use and where it comes from: the BLOWHORN_EXTENSION_ROLLBACK override, your Blowhorn account setting, or the background default. A job whose driver mode is extension-classic or extension-background outranks the account setting for that job. Setting MODE writes extensionMode to your account's Blowhorn preferences in your Blowhorn account (sign in first with 'blowhorn auth login'); default clears it. --group collapsed or open writes chromeGroup the same way: how the Blowhorn tab group sits beside your tabs between steps. Collapsed is the default. --window side-tab or own-window writes chromeWindow the same way: where Blowhorn works. side-tab is an inactive tab beside your tabs, the default. own-window is one window of its own, mostly behind yours with an edge showing, opened without focus and reused for the browser session; on a Mac keep that edge showing, since a window fully covered, minimized, hidden or on another desktop reads like a background tab. --json emits the report as one object, for a script or the desktop app.
Request body required
The flags and arguments of `blowhorn chrome extension-mode`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
extension_mode |
string | classic, background, or default to clear the account setting; omit to show |
group |
string | collapsed or open: how the Blowhorn tab group sits beside your tabs between steps. default clears the account setting (collapsed) |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome extension-mode' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome extension-mode' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
window |
string | side-tab or own-window: where Blowhorn works, in an inactive tab beside your tabs or in a window of its own, mostly behind yours with an edge showing, that never takes focus (on a Mac, keep that edge showing). default clears the account setting (side-tab) |
Responses
200
What the command prints to stdout with --json when it exits 0.
What chrome extension-mode prints with --json: the extension mode a run started now would use, where it comes from, the account setting, the rollback override, each feature on or off, the tab group setting and where Blowhorn works (the driving window). With MODE, --group or --window, written says the account setting was saved to your Blowhorn account.
| Name | Type | Description |
|---|---|---|
account required |
string | null | The extensionMode account preference, or null when it is not set. Values: |
account_error required |
string | null | Why the account setting could not be read from your Blowhorn account; account is then the cached setting a run falls back to. |
features required |
object | |
features.checkpoint_front required |
boolean | |
features.first_page required |
boolean | |
features.lease_at_goto required |
boolean | |
features.placement required |
boolean | |
features.quiet_screenshot required |
boolean | |
features.trusted_input required |
boolean | |
group required |
string | How the Blowhorn tab group sits beside your tabs between steps. Collapsed unless the account says open. Values: |
group_account required |
string | null | The chromeGroup account preference, or null when it is not set. Values: |
mode required |
string | Values: |
ok required |
boolean | |
rollback required |
string | null | The raw BLOWHORN_EXTENSION_ROLLBACK value, or null when it is unset. |
rollback_error required |
string | null | Why that value is refused; a run would refuse to start with the same sentence. |
source required |
string | Which layer decided. A job's driver mode is a fourth layer that only a running job has. Values: |
window required |
string | Where Blowhorn works in Chrome. side_tab is an inactive tab beside your tabs. own_window is a window of its own, mostly behind yours with an edge showing, which never takes focus. side_tab unless the account says own_window. Values: |
window_account required |
string | null | The chromeWindow account preference, or null when it is not set. Values: |
written required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeExtensionModeResponse
| Name | Type | Description |
|---|---|---|
account required |
string | null | The extensionMode account preference, or null when it is not set. Values: |
account_error required |
string | null | Why the account setting could not be read from your Blowhorn account; account is then the cached setting a run falls back to. |
features required |
object | |
features.checkpoint_front required |
boolean | |
features.first_page required |
boolean | |
features.lease_at_goto required |
boolean | |
features.placement required |
boolean | |
features.quiet_screenshot required |
boolean | |
features.trusted_input required |
boolean | |
group required |
string | How the Blowhorn tab group sits beside your tabs between steps. Collapsed unless the account says open. Values: |
group_account required |
string | null | The chromeGroup account preference, or null when it is not set. Values: |
mode required |
string | Values: |
ok required |
boolean | |
rollback required |
string | null | The raw BLOWHORN_EXTENSION_ROLLBACK value, or null when it is unset. |
rollback_error required |
string | null | Why that value is refused; a run would refuse to start with the same sentence. |
source required |
string | Which layer decided. A job's driver mode is a fourth layer that only a running job has. Values: |
window required |
string | Where Blowhorn works in Chrome. side_tab is an inactive tab beside your tabs. own_window is a window of its own, mostly behind yours with an edge showing, which never takes focus. side_tab unless the account says own_window. Values: |
window_account required |
string | null | The chromeWindow account preference, or null when it is not set. Values: |
written required |
boolean |
POST
/cli/chrome/extension-status
Report whether the Blowhorn Chrome extension is installed and connected: the unpacked extension directory, the native-host manifest (missing, current, or stale against this checkout's extension/manifest.json), the launcher, and whether the Chrome extension answers hello on its socket. An answered hello proves the extension is loaded in the real Chrome and talking; this command navigates nowhere, probes no tab, and contacts no platform. A manifest whose launcher is gone is not installed. The report also says which install route is on disk: policy (ExtensionInstallForcelist, every Chrome profile, Managed by your organization), external (Chrome offers the install in each profile), or none. That read writes nothing. Which Chrome profile accepted that install is not something this command can see. It does name each Chrome profile whose Secure Preferences or Preferences record a Chrome Web Store install of the extension, with its version, and tells a store install too old for this Blowhorn to update in chrome://extensions, never to load it unpacked. The one thing written is a lock beside each Chrome profile's own socket, in Blowhorn's directory (~/Library/Application Support/Blowhorn on macOS, ~/.local/share/blowhorn on Linux), which every Blowhorn client holds while it talks to that profile's Chrome extension: while a run holds one profile's lock, that profile's hello is not written, is reported not checked, and --json carries bridge_busy: true with a detail saying a run may be holding the bridge; another profile's run never waits on it. --json emits the report as one object, for a script or the desktop app.
Report whether the Blowhorn Chrome extension is installed and connected: the unpacked extension directory, the native-host manifest (missing, current, or stale against this checkout's extension/manifest.json), the launcher, and whether the Chrome extension answers hello on its socket. An answered hello proves the extension is loaded in the real Chrome and talking; this command navigates nowhere, probes no tab, and contacts no platform. A manifest whose launcher is gone is not installed. The report also says which install route is on disk: policy (ExtensionInstallForcelist, every Chrome profile, Managed by your organization), external (Chrome offers the install in each profile), or none. That read writes nothing. Which Chrome profile accepted that install is not something this command can see. It does name each Chrome profile whose Secure Preferences or Preferences record a Chrome Web Store install of the extension, with its version, and tells a store install too old for this Blowhorn to update in chrome://extensions, never to load it unpacked. The one thing written is a lock beside each Chrome profile's own socket, in Blowhorn's directory (~/Library/Application Support/Blowhorn on macOS, ~/.local/share/blowhorn on Linux), which every Blowhorn client holds while it talks to that profile's Chrome extension: while a run holds one profile's lock, that profile's hello is not written, is reported not checked, and --json carries bridge_busy: true with a detail saying a run may be holding the bridge; another profile's run never waits on it. --json emits the report as one object, for a script or the desktop app.
Request body required
The flags and arguments of `blowhorn chrome extension-status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome extension-status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome extension-status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints whether the Blowhorn Chrome extension is installed (native-messaging manifest, launcher, policy, unpacked directory) and whether it answers the bridge.
| Name | Type | Description |
|---|---|---|
bridge_busy required |
boolean | |
connected required |
boolean | |
connection_detail required |
string | null | |
extension_id required |
string | |
external_installed required |
boolean | |
external_path required |
string | |
installed required |
boolean | |
launcher_path required |
string | |
launcher_present required |
boolean | |
manifest_detail required |
string | null | |
manifest_path required |
string | |
manifest_state required |
string | The manifest's state, such as current, stale, missing or invalid. |
native_host_fallback required |
string | null | The sentence saying the installed Rust host is missing or not executable and the launcher runs the Python host again; null otherwise. |
ok required |
any | |
policy_error required |
string | null | |
policy_installed required |
boolean | |
policy_path required |
string | |
profile_states required |
array of object | The extension's state in each Chrome profile (Default and Profile N directories with a Preferences file), read from that profile's Secure Preferences or Preferences. enabled is on; disabled is in the profile and turned off (how the external route adds it until you turn it on); removed is an external add you removed; absent is nothing recorded yet. |
profile_states[].profile required |
string | |
profile_states[].state required |
string | Values: |
profile_states[].version required |
string | null | |
requirements required |
array of object | Engine-owned activity and platform prerequisites used by New order. |
requirements[].activity required |
string | |
requirements[].platform required |
string | |
requirements[].reason required |
string | |
requirements[].requirement required |
string | Values: |
route required |
string | null | How the extension reaches Chrome (policy, external or unpacked); null when none is installed. |
unpacked_dir required |
string | |
unpacked_present required |
boolean | |
webstore_profiles required |
array of object | Chrome profiles that installed the extension from the web store. |
webstore_profiles[].profile required |
string | |
webstore_profiles[].version required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeExtensionStatusResponse
| Name | Type | Description |
|---|---|---|
bridge_busy required |
boolean | |
connected required |
boolean | |
connection_detail required |
string | null | |
extension_id required |
string | |
external_installed required |
boolean | |
external_path required |
string | |
installed required |
boolean | |
launcher_path required |
string | |
launcher_present required |
boolean | |
manifest_detail required |
string | null | |
manifest_path required |
string | |
manifest_state required |
string | The manifest's state, such as current, stale, missing or invalid. |
native_host_fallback required |
string | null | The sentence saying the installed Rust host is missing or not executable and the launcher runs the Python host again; null otherwise. |
ok required |
any | |
policy_error required |
string | null | |
policy_installed required |
boolean | |
policy_path required |
string | |
profile_states required |
array of object | The extension's state in each Chrome profile (Default and Profile N directories with a Preferences file), read from that profile's Secure Preferences or Preferences. enabled is on; disabled is in the profile and turned off (how the external route adds it until you turn it on); removed is an external add you removed; absent is nothing recorded yet. |
profile_states[].profile required |
string | |
profile_states[].state required |
string | Values: |
profile_states[].version required |
string | null | |
requirements required |
array of object | Engine-owned activity and platform prerequisites used by New order. |
requirements[].activity required |
string | |
requirements[].platform required |
string | |
requirements[].reason required |
string | |
requirements[].requirement required |
string | Values: |
route required |
string | null | How the extension reaches Chrome (policy, external or unpacked); null when none is installed. |
unpacked_dir required |
string | |
unpacked_present required |
boolean | |
webstore_profiles required |
array of object | Chrome profiles that installed the extension from the web store. |
webstore_profiles[].profile required |
string | |
webstore_profiles[].version required |
string | null |
POST
/cli/chrome/map
Map a Blowhorn profile to one of this machine's Chrome profiles, by the Chrome profile's directory name as 'blowhorn chrome profiles' prints it ('Default', 'Profile 12'). The command checks that the Blowhorn profile is in the store, that Chrome has that directory (Chrome silently creates a new, empty profile for a directory it does not know, so a typo is refused rather than recorded), and that the directory is not already mapped to another Blowhorn profile on this machine; then it records the display name and signed-in account Chrome shows for the directory and prints what it recorded. A Blowhorn profile may have several Chrome profiles mapped on one machine (Work and Personal, say), so mapping another directory adds it; mapping a directory the profile already has refreshes the recorded identity, which is how a drifted name or account is confirmed. A Chrome profile belongs to one Blowhorn profile per machine. --replace removes the profile's other mappings on this machine in the same write.
Map a Blowhorn profile to one of this machine's Chrome profiles, by the Chrome profile's directory name as 'blowhorn chrome profiles' prints it ('Default', 'Profile 12'). The command checks that the Blowhorn profile is in the store, that Chrome has that directory (Chrome silently creates a new, empty profile for a directory it does not know, so a typo is refused rather than recorded), and that the directory is not already mapped to another Blowhorn profile on this machine; then it records the display name and signed-in account Chrome shows for the directory and prints what it recorded. A Blowhorn profile may have several Chrome profiles mapped on one machine (Work and Personal, say), so mapping another directory adds it; mapping a directory the profile already has refreshes the recorded identity, which is how a drifted name or account is confirmed. A Chrome profile belongs to one Blowhorn profile per machine. --replace removes the profile's other mappings on this machine in the same write.
Which Chrome profile a run uses, per platform: the one mapped --for that platform; else the profile's only mapping; else its one mapping with no --for (its default, which serves every platform no other mapping names). With several mappings and none of those, the run refuses by name. --for linkedin,x chooses this Chrome profile for those platforms and takes them off the profile's other mappings here; --default clears this mapping's platforms; neither keeps what the mapping had.
A mapping is identity, never permission: it does not make a profile eligible for anything; every browser run reads it to find the profile's Chrome and refuses a profile without one.
Mapping also pairs the profile: once the mapping is recorded the command asks Chrome to open a window on the Blowhorn extension's pairing page in that Chrome profile, carrying a token made from this machine's pairing key (chrome_pairing.key in Blowhorn's launcher directory, created on first use and readable only by you). The page keeps the token in the extension's storage, and a run through the extension refuses before any action unless the extension it reaches reports this profile's token. Mapping a profile to the directory it already has pairs it again. When Chrome cannot be asked to open the page the mapping still stands and the command says why and what to run. --json adds 'pairing': opened (whether Chrome was asked to open the page) and detail (why not, or null). With --dry-run the command checks everything, records nothing and opens no pairing page. Each refusal is one sentence and exit 1; a store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn chrome map`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
default |
boolean | Name no platform for this mapping: it serves every platform no other mapping of the profile names Default: |
directory required |
string | The Chrome profile directory, exactly as 'blowhorn chrome profiles' prints it ('Default', 'Profile 12') |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
for_platforms |
array of string | The platforms this Chrome profile is the one for (comma-separated or repeated: linkedin,x); they move off the profile's other mappings on this machine |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | The Blowhorn profile, as the store's profiles name it (company) |
pace |
string | Accepted by 'blowhorn chrome map' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
replace |
boolean | Remove the profile's other Chrome profile mappings on this machine in the same write Default: |
risk_profile |
string | Accepted by 'blowhorn chrome map' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the mapping `chrome map` recorded (or, with `--dry-run`, would record) between a Blowhorn profile and a Chrome profile directory. `replaced` is the earlier mapping of the same directory, if any (mapping it again confirms a drifted name or account); `removed` lists the profile's other mappings on this machine that `--replace` dropped. A recorded mapping also carries `pairing`: whether the extension's pairing page was opened in that Chrome profile; a dry run opens nothing and has no `pairing`.
| Name | Type | Description |
|---|---|---|
binding required |
ChromeBinding | A Blowhorn profile mapped to a Chrome profile directory on one machine. A mapping is identity, not permission. A profile may map several Chrome profiles on one machine; a Chrome profile belongs to at most one Blowhorn profile there. A `--dry-run` preview carries only the first four fields and `platforms`. |
binding.account required |
string | null | |
binding.created_at |
string | null | |
binding.directory required |
string | The Chrome profile directory name, such as Profile 5. |
binding.display_name required |
string | null | |
binding.host |
string | |
binding.platforms |
array of string | The platforms a run uses this Chrome profile for. Empty makes it the profile's default: it serves every platform none of the profile's other Chrome profiles on this machine names. |
binding.profile required |
string | The Blowhorn profile slug. |
binding.subject_id |
string | The profile's subject uuid. |
binding.updated_at |
string | null | |
dry_run required |
boolean | |
host required |
string | |
ok required |
any | |
pairing |
object | |
pairing.detail required |
string | null | Why the page was not opened, or null. |
pairing.opened required |
boolean | Chrome was asked to open the pairing page in the mapped Chrome profile. Pairing is done once the page says so. |
removed required |
array of ChromeBinding | |
removed[].account required |
string | null | |
removed[].created_at |
string | null | |
removed[].directory required |
string | The Chrome profile directory name, such as Profile 5. |
removed[].display_name required |
string | null | |
removed[].host |
string | |
removed[].platforms |
array of string | The platforms a run uses this Chrome profile for. Empty makes it the profile's default: it serves every platform none of the profile's other Chrome profiles on this machine names. |
removed[].profile required |
string | The Blowhorn profile slug. |
removed[].subject_id |
string | The profile's subject uuid. |
removed[].updated_at |
string | null | |
replaced required |
one of ChromeBinding, null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeMapResponse
| Name | Type | Description |
|---|---|---|
binding required |
ChromeBinding | A Blowhorn profile mapped to a Chrome profile directory on one machine. A mapping is identity, not permission. A profile may map several Chrome profiles on one machine; a Chrome profile belongs to at most one Blowhorn profile there. A `--dry-run` preview carries only the first four fields and `platforms`. |
binding.account required |
string | null | |
binding.created_at |
string | null | |
binding.directory required |
string | The Chrome profile directory name, such as Profile 5. |
binding.display_name required |
string | null | |
binding.host |
string | |
binding.platforms |
array of string | The platforms a run uses this Chrome profile for. Empty makes it the profile's default: it serves every platform none of the profile's other Chrome profiles on this machine names. |
binding.profile required |
string | The Blowhorn profile slug. |
binding.subject_id |
string | The profile's subject uuid. |
binding.updated_at |
string | null | |
dry_run required |
boolean | |
host required |
string | |
ok required |
any | |
pairing |
object | |
pairing.detail required |
string | null | Why the page was not opened, or null. |
pairing.opened required |
boolean | Chrome was asked to open the pairing page in the mapped Chrome profile. Pairing is done once the page says so. |
removed required |
array of ChromeBinding | |
removed[].account required |
string | null | |
removed[].created_at |
string | null | |
removed[].directory required |
string | The Chrome profile directory name, such as Profile 5. |
removed[].display_name required |
string | null | |
removed[].host |
string | |
removed[].platforms |
array of string | The platforms a run uses this Chrome profile for. Empty makes it the profile's default: it serves every platform none of the profile's other Chrome profiles on this machine names. |
removed[].profile required |
string | The Blowhorn profile slug. |
removed[].subject_id |
string | The profile's subject uuid. |
removed[].updated_at |
string | null | |
replaced required |
one of ChromeBinding, null |
POST
/cli/chrome/open-profile
Open a window in the Chrome profile a Blowhorn profile is mapped to on this machine, so you can look at it or sign in to a platform there yourself. The mapping decides which Chrome profile opens, by the same rule a run uses: with --sign-in, the mapping chosen for that platform; without it, the profile's only mapping or its default one. A profile with no mapping here, a mapping Chrome no longer has, or a name or account that drifted since the mapping was made is refused by name and nothing opens -- a mapping is identity, never a guess at another profile. With --sign-in <platform> the window opens on that platform's sign-in page; without it, on a new tab. Blowhorn asks Chrome to open the window and steps back: it drives nothing in it, attaches nothing to it, and never restarts or closes Chrome -- a Chrome already running just gains the window. If no Chrome is running, Chrome starts to open the window and is left running. Writes nothing. After you sign in there, 'blowhorn chrome discover' sees the sign-in and records it as the profile's account.
Open a window in the Chrome profile a Blowhorn profile is mapped to on this machine, so you can look at it or sign in to a platform there yourself. The mapping decides which Chrome profile opens, by the same rule a run uses: with --sign-in, the mapping chosen for that platform; without it, the profile's only mapping or its default one. A profile with no mapping here, a mapping Chrome no longer has, or a name or account that drifted since the mapping was made is refused by name and nothing opens -- a mapping is identity, never a guess at another profile. With --sign-in <platform> the window opens on that platform's sign-in page; without it, on a new tab. Blowhorn asks Chrome to open the window and steps back: it drives nothing in it, attaches nothing to it, and never restarts or closes Chrome -- a Chrome already running just gains the window. If no Chrome is running, Chrome starts to open the window and is left running. Writes nothing. After you sign in there, 'blowhorn chrome discover' sees the sign-in and records it as the profile's account.
Request body required
The flags and arguments of `blowhorn chrome open-profile`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | The Blowhorn profile, as the store's profiles name it (company) |
pace |
string | Accepted by 'blowhorn chrome open-profile' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome open-profile' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
sign_in_platform |
string | Open on this platform's sign-in page instead of a new tab, and choose the mapping by that platform, as a run would. One of linkedin, x, reddit, hn, slack, bluesky, github |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What chrome open-profile prints with --json: the Chrome profile the Blowhorn profile's mapping chose on this machine, the page the window opened on (a platform's sign-in page with --sign-in, else a new tab), and whether Chrome was asked to open it. Whether you then signed in is read by a later session check, never by this command. A refusal (no mapping, a drifted or missing directory, a platform with no sign-in page) prints a Problem instead.
| Name | Type | Description |
|---|---|---|
account required |
string | The signed-in account recorded when the mapping was made; empty when nobody was signed in. |
detail required |
string | null | Why the window did not open ('chrome could not open that chrome profile: <reason>'), or the dry-run note; null when it opened. |
directory required |
string | The Chrome profile directory the mapping chose, as chrome profiles prints it. |
display_name required |
string | The display name recorded when the mapping was made. |
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the window (always true on a dry run); false with exit 1 when it could not be asked. |
opened required |
boolean | Chrome was asked to open the window in this profile. |
platform required |
string | null | The --sign-in platform, or null when the window opened on a new tab. |
profile required |
string | The Blowhorn profile whose mapping was opened. |
url required |
string | The page the window opened on. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeOpenProfileResponse
| Name | Type | Description |
|---|---|---|
account required |
string | The signed-in account recorded when the mapping was made; empty when nobody was signed in. |
detail required |
string | null | Why the window did not open ('chrome could not open that chrome profile: <reason>'), or the dry-run note; null when it opened. |
directory required |
string | The Chrome profile directory the mapping chose, as chrome profiles prints it. |
display_name required |
string | The display name recorded when the mapping was made. |
dry_run required |
boolean | |
ok required |
boolean | Chrome was asked to open the window (always true on a dry run); false with exit 1 when it could not be asked. |
opened required |
boolean | Chrome was asked to open the window in this profile. |
platform required |
string | null | The --sign-in platform, or null when the window opened on a new tab. |
profile required |
string | The Blowhorn profile whose mapping was opened. |
url required |
string | The page the window opened on. |
POST
/cli/chrome/profiles
List every profile Chrome knows: its directory name (the identity a mapping uses), its display name, the signed-in Google account (blank when nobody is signed in), a marker on the profile Chrome used last, and the Blowhorn profile it is mapped to on this machine with whether that mapping's recorded name and account still match ('ok' or 'drift'). The profile list is read from 'Local State' in the Chrome data directory, beside 'DevToolsActivePort', and from no other file; the mapping is read from the store once; no browser opens.
List every profile Chrome knows: its directory name (the identity a mapping uses), its display name, the signed-in Google account (blank when nobody is signed in), a marker on the profile Chrome used last, and the Blowhorn profile it is mapped to on this machine with whether that mapping's recorded name and account still match ('ok' or 'drift'). The profile list is read from 'Local State' in the Chrome data directory, beside 'DevToolsActivePort', and from no other file; the mapping is read from the store once; no browser opens.
--json emits the same list as JSON with a stable shape: one object per profile with directory, display_name, account, gaia_name, last_used, mapped_to (a Blowhorn profile name or null) and identity ('ok', 'drift' or null when unmapped), plus host, for a script or the desktop app to read. When 'Local State' is missing or cannot be read the command says so in one line and exits 1; a store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn chrome profiles`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome profiles' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome profiles' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The Chrome profiles found under the Chrome data directory, each with the Blowhorn profile mapped to it on this machine, if any. Account names are real people's; never commit a real output.
| Name | Type | Description |
|---|---|---|
data_dir required |
string | |
host required |
string | The machine host the mappings are keyed by. |
last_used required |
string | null | Directory of the profile Chrome last used. |
local_state required |
object | |
local_state.detail required |
string | null | |
local_state.path required |
string | |
local_state.state required |
string | |
ok required |
boolean | |
profile_count required |
integer | |
profiles required |
array of object | |
profiles[].account required |
string | Signed-in Google account email, empty when nobody is signed in. |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].gaia_name required |
string | |
profiles[].identity required |
string | null | Whether the recorded identity of the mapping still holds (for example ok or drift); null when unmapped. |
profiles[].last_used required |
boolean | |
profiles[].mapped_platforms required |
array of string | The platforms this Chrome profile is chosen for; empty when it is the profile's default or unmapped. |
profiles[].mapped_to required |
string | null | The Blowhorn profile mapped to this Chrome profile on this host. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeProfilesResponse
| Name | Type | Description |
|---|---|---|
data_dir required |
string | |
host required |
string | The machine host the mappings are keyed by. |
last_used required |
string | null | Directory of the profile Chrome last used. |
local_state required |
object | |
local_state.detail required |
string | null | |
local_state.path required |
string | |
local_state.state required |
string | |
ok required |
boolean | |
profile_count required |
integer | |
profiles required |
array of object | |
profiles[].account required |
string | Signed-in Google account email, empty when nobody is signed in. |
profiles[].directory required |
string | |
profiles[].display_name required |
string | |
profiles[].gaia_name required |
string | |
profiles[].identity required |
string | null | Whether the recorded identity of the mapping still holds (for example ok or drift); null when unmapped. |
profiles[].last_used required |
boolean | |
profiles[].mapped_platforms required |
array of string | The platforms this Chrome profile is chosen for; empty when it is the profile's default or unmapped. |
profiles[].mapped_to required |
string | null | The Blowhorn profile mapped to this Chrome profile on this host. |
POST
/cli/chrome/status
Report whether Google Chrome is installed, whether it is running, whether its remote-debugging switch is on (chrome://inspect/#remote-debugging), the debugging port it recorded, how many profiles it has, and the launch mode a run would use right now: the configured setting (--chrome-launch, BLOWHORN_CHROME_LAUNCH, defaults.chrome.launch_mode, then the extension default), whether 'auto' would attach to this Chrome or launch Blowhorn's own, and why. When something is off, the last line says what to do next: where to turn the switch on, or that Chrome needs opening. Read-only: two files under the Chrome data directory and one process listing; no browser opens and nothing connects to Chrome.
Report whether Google Chrome is installed, whether it is running, whether its remote-debugging switch is on (chrome://inspect/#remote-debugging), the debugging port it recorded, how many profiles it has, and the launch mode a run would use right now: the configured setting (--chrome-launch, BLOWHORN_CHROME_LAUNCH, defaults.chrome.launch_mode, then the extension default), whether 'auto' would attach to this Chrome or launch Blowhorn's own, and why. When something is off, the last line says what to do next: where to turn the switch on, or that Chrome needs opening. Read-only: two files under the Chrome data directory and one process listing; no browser opens and nothing connects to Chrome.
--json emits the same report as JSON with a stable shape (installed, running, remote_debugging.enabled, remote_debugging.port, profile_count, last_used, ready, next_step, launch_mode.setting, launch_mode.mode, launch_mode.reason). The command exits 0 whenever it could report, whatever the report says.
Request body required
The flags and arguments of `blowhorn chrome status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints what Blowhorn can see of the operator's Chrome (the install and its version, whether it runs, the debugging port) and whether the configured launch mode is ready. Reads the app bundle's Info.plist and two files under Chrome's data directory, and nothing else.
| Name | Type | Description |
|---|---|---|
app_path required |
string | null | |
data_dir required |
string | |
devtools_port required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
devtools_port.detail required |
string | null | Why the file is not `ok`; null when it is. |
devtools_port.path required |
string | |
devtools_port.state required |
string | Values: |
installed required |
boolean | |
last_used required |
string | null | The Chrome profile directory used last. |
launch_mode required |
ChromeLaunchMode | How a run would reach Chrome right now, and whether that route is ready. |
launch_mode.forced required |
boolean | True unless the setting is `auto`. |
launch_mode.mode required |
string | The mode a run would use after `auto` is resolved. |
launch_mode.ready required |
boolean | |
launch_mode.reason required |
string | |
launch_mode.setting required |
string | The configured launch mode (extension, persistent, cdp or auto). |
launch_mode.source required |
string | Which layer chose the setting (flag, env, config or builtin). |
launch_mode_note required |
string | null | |
local_state required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
local_state.detail required |
string | null | Why the file is not `ok`; null when it is. |
local_state.path required |
string | |
local_state.state required |
string | Values: |
minimum_version required |
integer | The oldest Chrome major Blowhorn supports. |
next_step required |
string | null | |
ok required |
any | |
profile_count required |
integer | |
ready required |
boolean | |
remote_debugging required |
ChromeRemoteDebugging | Whether Chrome's remote-debugging server is up, read from `DevToolsActivePort`. Every field is null when the file said nothing. |
remote_debugging.browser_path required |
string | null | |
remote_debugging.enabled required |
boolean | null | |
remote_debugging.port required |
integer | null | |
running required |
boolean | |
supported required |
boolean | null | Whether version is at least minimum_version; null when the version is unknown. |
version required |
string | null | The Chrome app bundle's CFBundleShortVersionString; null when it could not be read. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeStatusResponse
| Name | Type | Description |
|---|---|---|
app_path required |
string | null | |
data_dir required |
string | |
devtools_port required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
devtools_port.detail required |
string | null | Why the file is not `ok`; null when it is. |
devtools_port.path required |
string | |
devtools_port.state required |
string | Values: |
installed required |
boolean | |
last_used required |
string | null | The Chrome profile directory used last. |
launch_mode required |
ChromeLaunchMode | How a run would reach Chrome right now, and whether that route is ready. |
launch_mode.forced required |
boolean | True unless the setting is `auto`. |
launch_mode.mode required |
string | The mode a run would use after `auto` is resolved. |
launch_mode.ready required |
boolean | |
launch_mode.reason required |
string | |
launch_mode.setting required |
string | The configured launch mode (extension, persistent, cdp or auto). |
launch_mode.source required |
string | Which layer chose the setting (flag, env, config or builtin). |
launch_mode_note required |
string | null | |
local_state required |
ChromeFileCheck | What one of Chrome's files (`Local State`, `DevToolsActivePort`) said, or why it could not be read. |
local_state.detail required |
string | null | Why the file is not `ok`; null when it is. |
local_state.path required |
string | |
local_state.state required |
string | Values: |
minimum_version required |
integer | The oldest Chrome major Blowhorn supports. |
next_step required |
string | null | |
ok required |
any | |
profile_count required |
integer | |
ready required |
boolean | |
remote_debugging required |
ChromeRemoteDebugging | Whether Chrome's remote-debugging server is up, read from `DevToolsActivePort`. Every field is null when the file said nothing. |
remote_debugging.browser_path required |
string | null | |
remote_debugging.enabled required |
boolean | null | |
remote_debugging.port required |
integer | null | |
running required |
boolean | |
supported required |
boolean | null | Whether version is at least minimum_version; null when the version is unknown. |
version required |
string | null | The Chrome app bundle's CFBundleShortVersionString; null when it could not be read. |
POST
/cli/chrome/suggest
Print a proposed mapping from every Blowhorn profile in the store to a Chrome profile on this machine, matched by display name (a Chrome profile named 'Company' proposes for the Blowhorn profile company), and apply nothing. Each row says why: 'name match', 'none' when no Chrome profile matches, 'ambiguous' when two match, 'already mapped' for a profile that has a mapping, and the holder's name when the matching directory is mapped to someone else. Apply a row you agree with by running the 'blowhorn chrome map' line it prints.
Print a proposed mapping from every Blowhorn profile in the store to a Chrome profile on this machine, matched by display name (a Chrome profile named 'Company' proposes for the Blowhorn profile company), and apply nothing. Each row says why: 'name match', 'none' when no Chrome profile matches, 'ambiguous' when two match, 'already mapped' for a profile that has a mapping, and the holder's name when the matching directory is mapped to someone else. Apply a row you agree with by running the 'blowhorn chrome map' line it prints.
--json emits the same rows as JSON. A store that cannot be reached is the named cause and exit 3.
Request body required
The flags and arguments of `blowhorn chrome suggest`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn chrome suggest' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome suggest' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints, for each Blowhorn profile, the Chrome profile directory whose signed-in account looks like it, and whether the profile is already mapped. Nothing is written.
| Name | Type | Description |
|---|---|---|
data_dir required |
string | |
host required |
string | |
ok required |
any | |
suggestions required |
array of object | |
suggestions[].account required |
string | null | |
suggestions[].directory required |
string | null | |
suggestions[].display_name required |
string | null | |
suggestions[].identity required |
string | null | Whether the mapped directory still shows the expected account (ok, drift, missing or unknown); null when unmapped. |
suggestions[].mapped required |
boolean | |
suggestions[].note required |
string | null | |
suggestions[].profile required |
string | The profile's name. |
suggestions[].profile_id required |
string (uuid) | The profile's id, its identity; the name can change, the id never does. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeSuggestResponse
| Name | Type | Description |
|---|---|---|
data_dir required |
string | |
host required |
string | |
ok required |
any | |
suggestions required |
array of object | |
suggestions[].account required |
string | null | |
suggestions[].directory required |
string | null | |
suggestions[].display_name required |
string | null | |
suggestions[].identity required |
string | null | Whether the mapped directory still shows the expected account (ok, drift, missing or unknown); null when unmapped. |
suggestions[].mapped required |
boolean | |
suggestions[].note required |
string | null | |
suggestions[].profile required |
string | The profile's name. |
suggestions[].profile_id required |
string (uuid) | The profile's id, its identity; the name can change, the id never does. |
POST
/cli/chrome/unmap
Remove one of a Blowhorn profile's Chrome profile mappings on this machine. Name the Chrome profile directory when the profile has more than one mapped here; with one, it may be left out. Chrome is not read and nothing under its directory is touched: only the store's row goes, so a mapping can be removed when 'Local State' is missing or unreadable. A profile with no mapping on this machine, or several and no directory named, is a one-sentence refusal and exit 1; with --dry-run the command says what would be removed and removes nothing.
Remove one of a Blowhorn profile's Chrome profile mappings on this machine. Name the Chrome profile directory when the profile has more than one mapped here; with one, it may be left out. Chrome is not read and nothing under its directory is touched: only the store's row goes, so a mapping can be removed when 'Local State' is missing or unreadable. A profile with no mapping on this machine, or several and no directory named, is a one-sentence refusal and exit 1; with --dry-run the command says what would be removed and removes nothing.
Request body required
The flags and arguments of `blowhorn chrome unmap`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
directory |
string | The Chrome profile directory to unmap, as 'blowhorn chrome profiles' prints it; needed only when the profile has more than one mapped on this machine |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
name required |
string | The Blowhorn profile, as the store's profiles name it (company) |
pace |
string | Accepted by 'blowhorn chrome unmap' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn chrome': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn chrome unmap' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The one mapping chrome unmap removed from this machine, or, with --dry-run, would remove. A profile with several mappings names the directory to unmap.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
host required |
string | |
ok required |
boolean | |
removed required |
object | |
removed.account required |
string | |
removed.created_at required |
string | null | |
removed.directory required |
string | |
removed.display_name required |
string | |
removed.host required |
string | |
removed.platforms required |
array of string | |
removed.profile required |
string | |
removed.subject_id required |
string | |
removed.updated_at required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ChromeUnmapResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
host required |
string | |
ok required |
boolean | |
removed required |
object | |
removed.account required |
string | |
removed.created_at required |
string | null | |
removed.directory required |
string | |
removed.display_name required |
string | |
removed.host required |
string | |
removed.platforms required |
array of string | |
removed.profile required |
string | |
removed.subject_id required |
string | |
removed.updated_at required |
string | null |
Auth
POST
/cli/auth/anonymous-usage-stats
Show or set whether your account shares anonymous usage data for product improvement.
Show or set whether your account shares anonymous usage data for product improvement.
Reads and writes anonymousUsageStats in your Blowhorn account's preferences (GET /api/identity/users/profile, PUT /api/identity/users/preferences with that one field, so no other preference changes). It belongs to the account, so every computer you use Blowhorn on follows it. Not set reads as on. Usage data is anonymous counts of what Blowhorn does; it never carries a post's text, a message, a credential or the contents of a page Blowhorn drives. Failure reports are a separate setting in the desktop app. With no flag it reads your account and always exits 0: signed out or unreachable, it reports the setting last read for this account (or on) and says why. --on and --off save the setting and exit 1 when your account cannot be written. --dry-run reads and sends nothing.
Request body required
The flags and arguments of `blowhorn auth anonymous-usage-stats`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn auth anonymous-usage-stats' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn auth': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn auth anonymous-usage-stats' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
usage_off |
boolean | Stop sharing anonymous usage data (saved to your account). Default: |
usage_on |
boolean | Share anonymous usage data for product improvement (saved to your account). Default: |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What auth anonymous-usage-stats prints with --json: whether your account shares anonymous usage data for product improvement, the anonymousUsageStats preference as stored, and where the value came from. Not set reads as on. With --on or --off, written says the setting was saved to your Blowhorn account; a write that fails prints ok false and the reason. With --dry-run and --on or --off, nothing is sent: written is false, dry_run is true and would_set is the value that would have been written.
object
| Name | Type | Description |
|---|---|---|
account required |
boolean | null | The anonymousUsageStats account preference, or null when it is not set. |
account_error required |
string | null | Why your account could not be read; account is then the value last read for this account, or null. |
anonymousUsageStats required |
boolean | Whether usage data may be shared. True unless the account says false. |
dry_run |
any | Present only with --dry-run and --on or --off; nothing was sent. |
ok required |
any | |
source required |
string | account when the account holds a value, default when it is not set (or could not be read and nothing was cached). Values: |
would_set |
boolean | Present only with --dry-run; the anonymousUsageStats value --on or --off would have written. |
written required |
boolean |
object
| Name | Type | Description |
|---|---|---|
error required |
string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AuthAnonymousUsageStatsResponse
POST
/cli/auth/login
Sign in to Blowhorn at https://cloud.blowhorn.ai in your browser.
Sign in to Blowhorn at https://cloud.blowhorn.ai in your browser.
Opens https://cloud.blowhorn.ai, where you sign in or create an account and allow the Blowhorn app, then returns here and keeps the token in the macOS Keychain (elsewhere a file only you can read beside cloud.yaml). It uses Blowhorn's native client sign-in: the browser answers on 127.0.0.1 with a one-time code this command redeems with a PKCE verifier; there is no client secret, and the token is never printed. After sign-in it reads your plan once; an account without a Blowhorn plan is sent straight to https://cloud.blowhorn.ai to start the beta. --no-browser prints the sign-in address instead of opening it; --json opens no browser either, its first line names the address to open (the desktop app runs --no-browser --json and opens it itself). --dry-run names where it would sign in and opens, waits for and saves nothing.
Request body required
The flags and arguments of `blowhorn auth login`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
no_browser |
boolean | Print the sign-in address and wait for the answer on 127.0.0.1 without opening a browser (--json implies it). Default: |
pace |
string | Accepted by 'blowhorn auth login' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn auth': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn auth login' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
sign_up |
boolean | Open https://cloud.blowhorn.ai to create a new account instead of signing in; it comes back the same way. Default: |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What auth login prints on success. With --json and a real sign-in, stdout is a JSON Lines stream, not a single JSON document: a one-line {"event":"authorize"} object (not covered here) comes first, so the desktop app, which runs --no-browser, can open the browser when it appears, then this payload as the second line. A consumer must read stdout line by line, one JSON object per line. --dry-run prints the dry-run shape and opens or saves nothing.
object
| Name | Type | Description |
|---|---|---|
display_name required |
string | null | |
entitled required |
boolean | null | Whether the plan grants Blowhorn; null when the plan could not be read. |
notice required |
string | null | Set when this organization has more computers registered than its plan allows. |
ok required |
boolean | |
organization_id required |
string | null | |
previous_token_id_unknown |
boolean | True when a token was kept but Blowhorn never learned its id or issuing server, so it could not be revoked and may remain in your Blowhorn account's token list. |
previous_token_revoked |
boolean | null | Whether the Blowhorn API token this sign-in replaced was revoked; null when none was kept. Also false when its id or issuing server is unknown (see previous_token_id_unknown). |
signed_in required |
boolean | |
start_url required |
string | null | Blowhorn address to start the beta, set only when entitled is false. |
token_store required |
string | Where the token was kept. The token itself is never printed. Values: |
update_prompt required |
string | null | Set when this build has to be updated before it can register this computer. |
object
| Name | Type | Description |
|---|---|---|
authorize_url required |
string | Blowhorn's native client consent page sign-in continues to. |
dry_run required |
boolean | |
login_url required |
string | The page the browser opens first - Blowhorn's sign-in page, or its sign-up page with --sign-up. |
ok required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AuthLoginResponse
POST
/cli/auth/logout
Sign out of Blowhorn and clear the local entitlement attestation cache.
Sign out of Blowhorn and clear the local entitlement attestation cache.
Removes the token 'blowhorn auth login' saved (Keychain item ai.blowhorn.cloud-token), the account it wrote to cloud.yaml (organization_id, display_name and the account facts), the process cache and the Keychain attestation item (ai.blowhorn.entitlement.v2). Unset BLOWHORN_CLOUD_TOKEN in the shell if it was exported.
Request body required
The flags and arguments of `blowhorn auth logout`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn auth logout' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn auth': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn auth logout' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Confirms the saved Blowhorn token, account and entitlement cache were removed (or, with --dry-run, would be), and whether the token was revoked; or why sign-out failed.
object
| Name | Type | Description |
|---|---|---|
message required |
string | |
ok required |
any | |
revoked required |
boolean | null | True when Blowhorn revoked the saved Blowhorn API token. False when it was kept but not revoked: Blowhorn refused, or does not know its id or issuing server, so it may remain in your Blowhorn account's token list (the message says which). Null when no token was kept, and on --dry-run, which revokes nothing. |
object
| Name | Type | Description |
|---|---|---|
error required |
string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AuthLogoutResponse
POST
/cli/auth/status
Show whether Blowhorn credentials and an entitlement attestation are present.
Show whether Blowhorn credentials and an entitlement attestation are present.
Reports token configured (never the token value), organization id, api_base, enforcement mode (BLOWHORN_ENTITLEMENT), and a summary of the last Feature read. Live, it first re-reads the signed-in account from Blowhorn (name, photo, organizations, plan) into cloud.yaml and follows the organization your Blowhorn session in the browser has selected; BLOWHORN_CLOUD_ORGANIZATION still wins when set, and the plan read and shown is that organization's. --offline answers from presence checks only: it reads no saved secret, so macOS shows no Keychain prompt, and it makes no network request (the desktop app runs it at launch). Exits 3 when this computer is not signed in: no token, a token Blowhorn refused, or Blowhorn could not be reached. --offline exits 3 only when no token is configured.
Request body required
The flags and arguments of `blowhorn auth status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
offline |
boolean | Check only whether a token and a saved plan are present; read no secret and make no request. Default: |
pace |
string | Accepted by 'blowhorn auth status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn auth': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn auth status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Blowhorn sign-in and entitlement state. The token itself is never printed, only whether one is configured. With --offline every field comes from presence checks: no saved secret is read (so macOS shows no Keychain prompt), no request is made, and attestation is null.
| Name | Type | Description |
|---|---|---|
account required |
object | null | Who is signed in, for the desktop app's account button and menu. Read from cloud.yaml, where sign-in caches it and a live auth status refreshes it from Blowhorn's own answers, each checked against the meshery/schemas construct Blowhorn consumes (schemas/meshery/consumed.yaml): User and its Preference from GET /api/identity/users/profile, AvailableOrganization from GET /api/identity/orgs, and the organization's Subscription and its Plan from /api/entitlement. So --offline answers it with no secret read and no request. Null when no token is configured. A field Blowhorn did not answer is null. The organization is the one organization_id names: the session's active organization (Preference.selectedOrganizationId when the account is still a member, else the first listed, the same choice the browser session makes), unless BLOWHORN_CLOUD_ORGANIZATION names another. |
account.avatar_url required |
string | null | User.avatarUrl (meshery/schemas v1beta2 user), the profile photo in your Blowhorn account. |
account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
account.organization_count required |
integer | null | How many live organizations the account belongs to, as GET /api/identity/orgs last counted them; more than one offers Switch organization. |
account.organization_name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization) of the organization this status reports. |
account.organizations required |
array of object | The account's live organizations (GET /api/identity/orgs, soft-deleted ones left out, up to Blowhorn's page of 100), in Blowhorn's order; Switch organization offers these. |
account.organizations[].description required |
string | null | AvailableOrganization.description (meshery/schemas v1beta2 organization), null when Cloud gave none; shown on hover beside the id to tell two organizations of the same name apart. |
account.organizations[].id required |
string | AvailableOrganization.id (meshery/schemas v1beta2 organization). |
account.organizations[].name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization). |
account.plan_name required |
string | null | Plan.name (meshery/schemas v1beta3 plan, the PlanName enum) of the organization this status reports, exactly as Blowhorn names it, and Free when Blowhorn answered with no current subscription (every organization is on Free unless one names another plan). Null only when it has never been read for that organization; the app then shows the organization alone. Values: |
api_base required |
string | null | |
attestation required |
object | null | The cached plan attestation, or null when none has been read. |
attestation.age_seconds required |
integer | |
attestation.features required |
object | |
attestation.features.browser_automation required |
boolean | |
attestation.features.max_devices_per_seat required |
integer | null | |
attestation.features.max_profiles required |
integer | null | Null means unlimited. |
attestation.features.platforms required |
array of string | |
attestation.features.scheduler required |
boolean | |
attestation.fetched_at required |
string | ISO 8601 UTC instant the plan was fetched. |
attestation.fresh required |
boolean | |
attestation.organization_id required |
string | null | |
attestation_present required |
boolean | Whether a saved plan is held. With --offline, from the Keychain item's attributes only; live, whether attestation is non-null. |
checked required |
string | presence for --offline (attributes only: no secret read, no request); live when the saved plan was read and, when due, refreshed from Blowhorn. Values: |
cloud_yaml required |
string | null | Path of the cloud.yaml in use, if any. |
display_name required |
string | null | |
enforcement required |
string | The entitlement enforcement mode, or "invalid (<reason>)" when the setting does not parse. |
fetch_error required |
string | null | |
ok required |
boolean | False when this computer is not signed in. True when a token is configured and, live, Blowhorn accepted it. |
organization_id required |
string | null | |
permission_keys |
array of string | Live only: the ids of the permission keys the signed-in session holds in the Blowhorn parent organization (GET /api/identity/orgs/by-domain resolves the parent organization, then GET /api/identity/orgs/{id}/users/keys), which the desktop app's Diagnostics gate matches the Access Diagnostics key id against. Empty when no token is configured or when either read fails: the gate fails closed. Absent from the --offline payload, which makes no request. |
previous_account required |
object | null | Who was signed in on this computer before the session ended, for the desktop app's returning sign-in screen, which greets that person by name. Read from cloud.yaml, where sign-out removes the account: a name still there with no token configured means the token was lost, removed or has timed out, never that the person signed out. Only the name and email, never the photo or the organizations. Null while a token is configured (account answers then), after a sign-out, and when cloud.yaml names nobody. Answered by --offline too: no secret read, no request. |
previous_account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
previous_account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
previous_account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
ready_to_fetch required |
boolean | |
sign_in_problem |
string | Why this computer is not signed in, present only when signed_in is false. no token means none is configured. token refused means Blowhorn answered 401 or 403. unreachable means Blowhorn could not be reached. --offline reports only no token. Values: |
signed_in required |
boolean | Whether this computer is signed in to Blowhorn. |
token_configured required |
boolean | Whether a Blowhorn token is configured. The token is never printed. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AuthStatusResponse
| Name | Type | Description |
|---|---|---|
account required |
object | null | Who is signed in, for the desktop app's account button and menu. Read from cloud.yaml, where sign-in caches it and a live auth status refreshes it from Blowhorn's own answers, each checked against the meshery/schemas construct Blowhorn consumes (schemas/meshery/consumed.yaml): User and its Preference from GET /api/identity/users/profile, AvailableOrganization from GET /api/identity/orgs, and the organization's Subscription and its Plan from /api/entitlement. So --offline answers it with no secret read and no request. Null when no token is configured. A field Blowhorn did not answer is null. The organization is the one organization_id names: the session's active organization (Preference.selectedOrganizationId when the account is still a member, else the first listed, the same choice the browser session makes), unless BLOWHORN_CLOUD_ORGANIZATION names another. |
account.avatar_url required |
string | null | User.avatarUrl (meshery/schemas v1beta2 user), the profile photo in your Blowhorn account. |
account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
account.organization_count required |
integer | null | How many live organizations the account belongs to, as GET /api/identity/orgs last counted them; more than one offers Switch organization. |
account.organization_name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization) of the organization this status reports. |
account.organizations required |
array of object | The account's live organizations (GET /api/identity/orgs, soft-deleted ones left out, up to Blowhorn's page of 100), in Blowhorn's order; Switch organization offers these. |
account.organizations[].description required |
string | null | AvailableOrganization.description (meshery/schemas v1beta2 organization), null when Cloud gave none; shown on hover beside the id to tell two organizations of the same name apart. |
account.organizations[].id required |
string | AvailableOrganization.id (meshery/schemas v1beta2 organization). |
account.organizations[].name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization). |
account.plan_name required |
string | null | Plan.name (meshery/schemas v1beta3 plan, the PlanName enum) of the organization this status reports, exactly as Blowhorn names it, and Free when Blowhorn answered with no current subscription (every organization is on Free unless one names another plan). Null only when it has never been read for that organization; the app then shows the organization alone. Values: |
api_base required |
string | null | |
attestation required |
object | null | The cached plan attestation, or null when none has been read. |
attestation.age_seconds required |
integer | |
attestation.features required |
object | |
attestation.features.browser_automation required |
boolean | |
attestation.features.max_devices_per_seat required |
integer | null | |
attestation.features.max_profiles required |
integer | null | Null means unlimited. |
attestation.features.platforms required |
array of string | |
attestation.features.scheduler required |
boolean | |
attestation.fetched_at required |
string | ISO 8601 UTC instant the plan was fetched. |
attestation.fresh required |
boolean | |
attestation.organization_id required |
string | null | |
attestation_present required |
boolean | Whether a saved plan is held. With --offline, from the Keychain item's attributes only; live, whether attestation is non-null. |
checked required |
string | presence for --offline (attributes only: no secret read, no request); live when the saved plan was read and, when due, refreshed from Blowhorn. Values: |
cloud_yaml required |
string | null | Path of the cloud.yaml in use, if any. |
display_name required |
string | null | |
enforcement required |
string | The entitlement enforcement mode, or "invalid (<reason>)" when the setting does not parse. |
fetch_error required |
string | null | |
ok required |
boolean | False when this computer is not signed in. True when a token is configured and, live, Blowhorn accepted it. |
organization_id required |
string | null | |
permission_keys |
array of string | Live only: the ids of the permission keys the signed-in session holds in the Blowhorn parent organization (GET /api/identity/orgs/by-domain resolves the parent organization, then GET /api/identity/orgs/{id}/users/keys), which the desktop app's Diagnostics gate matches the Access Diagnostics key id against. Empty when no token is configured or when either read fails: the gate fails closed. Absent from the --offline payload, which makes no request. |
previous_account required |
object | null | Who was signed in on this computer before the session ended, for the desktop app's returning sign-in screen, which greets that person by name. Read from cloud.yaml, where sign-out removes the account: a name still there with no token configured means the token was lost, removed or has timed out, never that the person signed out. Only the name and email, never the photo or the organizations. Null while a token is configured (account answers then), after a sign-out, and when cloud.yaml names nobody. Answered by --offline too: no secret read, no request. |
previous_account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
previous_account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
previous_account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
ready_to_fetch required |
boolean | |
sign_in_problem |
string | Why this computer is not signed in, present only when signed_in is false. no token means none is configured. token refused means Blowhorn answered 401 or 403. unreachable means Blowhorn could not be reached. --offline reports only no token. Values: |
signed_in required |
boolean | Whether this computer is signed in to Blowhorn. |
token_configured required |
boolean | Whether a Blowhorn token is configured. The token is never printed. |
POST
/cli/auth/switch-organization
Switch the signed-in account to another of its organizations.
Switch the signed-in account to another of its organizations.
Makes ORGANIZATION_ID the organization the account's Blowhorn session has selected, with the same write Blowhorn's and Meshery's own organization switchers send (PUT /api/identity/users/preferences, selectedOrganizationId), so the browser and this computer follow it. ORGANIZATION_ID must be one of the account's organizations ('auth status --json' lists them as account.organizations). Then reports what 'auth status' reports, read live for the new organization. BLOWHORN_CLOUD_ORGANIZATION, when set, still names the organization this computer reports. --dry-run checks the id and sends nothing.
Request body required
The flags and arguments of `blowhorn auth switch-organization`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
organization_id required |
string | The id of one of the account's organizations (auth status --json: account.organizations). |
pace |
string | Accepted by 'blowhorn auth switch-organization' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn auth': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn auth switch-organization' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What auth status reports, read live after the account's selected organization was switched in Blowhorn (or, with --dry-run, read offline with nothing sent). BLOWHORN_CLOUD_ORGANIZATION, when set, still names the organization this computer reports.
| Name | Type | Description |
|---|---|---|
account required |
object | null | Who is signed in, for the desktop app's account button and menu. Read from cloud.yaml, where sign-in caches it and a live auth status refreshes it from Blowhorn's own answers, each checked against the meshery/schemas construct Blowhorn consumes (schemas/meshery/consumed.yaml): User and its Preference from GET /api/identity/users/profile, AvailableOrganization from GET /api/identity/orgs, and the organization's Subscription and its Plan from /api/entitlement. So --offline answers it with no secret read and no request. Null when no token is configured. A field Blowhorn did not answer is null. The organization is the one organization_id names: the session's active organization (Preference.selectedOrganizationId when the account is still a member, else the first listed, the same choice the browser session makes), unless BLOWHORN_CLOUD_ORGANIZATION names another. |
account.avatar_url required |
string | null | User.avatarUrl (meshery/schemas v1beta2 user), the profile photo in your Blowhorn account. |
account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
account.organization_count required |
integer | null | How many live organizations the account belongs to, as GET /api/identity/orgs last counted them; more than one offers Switch organization. |
account.organization_name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization) of the organization this status reports. |
account.organizations required |
array of object | The account's live organizations (GET /api/identity/orgs, soft-deleted ones left out, up to Blowhorn's page of 100), in Blowhorn's order; Switch organization offers these. |
account.organizations[].description required |
string | null | AvailableOrganization.description (meshery/schemas v1beta2 organization), null when Cloud gave none; shown on hover beside the id to tell two organizations of the same name apart. |
account.organizations[].id required |
string | AvailableOrganization.id (meshery/schemas v1beta2 organization). |
account.organizations[].name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization). |
account.plan_name required |
string | null | Plan.name (meshery/schemas v1beta3 plan, the PlanName enum) of the organization this status reports, exactly as Blowhorn names it, and Free when Blowhorn answered with no current subscription (every organization is on Free unless one names another plan). Null only when it has never been read for that organization; the app then shows the organization alone. Values: |
api_base required |
string | null | |
attestation required |
object | null | The cached plan attestation, or null when none has been read. |
attestation.age_seconds required |
integer | |
attestation.features required |
object | |
attestation.features.browser_automation required |
boolean | |
attestation.features.max_devices_per_seat required |
integer | null | |
attestation.features.max_profiles required |
integer | null | Null means unlimited. |
attestation.features.platforms required |
array of string | |
attestation.features.scheduler required |
boolean | |
attestation.fetched_at required |
string | ISO 8601 UTC instant the plan was fetched. |
attestation.fresh required |
boolean | |
attestation.organization_id required |
string | null | |
attestation_present required |
boolean | Whether a saved plan is held. With --offline, from the Keychain item's attributes only; live, whether attestation is non-null. |
checked required |
string | presence for --offline (attributes only: no secret read, no request); live when the saved plan was read and, when due, refreshed from Blowhorn. Values: |
cloud_yaml required |
string | null | Path of the cloud.yaml in use, if any. |
display_name required |
string | null | |
enforcement required |
string | The entitlement enforcement mode, or "invalid (<reason>)" when the setting does not parse. |
fetch_error required |
string | null | |
ok required |
boolean | False when this computer is not signed in. True when a token is configured and, live, Blowhorn accepted it. |
organization_id required |
string | null | |
permission_keys |
array of string | Live only: the ids of the permission keys the signed-in session holds in the Blowhorn parent organization (GET /api/identity/orgs/by-domain resolves the parent organization, then GET /api/identity/orgs/{id}/users/keys), which the desktop app's Diagnostics gate matches the Access Diagnostics key id against. Empty when no token is configured or when either read fails: the gate fails closed. Absent from the --offline payload, which makes no request. |
previous_account required |
object | null | Who was signed in on this computer before the session ended, for the desktop app's returning sign-in screen, which greets that person by name. Read from cloud.yaml, where sign-out removes the account: a name still there with no token configured means the token was lost, removed or has timed out, never that the person signed out. Only the name and email, never the photo or the organizations. Null while a token is configured (account answers then), after a sign-out, and when cloud.yaml names nobody. Answered by --offline too: no secret read, no request. |
previous_account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
previous_account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
previous_account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
ready_to_fetch required |
boolean | |
sign_in_problem |
string | Why this computer is not signed in, present only when signed_in is false. no token means none is configured. token refused means Blowhorn answered 401 or 403. unreachable means Blowhorn could not be reached. --offline reports only no token. Values: |
signed_in required |
boolean | Whether this computer is signed in to Blowhorn. |
token_configured required |
boolean | Whether a Blowhorn token is configured. The token is never printed. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AuthSwitchOrganizationResponse
| Name | Type | Description |
|---|---|---|
account required |
object | null | Who is signed in, for the desktop app's account button and menu. Read from cloud.yaml, where sign-in caches it and a live auth status refreshes it from Blowhorn's own answers, each checked against the meshery/schemas construct Blowhorn consumes (schemas/meshery/consumed.yaml): User and its Preference from GET /api/identity/users/profile, AvailableOrganization from GET /api/identity/orgs, and the organization's Subscription and its Plan from /api/entitlement. So --offline answers it with no secret read and no request. Null when no token is configured. A field Blowhorn did not answer is null. The organization is the one organization_id names: the session's active organization (Preference.selectedOrganizationId when the account is still a member, else the first listed, the same choice the browser session makes), unless BLOWHORN_CLOUD_ORGANIZATION names another. |
account.avatar_url required |
string | null | User.avatarUrl (meshery/schemas v1beta2 user), the profile photo in your Blowhorn account. |
account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
account.organization_count required |
integer | null | How many live organizations the account belongs to, as GET /api/identity/orgs last counted them; more than one offers Switch organization. |
account.organization_name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization) of the organization this status reports. |
account.organizations required |
array of object | The account's live organizations (GET /api/identity/orgs, soft-deleted ones left out, up to Blowhorn's page of 100), in Blowhorn's order; Switch organization offers these. |
account.organizations[].description required |
string | null | AvailableOrganization.description (meshery/schemas v1beta2 organization), null when Cloud gave none; shown on hover beside the id to tell two organizations of the same name apart. |
account.organizations[].id required |
string | AvailableOrganization.id (meshery/schemas v1beta2 organization). |
account.organizations[].name required |
string | null | AvailableOrganization.name (meshery/schemas v1beta2 organization). |
account.plan_name required |
string | null | Plan.name (meshery/schemas v1beta3 plan, the PlanName enum) of the organization this status reports, exactly as Blowhorn names it, and Free when Blowhorn answered with no current subscription (every organization is on Free unless one names another plan). Null only when it has never been read for that organization; the app then shows the organization alone. Values: |
api_base required |
string | null | |
attestation required |
object | null | The cached plan attestation, or null when none has been read. |
attestation.age_seconds required |
integer | |
attestation.features required |
object | |
attestation.features.browser_automation required |
boolean | |
attestation.features.max_devices_per_seat required |
integer | null | |
attestation.features.max_profiles required |
integer | null | Null means unlimited. |
attestation.features.platforms required |
array of string | |
attestation.features.scheduler required |
boolean | |
attestation.fetched_at required |
string | ISO 8601 UTC instant the plan was fetched. |
attestation.fresh required |
boolean | |
attestation.organization_id required |
string | null | |
attestation_present required |
boolean | Whether a saved plan is held. With --offline, from the Keychain item's attributes only; live, whether attestation is non-null. |
checked required |
string | presence for --offline (attributes only: no secret read, no request); live when the saved plan was read and, when due, refreshed from Blowhorn. Values: |
cloud_yaml required |
string | null | Path of the cloud.yaml in use, if any. |
display_name required |
string | null | |
enforcement required |
string | The entitlement enforcement mode, or "invalid (<reason>)" when the setting does not parse. |
fetch_error required |
string | null | |
ok required |
boolean | False when this computer is not signed in. True when a token is configured and, live, Blowhorn accepted it. |
organization_id required |
string | null | |
permission_keys |
array of string | Live only: the ids of the permission keys the signed-in session holds in the Blowhorn parent organization (GET /api/identity/orgs/by-domain resolves the parent organization, then GET /api/identity/orgs/{id}/users/keys), which the desktop app's Diagnostics gate matches the Access Diagnostics key id against. Empty when no token is configured or when either read fails: the gate fails closed. Absent from the --offline payload, which makes no request. |
previous_account required |
object | null | Who was signed in on this computer before the session ended, for the desktop app's returning sign-in screen, which greets that person by name. Read from cloud.yaml, where sign-out removes the account: a name still there with no token configured means the token was lost, removed or has timed out, never that the person signed out. Only the name and email, never the photo or the organizations. Null while a token is configured (account answers then), after a sign-out, and when cloud.yaml names nobody. Answered by --offline too: no secret read, no request. |
previous_account.email required |
string | null | User.email (meshery/schemas v1beta2 user). |
previous_account.first_name required |
string | null | User.firstName (meshery/schemas v1beta2 user). |
previous_account.last_name required |
string | null | User.lastName (meshery/schemas v1beta2 user). |
ready_to_fetch required |
boolean | |
sign_in_problem |
string | Why this computer is not signed in, present only when signed_in is false. no token means none is configured. token refused means Blowhorn answered 401 or 403. unreachable means Blowhorn could not be reached. --offline reports only no token. Values: |
signed_in required |
boolean | Whether this computer is signed in to Blowhorn. |
token_configured required |
boolean | Whether a Blowhorn token is configured. The token is never printed. |
Slack
POST
/cli/slack/delete
Delete a Slack message this profile sent with 'blowhorn slack send' (chat.delete). Only your own messages, by the store's send ledger (not_sent_by_profile otherwise, before any Slack call), even when the session is a workspace admin's. A dry run unless --confirm. Exit codes as 'slack send'.
Delete a Slack message this profile sent with 'blowhorn slack send' (chat.delete). Only your own messages, by the store's send ledger (not_sent_by_profile otherwise, before any Slack call), even when the session is a workspace admin's. A dry run unless --confirm. Exit codes as 'slack send'.
Request body required
The flags and arguments of `blowhorn slack delete`, one property per parameter.
| Name | Type | Description |
|---|---|---|
channel |
string | The channel id (C…, D… or G…) when --target is a bare ts |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
confirm |
boolean | Actually send. Without it the command is a dry run that sends nothing Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the one random wait 'blowhorn slack delete' takes: before each confirmed delete after the first when --profile names several profiles (a single profile, or a dry run, never waits): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
array of string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
target |
string | The message: a Slack message link, or its ts with --channel and --workspace |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | The workspace host (e.g. mesheryio.slack.com) when --target does not name one; for whoami, which workspace to ask |
Responses
200
What the command prints to stdout with --json when it exits 0.
What slack delete prints with --json on success: a dry run (the default), the deletion, or a multi-profile wrapper.
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
channel required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message required |
string | |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
changed required |
boolean | |
channel required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message |
string | Present only when nothing changed (Slack said already done). |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
results required |
array of object | |
results[].exit_code required |
integer | |
results[].ok required |
boolean | |
results[].outcome required |
string | |
summary required |
object | |
summary.failed required |
integer | |
summary.outcomes required |
object | Count of results by outcome. |
summary.profiles required |
integer | |
summary.succeeded required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackDeleteResponse
POST
/cli/slack/edit
Replace the text of a Slack message this profile sent with 'blowhorn slack send' (chat.update). Only your own messages: the store's send ledger must show this profile sent that ts, or the result is not_sent_by_profile before any Slack call. A dry run unless --confirm. --target is the message link (its own ts, never the ?thread_ts= parent) or a bare ts with --channel and --workspace; the text comes from --message, --file or stdin ('-') and is sent untouched. Several profiles (--profile a,b) run one after another. Exit codes as 'slack send'.
Replace the text of a Slack message this profile sent with 'blowhorn slack send' (chat.update). Only your own messages: the store's send ledger must show this profile sent that ts, or the result is not_sent_by_profile before any Slack call. A dry run unless --confirm. --target is the message link (its own ts, never the ?thread_ts= parent) or a bare ts with --channel and --workspace; the text comes from --message, --file or stdin ('-') and is sent untouched. Several profiles (--profile a,b) run one after another. Exit codes as 'slack send'.
Request body required
The flags and arguments of `blowhorn slack edit`, one property per parameter.
| Name | Type | Description |
|---|---|---|
channel |
string | The channel id (C…, D… or G…) when --target is a bare ts |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
confirm |
boolean | Actually send. Without it the command is a dry run that sends nothing Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
message |
string | The message text, sent untouched (Slack mrkdwn included); '-' reads it from stdin |
message_file |
string | Read the message text from this file ('-' for stdin), sent untouched |
pace |
string | Pace of the one random wait 'blowhorn slack edit' takes: before each confirmed edit after the first when --profile names several profiles (a single profile, or a dry run, never waits): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
array of string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
target |
string | The message: a Slack message link, or its ts with --channel and --workspace |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | The workspace host (e.g. mesheryio.slack.com) when --target does not name one; for whoami, which workspace to ask |
Responses
200
What the command prints to stdout with --json when it exits 0.
What slack edit prints with --json on success: a dry run (the default), the edit, or a multi-profile wrapper.
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
channel required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message required |
string | |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
attribution required |
object | Fields in Slack's answer showing the message is not plainly the person's (bot_id, app_id, bot_profile, subtype). Empty when none. |
changed required |
boolean | |
channel required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message |
string | Present only when nothing changed (Slack said already done). |
ok required |
boolean | |
outcome required |
string | Values: |
permalink required |
string | null | |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
results required |
array of object | |
results[].exit_code required |
integer | |
results[].ok required |
boolean | |
results[].outcome required |
string | |
summary required |
object | |
summary.failed required |
integer | |
summary.outcomes required |
object | Count of results by outcome. |
summary.profiles required |
integer | |
summary.succeeded required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackEditResponse
POST
/cli/slack/profiles
List every profile's captured Slack workspaces (or only the --profile names given), the credentials each holds (session, user-token, bot-token) and which one --slack-auth would use. Reads the store and makes no Slack call. No secret is printed.
List every profile's captured Slack workspaces (or only the --profile names given), the credentials each holds (session, user-token, bot-token) and which one --slack-auth would use. Reads the store and makes no Slack call. No secret is printed.
Request body required
The flags and arguments of `blowhorn slack profiles`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn slack profiles' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn slack profiles' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Every profile's Slack workspaces and which credential kinds each holds, read from the store with no Slack call. Presence only, never a credential value.
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
profiles required |
array of object | |
profiles[].known required |
boolean | |
profiles[].profile required |
string | |
profiles[].unprefixed required |
array of string | Credential kinds held without a workspace prefix (presence only). |
profiles[].workspaces required |
array of object | |
profiles[].workspaces[].held required |
array of string | Which credential kinds the store holds (presence only). |
profiles[].workspaces[].user required |
string | null | |
profiles[].workspaces[].user_id required |
string | null | |
profiles[].workspaces[].workspace required |
string | |
profiles[].workspaces[].would_use required |
string | null | The mechanism a send would use, or null when none may send. |
slack_auth required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackProfilesResponse
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
profiles required |
array of object | |
profiles[].known required |
boolean | |
profiles[].profile required |
string | |
profiles[].unprefixed required |
array of string | Credential kinds held without a workspace prefix (presence only). |
profiles[].workspaces required |
array of object | |
profiles[].workspaces[].held required |
array of string | Which credential kinds the store holds (presence only). |
profiles[].workspaces[].user required |
string | null | |
profiles[].workspaces[].user_id required |
string | null | |
profiles[].workspaces[].workspace required |
string | |
profiles[].workspaces[].would_use required |
string | null | The mechanism a send would use, or null when none may send. |
slack_auth required |
string |
POST
/cli/slack/react
Add an emoji reaction to any Slack message as the profile (reactions.add), or take yours back with --remove (reactions.remove). A dry run unless --confirm. A reaction already there (or already gone) is success with changed: false. --emoji takes white_check_mark or :white_check_mark:. Several profiles (--profile a,b) each react, one after another. Exit codes as 'slack send'.
Add an emoji reaction to any Slack message as the profile (reactions.add), or take yours back with --remove (reactions.remove). A dry run unless --confirm. A reaction already there (or already gone) is success with changed: false. --emoji takes white_check_mark or :white_check_mark:. Several profiles (--profile a,b) each react, one after another. Exit codes as 'slack send'.
Request body required
The flags and arguments of `blowhorn slack react`, one property per parameter.
| Name | Type | Description |
|---|---|---|
channel |
string | The channel id (C…, D… or G…) when --target is a bare ts |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
confirm |
boolean | Actually send. Without it the command is a dry run that sends nothing Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
emoji |
string | The emoji name, with or without colons (white_check_mark, :+1:) |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Pace of the one random wait 'blowhorn slack react' takes: before each confirmed reaction after the first when --profile names several profiles (a single profile, or a dry run, never waits): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
array of string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
remove |
boolean | Take the reaction back (reactions.remove) instead of adding it Default: |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
target |
string | The message: a Slack message link, or its ts with --channel and --workspace |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | The workspace host (e.g. mesheryio.slack.com) when --target does not name one; for whoami, which workspace to ask |
Responses
200
What the command prints to stdout with --json when it exits 0.
What slack react prints with --json on success: a dry run (the default), the reaction added or removed (changed false when Slack said it was already so), or a multi-profile wrapper.
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
channel required |
string | |
dry_run required |
boolean | |
emoji required |
string | |
exit_code required |
integer | |
fallback required |
boolean | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message required |
string | |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
changed required |
boolean | |
channel required |
string | |
dry_run required |
boolean | |
emoji required |
string | |
exit_code required |
integer | |
fallback required |
boolean | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message |
string | Present only when nothing changed (Slack said already done). |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
results required |
array of object | |
results[].exit_code required |
integer | |
results[].ok required |
boolean | |
results[].outcome required |
string | |
summary required |
object | |
summary.failed required |
integer | |
summary.outcomes required |
object | Count of results by outcome. |
summary.profiles required |
integer | |
summary.succeeded required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackReactResponse
POST
/cli/slack/send
Send one message to Slack as one profile, or as each of several (--profile a,b: one after another, each with its own idempotency key, ledger entry and result; one profile's failure never stops the next, and exit is 0 only when every profile succeeded), exactly once. A dry run unless --confirm: the dry run resolves the credential, the target and the idempotency key and says what would happen. --target takes a message link (a thread reply: the ?thread_ts= parent when the link is a reply's), a channel link (a top-level post), or #channel, @user or a channel id with --workspace. The text comes from --message, --file <path>, or stdin with --message - or --file -, and is sent untouched (Slack mrkdwn included). Every send carries a client_msg_id and is claimed in the store's send ledger under its idempotency key (--idempotency-key, else derived from profile, workspace, channel, thread and text) before it goes out: a key already sent replays its ts and link without sending again, and a send whose answer was never recorded is 'outcome unknown' and is never resent automatically. The result names the credential that ran (--slack-auth), whether that was a fallback, warns when a token may show a Slack app's attribution, and reports bot_id, app_id, bot_profile or subtype on Slack's answer. Exit codes: 0 sent, replayed or previewed; 1 refused (credential or Slack); 2 usage or key conflict; 3 store unavailable or a contended key; 4 outcome unknown; 5 rate-limited (Retry-After kept).
Send one message to Slack as one profile, or as each of several (--profile a,b: one after another, each with its own idempotency key, ledger entry and result; one profile's failure never stops the next, and exit is 0 only when every profile succeeded), exactly once. A dry run unless --confirm: the dry run resolves the credential, the target and the idempotency key and says what would happen. --target takes a message link (a thread reply: the ?thread_ts= parent when the link is a reply's), a channel link (a top-level post), or #channel, @user or a channel id with --workspace. The text comes from --message, --file <path>, or stdin with --message - or --file -, and is sent untouched (Slack mrkdwn included). Every send carries a client_msg_id and is claimed in the store's send ledger under its idempotency key (--idempotency-key, else derived from profile, workspace, channel, thread and text) before it goes out: a key already sent replays its ts and link without sending again, and a send whose answer was never recorded is 'outcome unknown' and is never resent automatically. The result names the credential that ran (--slack-auth), whether that was a fallback, warns when a token may show a Slack app's attribution, and reports bot_id, app_id, bot_profile or subtype on Slack's answer. Exit codes: 0 sent, replayed or previewed; 1 refused (credential or Slack); 2 usage or key conflict; 3 store unavailable or a contended key; 4 outcome unknown; 5 rate-limited (Retry-After kept).
Request body required
The flags and arguments of `blowhorn slack send`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
confirm |
boolean | Actually send. Without it the command is a dry run that sends nothing Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
idempotency_key |
string | The key this send is recorded under; a key already sent is replayed, never sent twice (default: derived from profile, workspace, channel, thread and text) |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
message |
string | The message text, sent untouched (Slack mrkdwn included); '-' reads it from stdin |
message_file |
string | Read the message text from this file ('-' for stdin), sent untouched |
pace |
string | Pace of the one random wait 'blowhorn slack send' takes: before each confirmed send after the first when --profile names several profiles (a single profile, or a dry run, never waits): a preset name (fast/normal/slow) or a numeric multiplier (e.g. 0.5, 2.0). Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
array of string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Risk Profile this run uses: cautious, standard or brisk. For this one run it beats the schedule's value, the profile's choice and configuration. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
target |
string | Where to send: a Slack message link (thread reply), a channel link (top-level post), or #channel, @user or a channel id with --workspace |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | The workspace host (e.g. mesheryio.slack.com) when --target does not name one; for whoami, which workspace to ask |
Responses
200
What the command prints to stdout with --json when it exits 0.
What slack send prints with --json on success: a dry run (the default), a real send, a replay of an earlier send under the same idempotency key, or, for several profiles, a multi-profile wrapper. No Slack credential is ever printed.
object
| Name | Type | Description |
|---|---|---|
channel required |
string | |
client_msg_id required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
kind required |
string | What the target resolved to (channel, thread, message link, ...). |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message required |
string | |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
channel required |
string | |
client_msg_id required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
kind required |
string | What the target resolved to (channel, thread, message link, ...). |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
message required |
string | |
ok required |
boolean | |
outcome required |
string | Values: |
permalink required |
string | null | |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | null | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
attribution required |
object | Fields in Slack's answer showing the message is not plainly the person's (bot_id, app_id, bot_profile, subtype). Empty when none. |
channel required |
string | |
client_msg_id required |
string | |
dry_run required |
boolean | |
exit_code required |
integer | |
fallback required |
boolean | |
idempotency_key required |
string | |
kind required |
string | What the target resolved to (channel, thread, message link, ...). |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
ok required |
boolean | |
outcome required |
string | Values: |
permalink required |
string | null | |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
thread_ts required |
string | null | |
ts required |
string | |
warnings required |
array of string | |
workspace required |
string | null |
object
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
ok required |
boolean | |
outcome required |
string | Values: |
results required |
array of object | |
results[].exit_code required |
integer | |
results[].ok required |
boolean | |
results[].outcome required |
string | |
summary required |
object | |
summary.failed required |
integer | |
summary.outcomes required |
object | Count of results by outcome. |
summary.profiles required |
integer | |
summary.succeeded required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackSendResponse
POST
/cli/slack/whoami
Ask Slack (auth.test) who a profile's Slack credential is in one workspace: user, user id and team, with the credential --slack-auth allowed and whether that was a fallback. Sends no message. --workspace may be left out when the profile has captured exactly one workspace.
Ask Slack (auth.test) who a profile's Slack credential is in one workspace: user, user id and team, with the credential --slack-auth allowed and whether that was a fallback. Sends no message. --workspace may be left out when the profile has captured exactly one workspace.
Request body required
The flags and arguments of `blowhorn slack whoami`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as JSON (no secrets) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn slack whoami' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn slack': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn slack whoami' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
slack_auth |
string | Which Slack credential may send, for one run: 'session' (your captured signed-in web session; the message is yours, with no app attached), 'user-token' (an xoxp- app token for you) or 'bot-token' (an xoxb- bot token), or a comma-delimited list of them tried in order (e.g. 'session,user-token,bot-token'). One value is directive: that credential or a refusal naming why. A token may show the Slack app's attribution. This flag, then BLOWHORN_SLACK_AUTH, then defaults.slack.auth_mode in .blowhorn.yaml, then the built-in, selects it; any other value is refused before anything runs. Dry runs and results name the credential that ran and whether it was a fallback |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
workspace |
string | The workspace host (e.g. mesheryio.slack.com) when --target does not name one; for whoami, which workspace to ask |
Responses
200
What the command prints to stdout with --json when it exits 0.
Who the profile's allowed Slack credential is in a workspace, from Slack's auth.test. Carries no credential.
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
fallback required |
boolean | |
identity required |
object | Slack auth.test answer fields that were present. |
identity.bot_id |
string | |
identity.team |
string | |
identity.team_id |
string | |
identity.url |
string | |
identity.user |
string | |
identity.user_id |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
warnings required |
array of string | |
workspace required |
string | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SlackWhoamiResponse
| Name | Type | Description |
|---|---|---|
exit_code required |
integer | |
fallback required |
boolean | |
identity required |
object | Slack auth.test answer fields that were present. |
identity.bot_id |
string | |
identity.team |
string | |
identity.team_id |
string | |
identity.url |
string | |
identity.user |
string | |
identity.user_id |
string | |
mechanism required |
string | null | Credential kind used: session, user-token or bot-token. The credential itself is never printed. |
ok required |
boolean | |
outcome required |
string | Values: |
profile required |
string | |
skipped required |
array of string | |
slack_auth required |
string | null | The slack auth setting and what set it. |
warnings required |
array of string | |
workspace required |
string | null |
Service
POST
/cli/service/adopt
Retire what stands in one owner's way so one owner holds the label: with --by app, the file-installed service.
Retire what stands in one owner's way so one owner holds the label: with --by app, the file-installed service.
Request body required
The flags and arguments of `blowhorn service adopt`, one property per parameter.
| Name | Type | Description |
|---|---|---|
by |
string | Who takes the label: file (this CLI's LaunchAgent; nothing stands in its way) or app (the desktop app; retires the file agent). Values: Default: |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service adopt' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service adopt' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints whether `service adopt` retired the other owner's registration so the named owner can take the service. A refusal (a tick is running, the plist is unreadable) prints the same document with `ok: false` and the cause in `error`.
| Name | Type | Description |
|---|---|---|
adopted required |
boolean | |
by required |
string | The owner adopting the service (app or file). |
dry_run |
boolean | |
error |
string | Why the adoption was refused (`ok` is false). |
file_removed required |
boolean | Whether the file-installed plist was removed. |
label required |
string | |
message |
string | |
ok required |
boolean | |
owner required |
string | The owner found before adopting (app, file, none or unknown). |
reason |
string | Why nothing was done. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceAdoptResponse
| Name | Type | Description |
|---|---|---|
adopted required |
boolean | |
by required |
string | The owner adopting the service (app or file). |
dry_run |
boolean | |
error |
string | Why the adoption was refused (`ok` is false). |
file_removed required |
boolean | Whether the file-installed plist was removed. |
label required |
string | |
message |
string | |
ok required |
boolean | |
owner required |
string | The owner found before adopting (app, file, none or unknown). |
reason |
string | Why nothing was done. |
POST
/cli/service/cancel
Ask the running background service to stop its pass at the next safe point; the activity in progress returns to the queue.
Ask the running background service to stop its pass at the next safe point; the activity in progress returns to the queue.
Request body required
The flags and arguments of `blowhorn service cancel`, one property per parameter.
| Name | Type | Description |
|---|---|---|
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service cancel' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service cancel' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints what `service cancel` asked of the resident service: the pass in flight stops at its next safe point (never mid-post) and its row returns to the queue. With nothing in flight, or only a tick the service did not start, nothing is cancelled. `--dry-run` asks nothing.
ServiceCancelAnswer
| Name | Type | Description |
|---|---|---|
cancelling required |
boolean | |
foreign |
boolean | True when the running tick was not started by the service, so it is not the service's to stop. |
idle required |
boolean | True when no pass was in flight. |
label required |
string | |
log_path required |
string | null | |
message required |
string | |
ok required |
any | |
pid required |
integer | null |
ServiceCancelDryRun
| Name | Type | Description |
|---|---|---|
control_socket required |
string | |
dry_run required |
any | |
label required |
string | |
message required |
string | |
ok required |
any | |
pass_pid required |
integer | null |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceCancelResponse
POST
/cli/service/install
Install the background service as a per-user LaunchAgent on a machine without the desktop app.
Install the background service as a per-user LaunchAgent on a machine without the desktop app.
Request body required
The flags and arguments of `blowhorn service install`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service install' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service install' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service install prints with --json on success: the installed service definition, or the reason nothing was done, or with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
bootstrapped required |
boolean | |
dry_run |
boolean | Present only with --dry-run. |
installed required |
boolean | |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceInstallResponse
| Name | Type | Description |
|---|---|---|
bootstrapped required |
boolean | |
dry_run |
boolean | Present only with --dry-run. |
installed required |
boolean | |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
POST
/cli/service/restart
Restart the background service between passes, so it picks up a changed checkout.
Restart the background service between passes, so it picks up a changed checkout.
Request body required
The flags and arguments of `blowhorn service restart`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service restart' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service restart' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service restart prints with --json on success: the service was asked to restart after its pass, or kickstarted; with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
action |
string | 'What was done, for example bootstrapped, pass requested, busy, draining, restarting, kickstarted.' |
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
reply |
object | The service control socket's answer when it was asked. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceRestartResponse
| Name | Type | Description |
|---|---|---|
action |
string | 'What was done, for example bootstrapped, pass requested, busy, draining, restarting, kickstarted.' |
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
reply |
object | The service control socket's answer when it was asked. |
POST
/cli/service/run
Run the background service in this terminal: a pass whenever a routine falls due, re-scanning at least every minute, until stopped. This is what launchd runs.
Run the background service in this terminal: a pass whenever a routine falls due, re-scanning at least every minute, until stopped. This is what launchd runs.
Request body required
The flags and arguments of `blowhorn service run`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service run' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service run' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service run prints with --json on success: with --dry-run the plan, otherwise a single line when the foreground supervisor exits.
DryRun
| Name | Type | Description |
|---|---|---|
checkout required |
string | |
control_socket required |
string | |
dry_run required |
boolean | |
log_dir required |
string | |
message required |
string | |
ok required |
boolean | |
pass_argv required |
array of string | |
python required |
string | |
rescan_s required |
number | The longest the service sleeps between passes; it wakes sooner for a due routine. |
state_path required |
string | |
would_take_over required |
integer | null | Pid of a stale state the run would take over. |
Exited
| Name | Type | Description |
|---|---|---|
exit_reason required |
string | |
ok required |
boolean | True when the supervisor exited with code 0. |
state_path required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceRunResponse
POST
/cli/service/start
Start the installed background service, or ask a running one for a pass now.
Start the installed background service, or ask a running one for a pass now.
Request body required
The flags and arguments of `blowhorn service start`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service start' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service start' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service start prints with --json on success: the service was loaded, asked for a pass, or kickstarted; with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
action |
string | 'What was done, for example bootstrapped, pass requested, busy, draining, restarting, kickstarted.' |
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
reply |
object | The service control socket's answer when it was asked. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceStartResponse
| Name | Type | Description |
|---|---|---|
action |
string | 'What was done, for example bootstrapped, pass requested, busy, draining, restarting, kickstarted.' |
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
reply |
object | The service control socket's answer when it was asked. |
POST
/cli/service/status
Show the background service: who registered it, whether it runs, its last pass.
Show the background service: who registered it, whether it runs, its last pass.
Request body required
The flags and arguments of `blowhorn service status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the background service's state - installed, loaded, owner, definition drift, tick in flight and the resident supervisor. Reads launchd and the service's state file.
| Name | Type | Description |
|---|---|---|
chrome required |
null | Reserved; always null today. |
control_socket required |
string | |
drift required |
array of object | |
drift[].expected required |
any | |
drift[].installed required |
any | |
drift[].key required |
string | |
drift_summary required |
string | null | |
installed required |
boolean | |
label required |
string | The launchd label. |
legacy required |
object | null | Set when this checkout has a file-installed ai.blowhorn.service it wrote before the dev label (the app's label, which the app may still adopt). `blowhorn service update` moves it to this label. |
legacy.label required |
string | |
legacy.loaded required |
boolean | |
legacy.message required |
string | |
legacy.plist_path required |
string | |
loaded required |
boolean | |
ok required |
any | |
owner required |
string | Values: |
parent_bundle_version required |
string | null | launchctl's `parent bundle version` for an app-registered job, the version of the copy that registered it. Null when not loaded, when launchctl names none, or for a file-owned job. |
plist_path required |
string | |
program_path required |
string | null | The executable the loaded job runs. For an app-owned job, the running pid's executable (`ps -o comm=`), which names the copy of Blowhorn that registered it; for a file-owned job, ProgramArguments[0] of the installed plist. Null when unknown or not loaded. |
project_dir required |
string | |
resident required |
one of ServiceResidentState, null | |
state_path required |
string | |
stderr required |
string | |
stdout required |
string | |
tick_running required |
object | |
tick_running.pid required |
integer | null | |
tick_running.running required |
boolean | |
tick_running.started_at required |
string | null | |
unreadable required |
string | null | Why an installed plist could not be compared. |
up_to_date required |
boolean | null | Null when nothing is installed or the installed file could not be compared. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceStatusResponse
| Name | Type | Description |
|---|---|---|
chrome required |
null | Reserved; always null today. |
control_socket required |
string | |
drift required |
array of object | |
drift[].expected required |
any | |
drift[].installed required |
any | |
drift[].key required |
string | |
drift_summary required |
string | null | |
installed required |
boolean | |
label required |
string | The launchd label. |
legacy required |
object | null | Set when this checkout has a file-installed ai.blowhorn.service it wrote before the dev label (the app's label, which the app may still adopt). `blowhorn service update` moves it to this label. |
legacy.label required |
string | |
legacy.loaded required |
boolean | |
legacy.message required |
string | |
legacy.plist_path required |
string | |
loaded required |
boolean | |
ok required |
any | |
owner required |
string | Values: |
parent_bundle_version required |
string | null | launchctl's `parent bundle version` for an app-registered job, the version of the copy that registered it. Null when not loaded, when launchctl names none, or for a file-owned job. |
plist_path required |
string | |
program_path required |
string | null | The executable the loaded job runs. For an app-owned job, the running pid's executable (`ps -o comm=`), which names the copy of Blowhorn that registered it; for a file-owned job, ProgramArguments[0] of the installed plist. Null when unknown or not loaded. |
project_dir required |
string | |
resident required |
one of ServiceResidentState, null | |
state_path required |
string | |
stderr required |
string | |
stdout required |
string | |
tick_running required |
object | |
tick_running.pid required |
integer | null | |
tick_running.running required |
boolean | |
tick_running.started_at required |
string | null | |
unreadable required |
string | null | Why an installed plist could not be compared. |
up_to_date required |
boolean | null | Null when nothing is installed or the installed file could not be compared. |
POST
/cli/service/stop
Stop the background service, waiting for the pass in flight to finish; the installed definition stays.
Stop the background service, waiting for the pass in flight to finish; the installed definition stays.
Request body required
The flags and arguments of `blowhorn service stop`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service stop' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service stop' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service stop prints with --json on success: whether the service was stopped, the reason nothing was done, or with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
stopped required |
boolean | |
waited_s |
number | Seconds waited for the service to leave launchd. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceStopResponse
| Name | Type | Description |
|---|---|---|
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
stopped required |
boolean | |
waited_s |
number | Seconds waited for the service to leave launchd. |
POST
/cli/service/test
Validate the schedule and preview a pass, the way the service would run it.
Validate the schedule and preview a pass, the way the service would run it.
Request body required
The flags and arguments of `blowhorn service test`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service test' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service test' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service test prints with --json on success: the outputs of schedule validate and a dry-run schedule tick, or with --dry-run the two commands it would run.
Result
| Name | Type | Description |
|---|---|---|
label required |
string | |
loaded required |
boolean | |
message required |
string | |
ok required |
boolean | |
owner required |
string | |
preview required |
any | The parsed JSON of the dry-run schedule tick (see ScheduleTickResponse), or its raw text. |
validate required |
any | The parsed JSON of schedule validate, or its raw text when it was not JSON. |
DryRun
| Name | Type | Description |
|---|---|---|
commands required |
array of array of string | |
dry_run required |
boolean | |
message required |
string | |
ok required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceTestResponse
POST
/cli/service/uninstall
Stop and remove the per-user background service this CLI installed.
Stop and remove the per-user background service this CLI installed.
Request body required
The flags and arguments of `blowhorn service uninstall`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service uninstall' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service uninstall' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service uninstall prints with --json on success: whether the service definition was removed, the reason nothing was done, or with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
removed required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceUninstallResponse
| Name | Type | Description |
|---|---|---|
dry_run |
boolean | Present only with --dry-run. |
label required |
string | The launchd label of the background service. |
message required |
string | |
ok required |
boolean | |
owner required |
string | Which owner registered the service (file, app or none). |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (for example not installed, already installed). |
removed required |
boolean |
POST
/cli/service/update
Bring the installed service definition up to date with this checkout's template.
Bring the installed service definition up to date with this checkout's template.
Request body required
The flags and arguments of `blowhorn service update`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn service update' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn service': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn service update' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What service update prints with --json on success: whether the installed service definition was rewritten to match this checkout, or why nothing was done; with --dry-run the plan.
| Name | Type | Description |
|---|---|---|
drift required |
array of string | The keys of the installed definition that differ from this checkout's. |
drift_summary required |
string | null | |
dry_run |
boolean | |
label required |
string | |
loaded |
boolean | |
message |
string | |
ok required |
boolean | |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (registered by Blowhorn.app, not installed, up to date). |
reloaded |
boolean | |
tick_running required |
object | |
tick_running.pid required |
integer | null | |
tick_running.running required |
boolean | |
tick_running.started_at required |
string | null | |
up_to_date |
boolean | |
updated required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ServiceUpdateResponse
| Name | Type | Description |
|---|---|---|
drift required |
array of string | The keys of the installed definition that differ from this checkout's. |
drift_summary required |
string | null | |
dry_run |
boolean | |
label required |
string | |
loaded |
boolean | |
message |
string | |
ok required |
boolean | |
plist_path required |
string | |
reason |
string | Present when nothing needed doing (registered by Blowhorn.app, not installed, up to date). |
reloaded |
boolean | |
tick_running required |
object | |
tick_running.pid required |
integer | null | |
tick_running.running required |
boolean | |
tick_running.started_at required |
string | null | |
up_to_date |
boolean | |
updated required |
boolean |
Desktop
POST
/cli/desktop/build
Package the Electron desktop app as an ad-hoc-signed universal macOS build.
Package the Electron desktop app as an ad-hoc-signed universal macOS build.
Request body required
The flags and arguments of `blowhorn desktop build`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dir_only |
boolean | Build the unpacked app directory instead of a distributable Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn desktop build' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn desktop': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn desktop build' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The desktop build command, planned (--dry-run) or run with its captured output.
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
cwd required |
string | |
dry_run required |
boolean | |
ok required |
boolean |
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
ok required |
boolean | |
script required |
string | The package script that ran (for example dist:mac). |
stderr required |
string | |
stdout required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
DesktopBuildResponse
POST
/cli/desktop/install
Install the Electron desktop app dependencies with pnpm (pnpm install --frozen-lockfile).
Install the Electron desktop app dependencies with pnpm (pnpm install --frozen-lockfile).
Request body required
The flags and arguments of `blowhorn desktop install`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn desktop install' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn desktop': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn desktop install' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The dependency install command, planned (--dry-run) or run with its captured output.
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
cwd required |
string | |
dry_run required |
boolean | |
ok required |
boolean |
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
ok required |
boolean | |
stderr required |
string | |
stdout required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
DesktopInstallResponse
POST
/cli/desktop/start
Launch the Electron desktop app through pnpm dev; refuse when one is already running.
Launch the Electron desktop app through pnpm dev; refuse when one is already running.
Request body required
The flags and arguments of `blowhorn desktop start`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn desktop start' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn desktop': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn desktop start' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
wait |
boolean | Wait for the desktop app process to exit instead of detaching Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Starting the desktop app - planned (--dry-run), detached with a pid and log path, or (--wait) run to exit with its captured output.
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
cwd required |
string | |
dry_run required |
boolean | |
ok required |
boolean | |
wait required |
boolean |
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
log_path required |
string | |
ok required |
boolean | |
pid required |
integer | |
wait required |
boolean |
object
| Name | Type | Description |
|---|---|---|
command required |
array of string | |
ok required |
boolean | |
stderr required |
string | |
stdout required |
string | |
wait required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
DesktopStartResponse
POST
/cli/desktop/status
Show whether desktop dependencies and packaged output are present. Read-only: it names the package manager an install would start from (a matching pnpm, else corepack unprobed, else npm exec) and runs no corepack download; 'desktop install' is what checks corepack can run the pinned pnpm.
Show whether desktop dependencies and packaged output are present. Read-only: it names the package manager an install would start from (a matching pnpm, else corepack unprobed, else npm exec) and runs no corepack download; 'desktop install' is what checks corepack can run the pinned pnpm.
Request body required
The flags and arguments of `blowhorn desktop status`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn desktop status' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn desktop': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn desktop status' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Whether the desktop app's dependencies are installed and the app built, from the desktop directory on disk.
| Name | Type | Description |
|---|---|---|
built required |
boolean | The out directory exists. |
desktop_dir required |
string | |
installed required |
boolean | node_modules exists. |
ok required |
boolean | |
package_manager required |
string | null | Name of the package manager found, null when none. |
pnpm_lock required |
boolean | |
release_dir required |
string | |
release_exists required |
boolean |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
DesktopStatusResponse
| Name | Type | Description |
|---|---|---|
built required |
boolean | The out directory exists. |
desktop_dir required |
string | |
installed required |
boolean | node_modules exists. |
ok required |
boolean | |
package_manager required |
string | null | Name of the package manager found, null when none. |
pnpm_lock required |
boolean | |
release_dir required |
string | |
release_exists required |
boolean |
Store
POST
/cli/store
Report whether this computer can use Blowhorn, and say why not when it cannot: signed in, a client the server still accepts, and a member of the organization. --replay-spool also drains the results already waiting in the local write-back spool; apart from it, nothing here writes anything.
Report whether this computer can use Blowhorn, and say why not when it cannot: signed in, a client the server still accepts, and a member of the organization. --replay-spool also drains the results already waiting in the local write-back spool; apart from it, nothing here writes anything.
Exits 0 when the store is usable and 3 when it is not -- the same code every other command uses for an unreachable store, so a wrapper can tell an outage from a failed job. --json emits the check as one object: ok, exitCode, cause, error, organization, reachable, spoolDepth, warnings, rungs, replay and cloud; configured is always true, and configPath, configMode, dsn, tunnel, serverVersion, schema and validation are always null.
Request body required
The flags and arguments of `blowhorn store`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the check as machine-readable JSON (the shape the desktop app's Setup row reads) Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn store' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn store': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
replay_spool |
boolean | Write out the results waiting in the local write-back spool now, rather than leaving them for the next run to drain, and report how many write-backs were pushed, kept and dropped. An entry whose activity no longer exists is named and dropped from the pending file (the month's ledger still holds it); nothing else is ever deleted. With --dry-run it writes nothing and reports what the run without it would have done Default: |
risk_profile |
string | Accepted by 'blowhorn store' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the Blowhorn check: the capabilities handshake as one rung, a replay rung with `--replay-spool`, and any warnings. One shape covers a usable store (`ok: true`, exit 0) and a named cause (`ok: false`, exit 3), so the desktop's Setup row reads both alike. The keys the retired Postgres probe filled (`configPath`, `configMode`, `dsn`, `tunnel`, `serverVersion`, `schema`) are always null and `properties` is always empty.
| Name | Type | Description |
|---|---|---|
cause required |
string | null | The named cause (cloud_signed_out, cloud_refused, cloud_upgrade_required, cloud_rate_limited and so on); null when the store is usable. |
cloud |
object | The capabilities handshake. |
cloud.bound required |
boolean | |
cloud.cause required |
string | null | |
cloud.contract_version required |
string | null | |
cloud.message required |
string | |
cloud.notice required |
string | null | Set when this organization has more computers registered than its plan allows. |
cloud.ok required |
boolean | |
cloud.organization_id required |
string | null | |
configMode required |
string | null | The private file's mode, such as 0600. |
configPath required |
string | null | |
configured required |
boolean | null | |
dsn required |
string | null | The connection string with the password replaced by asterisks. |
error required |
string | null | |
exitCode required |
integer | Values: |
offline required |
boolean | |
ok required |
boolean | |
organization required |
string | null | |
properties required |
array of StoreSettingRow | |
properties[].choices required |
array of string | |
properties[].document required |
string | Which file holds it - the tracked config or the private connection file. Values: |
properties[].hint required |
string | |
properties[].key required |
string | The dotted setting name, such as store.host or defaults.store.organization. |
properties[].kind required |
string | Values: |
properties[].label required |
string | |
properties[].masked required |
boolean | |
properties[].source required |
string | The layer that supplied the value, such as config, env or builtin. |
properties[].value required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
reachable required |
boolean | null | |
replay required |
one of null, object | |
rungs required |
array of object | |
rungs[].detail required |
string | |
rungs[].name required |
string | |
rungs[].state required |
string | Values: |
schema required |
one of null, object | |
serverVersion required |
string | null | |
spoolDepth required |
integer | Write-backs waiting in the local spool. |
tunnel required |
one of null, object | |
validation required |
null | Reserved; always null today. |
warnings required |
array of string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
StoreResponse
| Name | Type | Description |
|---|---|---|
cause required |
string | null | The named cause (cloud_signed_out, cloud_refused, cloud_upgrade_required, cloud_rate_limited and so on); null when the store is usable. |
cloud |
object | The capabilities handshake. |
cloud.bound required |
boolean | |
cloud.cause required |
string | null | |
cloud.contract_version required |
string | null | |
cloud.message required |
string | |
cloud.notice required |
string | null | Set when this organization has more computers registered than its plan allows. |
cloud.ok required |
boolean | |
cloud.organization_id required |
string | null | |
configMode required |
string | null | The private file's mode, such as 0600. |
configPath required |
string | null | |
configured required |
boolean | null | |
dsn required |
string | null | The connection string with the password replaced by asterisks. |
error required |
string | null | |
exitCode required |
integer | Values: |
offline required |
boolean | |
ok required |
boolean | |
organization required |
string | null | |
properties required |
array of StoreSettingRow | |
properties[].choices required |
array of string | |
properties[].document required |
string | Which file holds it - the tracked config or the private connection file. Values: |
properties[].hint required |
string | |
properties[].key required |
string | The dotted setting name, such as store.host or defaults.store.organization. |
properties[].kind required |
string | Values: |
properties[].label required |
string | |
properties[].masked required |
boolean | |
properties[].source required |
string | The layer that supplied the value, such as config, env or builtin. |
properties[].value required |
ConfigOptionValue | A value a config option can hold: a string, number, integer, boolean, a list of strings, null (unset), or, for a masked option such as `store.password`, the object `{set: boolean}` that says whether a value exists and never carries it. |
reachable required |
boolean | null | |
replay required |
one of null, object | |
rungs required |
array of object | |
rungs[].detail required |
string | |
rungs[].name required |
string | |
rungs[].state required |
string | Values: |
schema required |
one of null, object | |
serverVersion required |
string | null | |
spoolDepth required |
integer | Write-backs waiting in the local spool. |
tunnel required |
one of null, object | |
validation required |
null | Reserved; always null today. |
warnings required |
array of string |
Analytics
POST
/cli/analytics/activities
Print several activities' results at once, from one set of store reads: the same result 'analytics activity' prints for each (delivery, reception where it is collected, and the derived follower change, per profile and platform), in the order given. An activity number the queue does not have is listed as not found rather than failing the rest. A computer with no store prints a short explanation and exits 3.
Print several activities' results at once, from one set of store reads: the same result 'analytics activity' prints for each (delivery, reception where it is collected, and the derived follower change, per profile and platform), in the order given. An activity number the queue does not have is listed as not found rather than failing the rest. A computer with no store prints a short explanation and exits 3.
Request body required
The flags and arguments of `blowhorn analytics activities`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activities required |
array of integer | Activity numbers, as 'content list' prints them |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics activities' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn analytics activities' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints several activities' results from one set of store reads: one `AnalyticsActivityResponse` per activity found, in the order asked, and `missing`, the activity numbers (as strings, the way `activityId` spells them) the content queue does not have, in the order asked. A computer with no store prints the Problem shape and exits 3.
| Name | Type | Description |
|---|---|---|
activities required |
array of AnalyticsActivityResponse | |
activities[].activityId required |
string | |
activities[].entries required |
array of ActivityResultEntry | |
activities[].entries[].delivery required |
ActivityDelivery | Mirrors meshery/schemas v1beta3 `ActivityDelivery`. Whether the activity went out for one profile and platform, from the content item's recorded outcome and the run log. Measured, never derived. |
activities[].entries[].followerChange required |
ActivityFollowerChange | Mirrors meshery/schemas v1beta3 `ActivityFollowerChange`. Derived: followers at the reading before the activity and the first reading at least `windowDays` after it. Correlation only. |
activities[].entries[].platform required |
string | |
activities[].entries[].profile required |
string | |
activities[].entries[].reception required |
ActivityReception | Mirrors meshery/schemas v1beta3 `ActivityReception`. Measured reception with the date it was read, or collected false with a reason and one sentence. Never zeros for missing data. `comparison` is derived: the median of the same subject's other activities and this activity's ratio to it. |
activities[].generatedAt required |
string (date-time) | |
activities[].kind required |
string | post, repost or comment. |
activities[].ok required |
any | |
activities[].platform required |
string | |
activities[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
activities[].title required |
string | |
generatedAt required |
string (date-time) | |
missing required |
array of string | |
ok required |
any |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsActivitiesResponse
| Name | Type | Description |
|---|---|---|
activities required |
array of AnalyticsActivityResponse | |
activities[].activityId required |
string | |
activities[].entries required |
array of ActivityResultEntry | |
activities[].entries[].delivery required |
ActivityDelivery | Mirrors meshery/schemas v1beta3 `ActivityDelivery`. Whether the activity went out for one profile and platform, from the content item's recorded outcome and the run log. Measured, never derived. |
activities[].entries[].followerChange required |
ActivityFollowerChange | Mirrors meshery/schemas v1beta3 `ActivityFollowerChange`. Derived: followers at the reading before the activity and the first reading at least `windowDays` after it. Correlation only. |
activities[].entries[].platform required |
string | |
activities[].entries[].profile required |
string | |
activities[].entries[].reception required |
ActivityReception | Mirrors meshery/schemas v1beta3 `ActivityReception`. Measured reception with the date it was read, or collected false with a reason and one sentence. Never zeros for missing data. `comparison` is derived: the median of the same subject's other activities and this activity's ratio to it. |
activities[].generatedAt required |
string (date-time) | |
activities[].kind required |
string | post, repost or comment. |
activities[].ok required |
any | |
activities[].platform required |
string | |
activities[].state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
activities[].title required |
string | |
generatedAt required |
string (date-time) | |
missing required |
array of string | |
ok required |
any |
POST
/cli/analytics/activity
Print one activity's result, read from the store: for each profile and platform it is addressed to, whether it went out (when, the live link, which computer and browser profile ran it, and on a failure the error and the fix), how it was received where reception is collected (LinkedIn page activities, from the page content exports, with the date the figures were read and a comparison with the page's typical activity), and the follower change around it, labelled derived. Anything not collected says so, with a reason, never as a zero. A computer with no store prints a short explanation and exits 3.
Print one activity's result, read from the store: for each profile and platform it is addressed to, whether it went out (when, the live link, which computer and browser profile ran it, and on a failure the error and the fix), how it was received where reception is collected (LinkedIn page activities, from the page content exports, with the date the figures were read and a comparison with the page's typical activity), and the follower change around it, labelled derived. Anything not collected says so, with a reason, never as a zero. A computer with no store prints a short explanation and exits 3.
Request body required
The flags and arguments of `blowhorn analytics activity`, one property per parameter.
| Name | Type | Description |
|---|---|---|
activity required |
integer | Activity number, as 'content list' prints it |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics activity' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn analytics activity' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints one activity's result: meshery/schemas v1beta3 `ActivityResult` (camelCase, as that contract spells it) plus `ok`. `activityId` is the content item's row number. One entry per profile and platform the activity is addressed to, each with delivery, reception and the derived follower change. A computer with no store prints the Problem shape and exits 3.
| Name | Type | Description |
|---|---|---|
activityId required |
string | |
entries required |
array of ActivityResultEntry | |
entries[].delivery required |
ActivityDelivery | Mirrors meshery/schemas v1beta3 `ActivityDelivery`. Whether the activity went out for one profile and platform, from the content item's recorded outcome and the run log. Measured, never derived. |
entries[].delivery.attemptedAt required |
string (date-time) | null | |
entries[].delivery.browserProfile required |
object | null | |
entries[].delivery.computer required |
string | null | Host name of the computer whose run delivered or attempted it. |
entries[].delivery.deliveredAt required |
string (date-time) | null | |
entries[].delivery.durationSeconds required |
number | null | |
entries[].delivery.error required |
string | null | |
entries[].delivery.evidence required |
string | What the status rests on. Values: |
entries[].delivery.fix required |
object | null | |
entries[].delivery.link required |
string | null | Link to the live item. |
entries[].delivery.logPath required |
string | null | Log file of the matched run, on the computer that ran it. |
entries[].delivery.runId required |
string | null | |
entries[].delivery.scheduledFor required |
string (date-time) | null | |
entries[].delivery.status required |
string | delivered, unconfirmed (clicked, outcome not read), failed (last attempt failed), scheduled, pending (due, not yet run), draft (not approved). Values: |
entries[].followerChange required |
ActivityFollowerChange | Mirrors meshery/schemas v1beta3 `ActivityFollowerChange`. Derived: followers at the reading before the activity and the first reading at least `windowDays` after it. Correlation only. |
entries[].followerChange.after |
object | null | |
entries[].followerChange.before |
object | null | |
entries[].followerChange.change |
integer | null | |
entries[].followerChange.collected required |
boolean | |
entries[].followerChange.derived required |
any | |
entries[].followerChange.formula required |
string | |
entries[].followerChange.message |
string | |
entries[].followerChange.reason |
string | Values: |
entries[].followerChange.subject |
string | |
entries[].followerChange.windowDays required |
integer | |
entries[].platform required |
string | |
entries[].profile required |
string | |
entries[].reception required |
ActivityReception | Mirrors meshery/schemas v1beta3 `ActivityReception`. Measured reception with the date it was read, or collected false with a reason and one sentence. Never zeros for missing data. `comparison` is derived: the median of the same subject's other activities and this activity's ratio to it. |
entries[].reception.asOf |
string (date) | null | Date the figures were read. |
entries[].reception.collected required |
boolean | |
entries[].reception.comparison |
object | null | |
entries[].reception.measured required |
any | |
entries[].reception.message |
string | |
entries[].reception.metrics |
ActivityMetrics | Mirrors meshery/schemas v1beta3 `ActivityMetrics`. Per-activity engagement figures. The known keys are always present and are null when the source did not report them, never zero. A platform-specific figure not listed here is carried under its own key only when reported. |
entries[].reception.reason |
string | Values: |
entries[].reception.source |
string | Where the figures came from. |
entries[].reception.subject |
string | Account or page the figures belong to. |
generatedAt required |
string (date-time) | |
kind required |
string | post, repost or comment. |
ok required |
any | |
platform required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
title required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsActivityResponse
| Name | Type | Description |
|---|---|---|
activityId required |
string | |
entries required |
array of ActivityResultEntry | |
entries[].delivery required |
ActivityDelivery | Mirrors meshery/schemas v1beta3 `ActivityDelivery`. Whether the activity went out for one profile and platform, from the content item's recorded outcome and the run log. Measured, never derived. |
entries[].delivery.attemptedAt required |
string (date-time) | null | |
entries[].delivery.browserProfile required |
object | null | |
entries[].delivery.computer required |
string | null | Host name of the computer whose run delivered or attempted it. |
entries[].delivery.deliveredAt required |
string (date-time) | null | |
entries[].delivery.durationSeconds required |
number | null | |
entries[].delivery.error required |
string | null | |
entries[].delivery.evidence required |
string | What the status rests on. Values: |
entries[].delivery.fix required |
object | null | |
entries[].delivery.link required |
string | null | Link to the live item. |
entries[].delivery.logPath required |
string | null | Log file of the matched run, on the computer that ran it. |
entries[].delivery.runId required |
string | null | |
entries[].delivery.scheduledFor required |
string (date-time) | null | |
entries[].delivery.status required |
string | delivered, unconfirmed (clicked, outcome not read), failed (last attempt failed), scheduled, pending (due, not yet run), draft (not approved). Values: |
entries[].followerChange required |
ActivityFollowerChange | Mirrors meshery/schemas v1beta3 `ActivityFollowerChange`. Derived: followers at the reading before the activity and the first reading at least `windowDays` after it. Correlation only. |
entries[].followerChange.after |
object | null | |
entries[].followerChange.before |
object | null | |
entries[].followerChange.change |
integer | null | |
entries[].followerChange.collected required |
boolean | |
entries[].followerChange.derived required |
any | |
entries[].followerChange.formula required |
string | |
entries[].followerChange.message |
string | |
entries[].followerChange.reason |
string | Values: |
entries[].followerChange.subject |
string | |
entries[].followerChange.windowDays required |
integer | |
entries[].platform required |
string | |
entries[].profile required |
string | |
entries[].reception required |
ActivityReception | Mirrors meshery/schemas v1beta3 `ActivityReception`. Measured reception with the date it was read, or collected false with a reason and one sentence. Never zeros for missing data. `comparison` is derived: the median of the same subject's other activities and this activity's ratio to it. |
entries[].reception.asOf |
string (date) | null | Date the figures were read. |
entries[].reception.collected required |
boolean | |
entries[].reception.comparison |
object | null | |
entries[].reception.measured required |
any | |
entries[].reception.message |
string | |
entries[].reception.metrics |
ActivityMetrics | Mirrors meshery/schemas v1beta3 `ActivityMetrics`. Per-activity engagement figures. The known keys are always present and are null when the source did not report them, never zero. A platform-specific figure not listed here is carried under its own key only when reported. |
entries[].reception.reason |
string | Values: |
entries[].reception.source |
string | Where the figures came from. |
entries[].reception.subject |
string | Account or page the figures belong to. |
generatedAt required |
string (date-time) | |
kind required |
string | post, repost or comment. |
ok required |
any | |
platform required |
string | |
state required |
ContentActivityState | Where a content activity stands. `pending` is ready to post, `scheduled` waits for its Promote On time, `draft` is not approved, `partial` has posted for some profiles, `done` for all, `unread` was clicked with its outcome never read, `invalid` has an unparseable Promote On and `unsupported` names no known platform. Values: |
title required |
string |
POST
/cli/analytics/history
Print the follower history the collectors have already recorded, as one dated series read from the store: every LinkedIn page and every profile's X trend, whichever machine collected them. Tolerates the N/A cells the collectors write, and derives the 30-day net, growth rate and (LinkedIn pages only) unfollowed figures, each labelled derived in the output; X profiles carry a net change and no unfollowed count, because X measures only the total. A store that cannot be reached exits 3.
Print the follower history the collectors have already recorded, as one dated series read from the store: every LinkedIn page and every profile's X trend, whichever machine collected them. Tolerates the N/A cells the collectors write, and derives the 30-day net, growth rate and (LinkedIn pages only) unfollowed figures, each labelled derived in the output; X profiles carry a net change and no unfollowed count, because X measures only the total. A store that cannot be reached exits 3.
Request body required
The flags and arguments of `blowhorn analytics history`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics history' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn analytics history' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
since |
string | How far back to read: a number with d, w, m or y (30d, 12m, 1y), or 'all' (default: 12m) |
subject |
string | One page or profile name (all by default) |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints follower history read from the analytics CSVs: one series point per subject per date, a summary per subject with the 30-day derivations, and what each source file held. LinkedIn pages also carry `unfollowed_30d` and their reception: the latest impressions and reactions, the date they were read, and their derived 30-day change.
| Name | Type | Description |
|---|---|---|
baseline_days required |
integer | |
derived required |
object | Derived field name to how it is computed. |
ok required |
any | |
series required |
array of object | |
series[].date required |
string | |
series[].followers required |
integer | null | |
series[].following required |
integer | null | |
series[].growth_rate required |
number | null | |
series[].impressions required |
integer | null | |
series[].kind required |
string | Values: |
series[].net_30d required |
integer | null | |
series[].new_30d required |
integer | null | |
series[].platform required |
string | |
series[].reactions required |
integer | null | |
series[].subject required |
string | |
series[].unfollowed_30d |
integer | null | LinkedIn pages only. |
since required |
string | null | |
since_label required |
string | |
sources required |
array of object | |
sources[].error required |
string | null | |
sources[].exists required |
boolean | |
sources[].kind required |
string | Values: |
sources[].path required |
string | null | |
sources[].platform required |
string | |
sources[].profile |
string | The profile a per-profile source belongs to. |
sources[].rows required |
integer | |
sources[].skipped required |
integer | |
subject required |
string | The subject asked for, or `all`. |
subjects required |
array of object | |
subjects[].baseline_date required |
string | null | The reading the 30-day figures are measured against. |
subjects[].first required |
string | |
subjects[].followers required |
integer | null | |
subjects[].following required |
integer | null | |
subjects[].growth_rate required |
number | null | |
subjects[].impressions |
integer | null | LinkedIn pages only. Impressions at the latest reading that has reception figures. |
subjects[].impressions_change_30d |
integer | null | LinkedIn pages only. Derived; impressions minus the reading at least 30 days earlier. |
subjects[].kind required |
string | Values: |
subjects[].last required |
string | |
subjects[].net_30d required |
integer | null | |
subjects[].new_30d required |
integer | null | |
subjects[].platform required |
string | |
subjects[].points required |
integer | |
subjects[].reactions |
integer | null | LinkedIn pages only. Reactions at that reading. |
subjects[].reactions_change_30d |
integer | null | LinkedIn pages only. Derived; reactions minus the reading at least 30 days earlier. |
subjects[].reception_date |
string | null | LinkedIn pages only. The date the reception figures were read. |
subjects[].subject required |
string | |
subjects[].unfollowed_30d |
integer | null | LinkedIn pages only. |
subjects[].username required |
string | null | |
unreadable_rows required |
integer | |
until required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsHistoryResponse
| Name | Type | Description |
|---|---|---|
baseline_days required |
integer | |
derived required |
object | Derived field name to how it is computed. |
ok required |
any | |
series required |
array of object | |
series[].date required |
string | |
series[].followers required |
integer | null | |
series[].following required |
integer | null | |
series[].growth_rate required |
number | null | |
series[].impressions required |
integer | null | |
series[].kind required |
string | Values: |
series[].net_30d required |
integer | null | |
series[].new_30d required |
integer | null | |
series[].platform required |
string | |
series[].reactions required |
integer | null | |
series[].subject required |
string | |
series[].unfollowed_30d |
integer | null | LinkedIn pages only. |
since required |
string | null | |
since_label required |
string | |
sources required |
array of object | |
sources[].error required |
string | null | |
sources[].exists required |
boolean | |
sources[].kind required |
string | Values: |
sources[].path required |
string | null | |
sources[].platform required |
string | |
sources[].profile |
string | The profile a per-profile source belongs to. |
sources[].rows required |
integer | |
sources[].skipped required |
integer | |
subject required |
string | The subject asked for, or `all`. |
subjects required |
array of object | |
subjects[].baseline_date required |
string | null | The reading the 30-day figures are measured against. |
subjects[].first required |
string | |
subjects[].followers required |
integer | null | |
subjects[].following required |
integer | null | |
subjects[].growth_rate required |
number | null | |
subjects[].impressions |
integer | null | LinkedIn pages only. Impressions at the latest reading that has reception figures. |
subjects[].impressions_change_30d |
integer | null | LinkedIn pages only. Derived; impressions minus the reading at least 30 days earlier. |
subjects[].kind required |
string | Values: |
subjects[].last required |
string | |
subjects[].net_30d required |
integer | null | |
subjects[].new_30d required |
integer | null | |
subjects[].platform required |
string | |
subjects[].points required |
integer | |
subjects[].reactions |
integer | null | LinkedIn pages only. Reactions at that reading. |
subjects[].reactions_change_30d |
integer | null | LinkedIn pages only. Derived; reactions minus the reading at least 30 days earlier. |
subjects[].reception_date |
string | null | LinkedIn pages only. The date the reception figures were read. |
subjects[].subject required |
string | |
subjects[].unfollowed_30d |
integer | null | LinkedIn pages only. |
subjects[].username required |
string | null | |
unreadable_rows required |
integer | |
until required |
string |
POST
/cli/analytics/import-exports
Read the LinkedIn page content exports already on disk and record each activity's reception figures in the store: impressions, views, clicks, click-through rate, likes, comments, reposts, follows and engagement rate from each export's All posts sheet, dated by the export. Each activity is matched to its activity by its link; activities made outside Blowhorn are kept and marked unmatched. Prints activities read, matched and stored per file. Safe to run again: a reading lands once per activity and export date. --dry-run reads and matches without writing.
Read the LinkedIn page content exports already on disk and record each activity's reception figures in the store: impressions, views, clicks, click-through rate, likes, comments, reposts, follows and engagement rate from each export's All posts sheet, dated by the export. Each activity is matched to its activity by its link; activities made outside Blowhorn are kept and marked unmatched. Prints activities read, matched and stored per file. Safe to run again: a reading lands once per activity and export date. --dry-run reads and matches without writing.
Request body required
The flags and arguments of `blowhorn analytics import-exports`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics import-exports' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
paths |
array of string | Export files to read (default: every content export in the analytics folder) |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn analytics import-exports' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the per-file verification of an import of LinkedIn page content exports' All posts sheets: rows read, matched to a content item by link, unmatched (kept), skipped with a reason, and the error for a file that could not be read, plus totals. `stored` is 0 on a dry run.
| Name | Type | Description |
|---|---|---|
dryRun required |
boolean | |
files required |
array of object | |
files[].activities required |
integer | |
files[].error required |
string | null | |
files[].exportDate required |
string (date) | null | |
files[].file required |
string | |
files[].matched required |
integer | |
files[].page required |
string | null | |
files[].sheet required |
string | |
files[].sheetPresent required |
boolean | |
files[].skipped required |
array of object | |
files[].skipped[].reason required |
string | |
files[].skipped[].row required |
integer | |
files[].unmatched required |
integer | |
ok required |
any | |
totals required |
object | |
totals.activities required |
integer | |
totals.errors required |
integer | |
totals.files required |
integer | |
totals.filesWithSheet required |
integer | |
totals.matched required |
integer | |
totals.readings required |
integer | |
totals.stored required |
integer | |
totals.unmatched required |
integer |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsImportExportsResponse
| Name | Type | Description |
|---|---|---|
dryRun required |
boolean | |
files required |
array of object | |
files[].activities required |
integer | |
files[].error required |
string | null | |
files[].exportDate required |
string (date) | null | |
files[].file required |
string | |
files[].matched required |
integer | |
files[].page required |
string | null | |
files[].sheet required |
string | |
files[].sheetPresent required |
boolean | |
files[].skipped required |
array of object | |
files[].skipped[].reason required |
string | |
files[].skipped[].row required |
integer | |
files[].unmatched required |
integer | |
ok required |
any | |
totals required |
object | |
totals.activities required |
integer | |
totals.errors required |
integer | |
totals.files required |
integer | |
totals.filesWithSheet required |
integer | |
totals.matched required |
integer | |
totals.readings required |
integer | |
totals.stored required |
integer | |
totals.unmatched required |
integer |
POST
/cli/analytics/operations
Aggregate the run ledger, read from the store, into operations per profile, per platform, per command, per day or per host: how many runs each action made, what they confirmed, skipped and failed, and which profiles and platforms were active. Reads the ledger only - a dry run is counted apart and never as an operation, and records exist from the first run after this command shipped, so a window older than that reports zero rather than guessing. Per host is the per-machine history now that several machines share one store. A store that cannot be reached exits 3.
Aggregate the run ledger, read from the store, into operations per profile, per platform, per command, per day or per host: how many runs each action made, what they confirmed, skipped and failed, and which profiles and platforms were active. Reads the ledger only - a dry run is counted apart and never as an operation, and records exist from the first run after this command shipped, so a window older than that reports zero rather than guessing. Per host is the per-machine history now that several machines share one store. A store that cannot be reached exits 3.
Request body required
The flags and arguments of `blowhorn analytics operations`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
group_by |
string | Aggregate per profile, platform, command, day or host -- host is the machine that ran it (default: profile) |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
kind |
string | Which ledger records to aggregate: run (action runs, the default) or tick (scheduler passes) |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics operations' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn analytics operations' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
since |
string | How far back to read: a number with d, w, m or y (30d, 12m, 1y), or 'all' (default: 30d) |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints the operations ledger aggregated for a window: totals, and one group per profile, platform, command, day or host with its record count, failed runs, summed counts and breakdowns. A dry run counts only in `dry_runs`.
| Name | Type | Description |
|---|---|---|
dry_runs required |
integer | |
group_by required |
string | Values: |
groups required |
array of object | |
groups[].by_command required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].by_platform required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].by_profile required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].counts required |
RunCounts | Summed action counts of a set of ledger records. For a scheduler tick record the counts are activities, not actions. |
groups[].counts.attempted required |
integer | |
groups[].counts.confirmed required |
integer | |
groups[].counts.failed required |
integer | |
groups[].counts.skipped required |
integer | |
groups[].counts.unconfirmed required |
integer | Actions clicked whose outcome was never read. |
groups[].dry_runs required |
integer | |
groups[].failed_runs required |
integer | |
groups[].first_ts required |
string | null | |
groups[].key required |
string | The profile, platform, command, day or host this group is. |
groups[].last_ts required |
string | null | |
groups[].records required |
integer | |
kind required |
string | Values: |
ok required |
any | |
records required |
integer | |
runs_dir required |
string | |
since required |
string | null | |
since_label required |
string | |
totals required |
object | |
totals.by_command required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_platform required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_profile required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_source required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.commands required |
array of string | |
totals.counts required |
RunCounts | Summed action counts of a set of ledger records. For a scheduler tick record the counts are activities, not actions. |
totals.counts.attempted required |
integer | |
totals.counts.confirmed required |
integer | |
totals.counts.failed required |
integer | |
totals.counts.skipped required |
integer | |
totals.counts.unconfirmed required |
integer | Actions clicked whose outcome was never read. |
totals.dry_runs required |
integer | |
totals.failed_runs required |
integer | |
totals.first_ts required |
string | null | |
totals.last_ts required |
string | null | |
totals.platforms_active required |
integer | |
totals.profiles_active required |
integer | |
totals.records required |
integer | |
unreadable_lines required |
integer | |
until required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsOperationsResponse
| Name | Type | Description |
|---|---|---|
dry_runs required |
integer | |
group_by required |
string | Values: |
groups required |
array of object | |
groups[].by_command required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].by_platform required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].by_profile required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
groups[].counts required |
RunCounts | Summed action counts of a set of ledger records. For a scheduler tick record the counts are activities, not actions. |
groups[].counts.attempted required |
integer | |
groups[].counts.confirmed required |
integer | |
groups[].counts.failed required |
integer | |
groups[].counts.skipped required |
integer | |
groups[].counts.unconfirmed required |
integer | Actions clicked whose outcome was never read. |
groups[].dry_runs required |
integer | |
groups[].failed_runs required |
integer | |
groups[].first_ts required |
string | null | |
groups[].key required |
string | The profile, platform, command, day or host this group is. |
groups[].last_ts required |
string | null | |
groups[].records required |
integer | |
kind required |
string | Values: |
ok required |
any | |
records required |
integer | |
runs_dir required |
string | |
since required |
string | null | |
since_label required |
string | |
totals required |
object | |
totals.by_command required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_platform required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_profile required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.by_source required |
CountMap | Record counts keyed by data - a command, a platform, a profile or a source name. |
totals.commands required |
array of string | |
totals.counts required |
RunCounts | Summed action counts of a set of ledger records. For a scheduler tick record the counts are activities, not actions. |
totals.counts.attempted required |
integer | |
totals.counts.confirmed required |
integer | |
totals.counts.failed required |
integer | |
totals.counts.skipped required |
integer | |
totals.counts.unconfirmed required |
integer | Actions clicked whose outcome was never read. |
totals.dry_runs required |
integer | |
totals.failed_runs required |
integer | |
totals.first_ts required |
string | null | |
totals.last_ts required |
string | null | |
totals.platforms_active required |
integer | |
totals.profiles_active required |
integer | |
totals.records required |
integer | |
unreadable_lines required |
integer | |
until required |
string |
POST
/cli/analytics/pages
List, add, remove or seed the LinkedIn pages analytics collects from for the signed-in organization, read from and written to the store: each page's name, its kind (company or showcase) and its numeric page id, from which the followers and content analytics URLs are derived. Collection walks the enabled pages; an organization with none configured collects the pages in use before per-organization configuration, and 'seed' writes those pages into the store once so the organization can edit its own list from there. --dry-run shows an add, remove or seed without writing. A store that cannot be reached exits 3.
List, add, remove or seed the LinkedIn pages analytics collects from for the signed-in organization, read from and written to the store: each page's name, its kind (company or showcase) and its numeric page id, from which the followers and content analytics URLs are derived. Collection walks the enabled pages; an organization with none configured collects the pages in use before per-organization configuration, and 'seed' writes those pages into the store once so the organization can edit its own list from there. --dry-run shows an add, remove or seed without writing. A store that cannot be reached exits 3.
Request body required
The flags and arguments of `blowhorn analytics pages`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn analytics pages' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
pages_action |
string | What to do: list (the default), add, remove or seed |
pages_disabled |
boolean | Add the page disabled: listed, but collection skips it until it is added again without this flag Default: |
pages_id |
string | The page's numeric LinkedIn id, or its LinkedIn page URL (add) |
pages_kind |
string | The page's LinkedIn kind: company or showcase (add; read from the URL when the page value is one) |
pages_name |
string | The page's display name, as analytics history names it (add and remove) |
platform |
string | Platform scope for 'blowhorn analytics'. One of: bluesky, linkedin, x, all ('all' means every platform listed here) Default: |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) Default: |
risk_profile |
string | Accepted by 'blowhorn analytics pages' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The signed-in organization's LinkedIn analytics pages (#741): for `list`, every configured page; for `add` and `seed`, the pages the write left; for `remove`, an empty list. Each page carries its name, its LinkedIn kind (company or showcase), its numeric page id and whether it is enabled, plus the followers and content analytics URLs derived from the kind and id.
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
dryRun required |
boolean | |
ok required |
any | |
pages required |
array of object | |
pages[].content_url required |
string | |
pages[].enabled required |
boolean | |
pages[].followers_url required |
string | |
pages[].name required |
string | |
pages[].page_id required |
string | |
pages[].page_kind required |
string | Values: |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
AnalyticsPagesResponse
| Name | Type | Description |
|---|---|---|
action required |
string | Values: |
dryRun required |
boolean | |
ok required |
any | |
pages required |
array of object | |
pages[].content_url required |
string | |
pages[].enabled required |
boolean | |
pages[].followers_url required |
string | |
pages[].name required |
string | |
pages[].page_id required |
string | |
pages[].page_kind required |
string | Values: |
POST
/cli/report
Generate a consolidated report summary from the exports already sitting in the repository-root data/analytics/ directory. Reads local files only - run 'blowhorn analytics' first if you want fresh numbers.
Generate a consolidated report summary from the exports already sitting in the repository-root data/analytics/ directory. Reads local files only - run 'blowhorn analytics' first if you want fresh numbers.
Request body required
The flags and arguments of `blowhorn report`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
no_open |
boolean | Write data/analytics/analytics_report.html without opening a browser Default: |
pace |
string | Accepted by 'blowhorn report' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope. 'blowhorn report' is not platform-specific, so the only accepted value is 'all' (the default) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn report' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Whether the HTML analytics report was regenerated and opened. With --json, stdout is exactly this one JSON document; the summary table and progress lines are written to stderr instead. The same shape is printed with ok false (and exit 1) when generation failed.
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
error required |
string | null | |
generated required |
boolean | |
ok required |
boolean | |
opened required |
boolean | Whether the report was opened in the default browser; false with --no-open. |
path required |
string | Where the report is (or would be) written. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ReportResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
error required |
string | null | |
generated required |
boolean | |
ok required |
boolean | |
opened required |
boolean | Whether the report was opened in the default browser; false with --no-open. |
path required |
string | Where the report is (or would be) written. |
Platforms
POST
/cli/platforms/capabilities
Show what each platform allows: text limits and counting, media, tagging, actions and published rates, with the provenance of every fact and the tier overrides a profile's account unlocks. A fact with no value is not on record and is never enforced. --platform reads one platform; --stale keeps only the platforms with a fact past its review date.
Show what each platform allows: text limits and counting, media, tagging, actions and published rates, with the provenance of every fact and the tier overrides a profile's account unlocks. A fact with no value is not on record and is never enforced. --platform reads one platform; --stale keeps only the platforms with a fact past its review date.
Request body required
The flags and arguments of `blowhorn platforms capabilities`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit machine-readable JSON output Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn platforms capabilities' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Platform scope for 'blowhorn platforms'. One of: bluesky, facebook, github, hn, instagram, linkedin, reddit, slack, tiktok, x, youtube, all ('all' means every platform listed here) |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn platforms capabilities' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
stale |
boolean | Keep only the platforms with a fact past their review date Default: |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What each platform allows, from the registry: the capability facts with their provenance, the tier overrides a profile's account unlocks, and how Blowhorn itself reads and paces the platform. A fact with no value is not on record and is never enforced.
| Name | Type | Description |
|---|---|---|
ok required |
any | |
platforms required |
object | The platforms read, keyed by platform id. |
stale required |
array of object | The facts past their review date, scoped to the platforms included. |
stale[].path required |
string | The dotted path of the fact. |
stale[].platform required |
string | The platform id the fact belongs to. |
stale[].reviewBy required |
string (date) | The date the fact was due to be re-read by. |
stale[].tier required |
string | null | The tier the override belongs to, or null for a base fact. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
PlatformsCapabilitiesResponse
| Name | Type | Description |
|---|---|---|
ok required |
any | |
platforms required |
object | The platforms read, keyed by platform id. |
stale required |
array of object | The facts past their review date, scoped to the platforms included. |
stale[].path required |
string | The dotted path of the fact. |
stale[].platform required |
string | The platform id the fact belongs to. |
stale[].reviewBy required |
string (date) | The date the fact was due to be re-read by. |
stale[].tier required |
string | null | The tier the override belongs to, or null for a base fact. |
Meta
POST
/cli/classify-url
Say what a link is, by the platform rules the engine keeps: 'post' when it names an existing post or comment on a supported platform, with the verbs that platform takes ('respond', 'amplify'); 'page' for any other web link, with the platform whose site it is on; 'none' for anything that is not a web link. The desktop app's drop reads this to choose its sheet. It reads the link text only: it opens no browser, signs in to nothing and writes nothing.
Say what a link is, by the platform rules the engine keeps: 'post' when it names an existing post or comment on a supported platform, with the verbs that platform takes ('respond', 'amplify'); 'page' for any other web link, with the platform whose site it is on; 'none' for anything that is not a web link. The desktop app's drop reads this to choose its sheet. It reads the link text only: it opens no browser, signs in to nothing and writes nothing.
Request body required
The flags and arguments of `blowhorn classify-url`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the classification as JSON with a stable shape, for a script or the desktop app Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn classify-url' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn classify-url': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn classify-url' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
url required |
string | The link to classify |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
Prints, for one link, what it is: `post` (an existing post or comment on a supported platform, with the verbs it takes), `page` (any other web link) or `none` (not a web link). Nothing is opened, signed in to or written; the classification reads the link text by the engine's own platform rules (`engine/blowhorn/url_classify.py`).
| Name | Type | Description |
|---|---|---|
kind required |
string | post (engage with it), page (a new order that uses the page) or none (not a web link). Values: |
ok required |
any | |
platform required |
string | null | The platform the link names, or null when it names none. Values: |
url required |
string | The link as it was given, trimmed. |
verbs required |
array of string | The engage verbs a post takes, in the order the sheet offers them; empty for a page and for a platform with none. |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
ClassifyUrlResponse
| Name | Type | Description |
|---|---|---|
kind required |
string | post (engage with it), page (a new order that uses the page) or none (not a web link). Values: |
ok required |
any | |
platform required |
string | null | The platform the link names, or null when it names none. Values: |
url required |
string | The link as it was given, trimmed. |
verbs required |
array of string | The engage verbs a post takes, in the order the sheet offers them; empty for a page and for a platform with none. |
POST
/cli/setup
Set up Blowhorn on this computer, safely and repeatably. Each step checks first and changes only what is missing or stale, so running it again is safe; every line says ok (already in place), changed (written by this run), planned, declined, todo (needs you) or failed. It checks that blowhorn is on PATH, lists your Chrome profiles from Chrome's Local State file, writes the Chrome extension's native-messaging host when it is missing or stale, adds the marked PATH block to your shell startup files (~/.zshrc, ~/.bashrc, ~/.config/fish/config.fish) when it is missing, so new terminals find blowhorn in ~/.local/bin, and checks your Blowhorn sign-in. Before it writes the native host it says, in plain language, which Chrome profiles the file reaches and asks; the PATH block is asked about the same way; --yes answers yes for a script, and --dry-run writes nothing. With --json, or with no terminal to ask in, nobody is asked: it writes nothing and reports planned. It never starts, opens or attaches to Chrome and installs no extension. It ends with what is left for you: sign in, install the extension from the Chrome Web Store, check it answers, and get the app from blowhorn.ai/download. --json prints the same as one object.
Set up Blowhorn on this computer, safely and repeatably. Each step checks first and changes only what is missing or stale, so running it again is safe; every line says ok (already in place), changed (written by this run), planned, declined, todo (needs you) or failed. It checks that blowhorn is on PATH, lists your Chrome profiles from Chrome's Local State file, writes the Chrome extension's native-messaging host when it is missing or stale, adds the marked PATH block to your shell startup files (~/.zshrc, ~/.bashrc, ~/.config/fish/config.fish) when it is missing, so new terminals find blowhorn in ~/.local/bin, and checks your Blowhorn sign-in. Before it writes the native host it says, in plain language, which Chrome profiles the file reaches and asks; the PATH block is asked about the same way; --yes answers yes for a script, and --dry-run writes nothing. With --json, or with no terminal to ask in, nobody is asked: it writes nothing and reports planned. It never starts, opens or attaches to Chrome and installs no extension. It ends with what is left for you: sign in, install the extension from the Chrome Web Store, check it answers, and get the app from blowhorn.ai/download. --json prints the same as one object.
Request body required
The flags and arguments of `blowhorn setup`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Print the result as one JSON object: steps, profiles, changed, already_ok and next_steps. Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn setup' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn setup': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn setup' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
yes |
boolean | Agree to the writes setup asks about (the Chrome native host, the PATH block) without being asked, for a script or the desktop app. Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
What setup did or would do, step by step. It carries no ok key; the exit code is 1 when any step failed and the same object is still printed.
| Name | Type | Description |
|---|---|---|
already_ok required |
array of string | Names of the steps whose state is ok. |
changed required |
array of string | Names of the steps whose state is changed. |
next_steps required |
array of string | |
profiles required |
array of string | Chrome profiles detected, labelled by name and directory. These are real people's names; never commit real output. |
steps required |
array of object | |
steps[].detail required |
string | |
steps[].name required |
string | |
steps[].state required |
string | Values: |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
SetupResponse
| Name | Type | Description |
|---|---|---|
already_ok required |
array of string | Names of the steps whose state is ok. |
changed required |
array of string | Names of the steps whose state is changed. |
next_steps required |
array of string | |
profiles required |
array of string | Chrome profiles detected, labelled by name and directory. These are real people's names; never commit real output. |
steps required |
array of object | |
steps[].detail required |
string | |
steps[].name required |
string | |
steps[].state required |
string | Values: |
POST
/cli/uninstall
Remove Blowhorn from this machine: the background service (the ai.blowhorn.service LaunchAgent, booted out and its file removed), the Chrome native host (the com.blowhorn.chrome_extension manifest, its launcher under ~/Library/Application Support/Blowhorn, and the every-profile install entries 'chrome extension-install --all-profiles' wrote), the ~/.local/bin/blowhorn launcher tools/install.sh wrote (a launcher without the installer's marker is kept), the marked PATH block in ~/.zshrc, ~/.bashrc and ~/.config/fish/config.fish (only the block comes out; the rest of each file stays), /Applications/Blowhorn.app (the bundle first unregisters its own Login Items entries - open at login and the background service - through its --unregister-login-items; a bundle too old to have it is reported not unregistered, naming System Settings > General > Login Items as the step left to you), and the checkout's venv/ (--venv-dir, else BLOWHORN_VENV_DIR, else <checkout>/venv, removed only when it holds pyvenv.cfg), node_modules/, desktop/node_modules/ and data/logs/. Every item is reported as removed, not present, not checked, kept, not unregistered or failed, in that order; a failed or not-unregistered item is reported and the run goes on, exiting 1 at the end. The sign-in on this machine (the Keychain item, the account record, and the guided setup progress) is removed on every uninstall. Local data is kept and named -- the store connection file an older version wrote (no longer read), the desktop app's state, Blowhorn's own Chrome trees and the checkout's data/cache/ (each profile's runtime state) -- unless --purge is given, which removes it after you type 'purge' at the prompt (or pass --yes, which --json needs); your Blowhorn account is never touched. Nothing under a Chrome profile is read or written: nobody is signed out of Chrome, Google Chrome stays installed, and the extension stays on the Chrome Web Store. Refused by name, in a dry run too, while a scheduler tick, a service pass, an extension run, Blowhorn.app, a desktop app started from this checkout or, with --purge, a Chrome on ~/.blowhorn is running, and while a write-back spool this run removes (under the checkout's data/logs/, or the app's state under --purge) holds run records that have not reached the shared store (run 'blowhorn store --replay-spool' first, or pass --discard-unsynced-runs when the store will never be reachable again); a spool in any other log directory is left in place and named with its count. --dry-run lists every item and removes nothing. ./tools/uninstall.sh at the checkout root runs the same code with the system Python once the venv is gone. --from-trash is the run the app makes on itself after you drag it to the Trash, before the Trash is emptied: purge scope is forced, the bundle is looked for in ~/.Trash as well as /Applications and removing it takes it out of the Trash, and the Login Items entry of a bundle that is already gone is reported instead of failing the run. macOS only; Linux and any other platform are refused by name.
Remove Blowhorn from this machine: the background service (the ai.blowhorn.service LaunchAgent, booted out and its file removed), the Chrome native host (the com.blowhorn.chrome_extension manifest, its launcher under ~/Library/Application Support/Blowhorn, and the every-profile install entries 'chrome extension-install --all-profiles' wrote), the ~/.local/bin/blowhorn launcher tools/install.sh wrote (a launcher without the installer's marker is kept), the marked PATH block in ~/.zshrc, ~/.bashrc and ~/.config/fish/config.fish (only the block comes out; the rest of each file stays), /Applications/Blowhorn.app (the bundle first unregisters its own Login Items entries - open at login and the background service - through its --unregister-login-items; a bundle too old to have it is reported not unregistered, naming System Settings > General > Login Items as the step left to you), and the checkout's venv/ (--venv-dir, else BLOWHORN_VENV_DIR, else <checkout>/venv, removed only when it holds pyvenv.cfg), node_modules/, desktop/node_modules/ and data/logs/. Every item is reported as removed, not present, not checked, kept, not unregistered or failed, in that order; a failed or not-unregistered item is reported and the run goes on, exiting 1 at the end. The sign-in on this machine (the Keychain item, the account record, and the guided setup progress) is removed on every uninstall. Local data is kept and named -- the store connection file an older version wrote (no longer read), the desktop app's state, Blowhorn's own Chrome trees and the checkout's data/cache/ (each profile's runtime state) -- unless --purge is given, which removes it after you type 'purge' at the prompt (or pass --yes, which --json needs); your Blowhorn account is never touched. Nothing under a Chrome profile is read or written: nobody is signed out of Chrome, Google Chrome stays installed, and the extension stays on the Chrome Web Store. Refused by name, in a dry run too, while a scheduler tick, a service pass, an extension run, Blowhorn.app, a desktop app started from this checkout or, with --purge, a Chrome on ~/.blowhorn is running, and while a write-back spool this run removes (under the checkout's data/logs/, or the app's state under --purge) holds run records that have not reached the shared store (run 'blowhorn store --replay-spool' first, or pass --discard-unsynced-runs when the store will never be reachable again); a spool in any other log directory is left in place and named with its count. --dry-run lists every item and removes nothing. ./tools/uninstall.sh at the checkout root runs the same code with the system Python once the venv is gone. --from-trash is the run the app makes on itself after you drag it to the Trash, before the Trash is emptied: purge scope is forced, the bundle is looked for in ~/.Trash as well as /Applications and removing it takes it out of the Trash, and the Login Items entry of a bundle that is already gone is reported instead of failing the run. macOS only; Linux and any other platform are refused by name.
Request body required
The flags and arguments of `blowhorn uninstall`, one property per parameter.
| Name | Type | Description |
|---|---|---|
bin_dir |
string | Where tools/install.sh wrote the launchers (its --bin-dir; default ~/.local/bin) |
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
discard_unsynced_runs |
boolean | Give up the run records waiting in a write-back spool this run removes (runs/pending-store.ndjson under the checkout's data/logs/, or the app's state under --purge) instead of refusing, when the shared store will never be reachable again; otherwise run 'blowhorn store --replay-spool' first. Asks you to type discard after naming the count of records it deletes (or pass --yes, which --json needs) Default: |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
from_trash |
boolean | The run the app makes on itself after you drag Blowhorn.app to the Trash, before the Trash is emptied: purge scope is forced (everything goes, local data included), the bundle is looked for in ~/.Trash as well as /Applications, removing it takes it out of the Trash, and the Login Items entry of a bundle that is already gone is reported instead of failing the run. Asks you to type purge first (or pass --yes, which --json needs) Default: |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as one JSON object: ok, dry_run, purge, from_trash, refusals, every item with its result, and the kept paths Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn uninstall' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn uninstall': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
purge |
boolean | Also remove local data a plain uninstall keeps: ~/.config/blowhorn (an older version's store.yaml, no longer read), the rest of ~/Library/Application Support/Blowhorn, ~/.blowhorn, the app's per-user Library state, the checkout's data/cache/ (each profile's runtime state) and the pause file. The sign-in (Keychain item and account record) is removed on every uninstall, with or without this flag. Asks you to type purge first; your Blowhorn account is never touched Default: |
risk_profile |
string | Accepted by 'blowhorn uninstall' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
venv_dir |
string | The virtualenv tools/install.sh created (its --venv-dir; default BLOWHORN_VENV_DIR, else <checkout>/venv). Removed only when it holds pyvenv.cfg; the filesystem root, your home directory, the checkout and a directory holding either are refused |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
yes |
boolean | Confirm --purge, --from-trash or --discard-unsynced-runs without the prompt, for a run with no terminal; refused without one of them Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The uninstall report - one object listing each artifact and what happened to it. The same shape is printed on a failed or refused run with ok false and error set; the early refusals (an option combination, an unsupported platform) print only ok false and error (see Problem).
| Name | Type | Description |
|---|---|---|
discard_unsynced_runs required |
boolean | |
dry_run required |
boolean | |
error required |
string | null | |
from_trash required |
boolean | True on the --from-trash run the trashed app makes on itself; purge is forced true there. |
items required |
array of object | |
items[].action required |
string | Values: |
items[].category required |
string | service, native host, chrome install, launcher, path block, desktop app, checkout, local data or shared store. |
items[].detail required |
string | null | |
items[].kind required |
string | launchd, file, tree, policy entry, login items, path block or remote. |
items[].label required |
string | |
items[].note required |
string | null | |
items[].path required |
string | null | |
items[].present required |
boolean | |
items[].result required |
string | removed, would remove, not present, not checked, kept, never touched, failed or not unregistered. |
kept required |
array of string | null | Paths of local data left in place. |
not_checked required |
array of string | Places that could not be read, each naming the file and what was therefore not looked for. |
ok required |
boolean | |
platform required |
string | |
purge required |
boolean | |
refusals required |
array of string | Sentences naming what is in flight; when non-empty nothing runs. |
store_touched required |
boolean | Always false; uninstall never changes the shared store. |
unsynced_runs_given_up required |
array of object | |
unsynced_runs_given_up[].count required |
integer | |
unsynced_runs_given_up[].log_dir required |
string | |
unsynced_runs_given_up[].path required |
string | |
unsynced_runs_kept required |
array of object | |
unsynced_runs_kept[].count required |
integer | |
unsynced_runs_kept[].log_dir required |
string | |
unsynced_runs_kept[].path required |
string |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
UninstallResponse
| Name | Type | Description |
|---|---|---|
discard_unsynced_runs required |
boolean | |
dry_run required |
boolean | |
error required |
string | null | |
from_trash required |
boolean | True on the --from-trash run the trashed app makes on itself; purge is forced true there. |
items required |
array of object | |
items[].action required |
string | Values: |
items[].category required |
string | service, native host, chrome install, launcher, path block, desktop app, checkout, local data or shared store. |
items[].detail required |
string | null | |
items[].kind required |
string | launchd, file, tree, policy entry, login items, path block or remote. |
items[].label required |
string | |
items[].note required |
string | null | |
items[].path required |
string | null | |
items[].present required |
boolean | |
items[].result required |
string | removed, would remove, not present, not checked, kept, never touched, failed or not unregistered. |
kept required |
array of string | null | Paths of local data left in place. |
not_checked required |
array of string | Places that could not be read, each naming the file and what was therefore not looked for. |
ok required |
boolean | |
platform required |
string | |
purge required |
boolean | |
refusals required |
array of string | Sentences naming what is in flight; when non-empty nothing runs. |
store_touched required |
boolean | Always false; uninstall never changes the shared store. |
unsynced_runs_given_up required |
array of object | |
unsynced_runs_given_up[].count required |
integer | |
unsynced_runs_given_up[].log_dir required |
string | |
unsynced_runs_given_up[].path required |
string | |
unsynced_runs_kept required |
array of object | |
unsynced_runs_kept[].count required |
integer | |
unsynced_runs_kept[].log_dir required |
string | |
unsynced_runs_kept[].path required |
string |
POST
/cli/upgrade
Carry this Mac's local data from an earlier home into this one. It carries profiles, analytics history, every run-ledger line (duplicates included), the write-back spool, the pause and the settings you changed. The earlier home is left exactly as it was, nothing already in the new home is overwritten, and a file that differs is kept and named in the report. Browser session folders are never carried. The pause is carried first, so a paused Mac is paused in the new home even if a later part fails. Each completed carry is recorded in upgrades.json in the new home, so it runs once; a carry that fails is not recorded and the next run tries again, adding nothing twice. While a scheduled pass is still running from the earlier home the answer is busy and nothing is touched. The earlier home is found from --source, then from the engine record the app wrote before this launch, then from the previous checkout at ~/code/blowhorn. A folder that belongs to another macOS user is never carried, even when named with --source; the report says so and carries nothing. --dry-run -- including one that comes from your settings -- only plans: the answer is planned, nothing is written, and the upgrade is still due. The desktop app runs this itself at launch; you never need a terminal for it.
Carry this Mac's local data from an earlier home into this one. It carries profiles, analytics history, every run-ledger line (duplicates included), the write-back spool, the pause and the settings you changed. The earlier home is left exactly as it was, nothing already in the new home is overwritten, and a file that differs is kept and named in the report. Browser session folders are never carried. The pause is carried first, so a paused Mac is paused in the new home even if a later part fails. Each completed carry is recorded in upgrades.json in the new home, so it runs once; a carry that fails is not recorded and the next run tries again, adding nothing twice. While a scheduled pass is still running from the earlier home the answer is busy and nothing is touched. The earlier home is found from --source, then from the engine record the app wrote before this launch, then from the previous checkout at ~/code/blowhorn. A folder that belongs to another macOS user is never carried, even when named with --source; the report says so and carries nothing. --dry-run -- including one that comes from your settings -- only plans: the answer is planned, nothing is written, and the upgrade is still due. The desktop app runs this itself at launch; you never need a terminal for it.
Request body required
The flags and arguments of `blowhorn upgrade`, one property per parameter.
| Name | Type | Description |
|---|---|---|
chrome_launch |
string | How this run reaches Google Chrome, for one run: 'extension' (the default) drives Chrome through the Blowhorn Chrome extension and starts no Playwright Chrome; 'persistent' launches Blowhorn's own Chrome from chrome.automation_data_dir with the mapped profile directory inside it; 'cdp' attaches to a running Chrome that is ready per 'blowhorn chrome status' (daily Chrome asks you to Allow once per connection; a dedicated attach-chrome started with 'blowhorn chrome attach-chrome start' does not); and 'auto' attaches when Chrome is ready and launches otherwise. This flag, then BLOWHORN_CHROME_LAUNCH, then BLOWHORN_BROWSER_DRIVER, then defaults.chrome.launch_mode in .blowhorn.yaml, selects the driver a run actually uses. Any other value is refused before anything runs. Every browser run needs the profile mapped with 'blowhorn chrome map' first, in any mode. A scheduler tick's jobs each resolve their own mode (this flag on the tick, BLOWHORN_CHROME_LAUNCH, the job's driver mode, BLOWHORN_BROWSER_DRIVER, defaults.chrome.launch_mode, then the extension) |
config |
string | Path to global config file (default: ./.blowhorn.yaml) |
dry_run |
boolean | Simulate execution without making changes Default: |
exclude |
string | Profiles to exclude when using --profile all (comma delimited); alias: --e; use 'none' or an empty string to clear configured defaults |
headed |
boolean | Keep the browser window visible and in front (overrides --headless) so a person can finish a sign-in or challenge Default: |
headless |
boolean | Run unattended in a background browser window: never headless (Chrome would announce the run as HeadlessChrome to every site) and no waiting for a person at a sign-in or challenge. When the run attaches to your running Chrome (--chrome-launch cdp, or auto while 'blowhorn chrome status' says ready) the tab opens in its own visible Chrome window instead: a hidden tab is throttled, so the previous app is only re-activated once Default: |
json_output |
boolean | Emit the report as one JSON object: ok, status (upgraded, planned, current, busy or failed), dry_run, target, source, a one-line summary, and one entry per step with its counts and any files kept Default: |
log_dir |
string | Directory where daily rotated stdout/stderr logs are persisted while still streaming to the console |
pace |
string | Accepted by 'blowhorn upgrade' because --pace is a global flag, but this command does no paced work -- it sleeps between nothing -- so the value changes nothing about how it runs and appears only in the Pace column of the -v summary. Resolved as CLI flag, then BLOWHORN_PACE, then defaults.pace in .blowhorn.yaml (normal when none is set); an unrecognized value is rejected before anything runs |
platform |
string | Accepted and ignored by 'blowhorn upgrade': this command never acts on a platform, so --platform has no effect here -- including a value inherited from BLOWHORN_PLATFORM or defaults.platform in .blowhorn.yaml |
profile |
string | Profile context (name, comma-delimited names, or 'all' alias for all profiles) |
risk_profile |
string | Accepted by 'blowhorn upgrade' because --risk-profile is a global flag, but this command does no gated work -- no Risk Profile hold, cap or pace applies to it -- so the value changes nothing about how it runs. A value after the command word wins over one before it; an unrecognized value is rejected before anything runs |
source |
string | The earlier version's home to carry data from; without it the engine record from before this launch is read, then the previous checkout at ~/code/blowhorn |
verbose |
boolean | Enable verbose output (includes COMMAND SUMMARY) Default: |
Responses
200
What the command prints to stdout with --json when it exits 0.
The upgrade report: whether this Mac's local data was carried from an earlier version's home into the home this version uses, and what each step carried. The same shape is printed on a busy or failed run with ok false and error set (a busy or failed status is read from status, here and in details on the desktop runner's failure shape).
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
error required |
string | null | |
ok required |
boolean | |
source required |
string | null | The earlier home data was carried from, or null when none was found. |
status required |
string | upgraded (steps applied and recorded), planned (a dry run: nothing written, the upgrade is still due), current (nothing to carry), busy (a pass is running from the earlier home; nothing was touched), failed (a step failed or the record would not parse; nothing was recorded for the failed step). Values: |
steps required |
array of object | |
steps[].counts |
object | What the step carried, by kind (pause, analytics_files, ledger_lines, spool_lines, settings). |
steps[].id required |
string | |
steps[].kept |
array of string | Target files that already held different bytes -- kept, never overwritten, and named. |
steps[].layout |
string | The earlier home's layout (root for analytics at its root, data for it under data/). Values: |
steps[].notes |
array of string | |
steps[].reason |
string | Why a not-run step did not run. |
steps[].source |
string | |
steps[].status required |
string | not-run when the earlier home was refused (it belongs to another macOS user); reason says why and nothing was carried. Values: |
summary required |
string | One line saying what happened, for a notification or a status row. |
target required |
string | The home data was carried into (the home whose .blowhorn.yaml the run resolved). |
default
What the command prints to stdout with --json when it exits non-zero: a Problem, or, for a command that reports per-item outcomes, its own shape with `ok: false`.
Problem
| Name | Type | Description |
|---|---|---|
code |
string | A stable machine-readable cause, where the command names one. |
details |
any | Structured context for the cause, where the command has any. |
error required |
string | The cause, as one sentence a person can act on. |
ok required |
any |
UpgradeResponse
| Name | Type | Description |
|---|---|---|
dry_run required |
boolean | |
error required |
string | null | |
ok required |
boolean | |
source required |
string | null | The earlier home data was carried from, or null when none was found. |
status required |
string | upgraded (steps applied and recorded), planned (a dry run: nothing written, the upgrade is still due), current (nothing to carry), busy (a pass is running from the earlier home; nothing was touched), failed (a step failed or the record would not parse; nothing was recorded for the failed step). Values: |
steps required |
array of object | |
steps[].counts |
object | What the step carried, by kind (pause, analytics_files, ledger_lines, spool_lines, settings). |
steps[].id required |
string | |
steps[].kept |
array of string | Target files that already held different bytes -- kept, never overwritten, and named. |
steps[].layout |
string | The earlier home's layout (root for analytics at its root, data for it under data/). Values: |
steps[].notes |
array of string | |
steps[].reason |
string | Why a not-run step did not run. |
steps[].source |
string | |
steps[].status required |
string | not-run when the earlier home was refused (it belongs to another macOS user); reason says why and nothing was carried. Values: |
summary required |
string | One line saying what happened, for a notification or a status row. |
target required |
string | The home data was carried into (the home whose .blowhorn.yaml the run resolved). |
No operation matches the filter.