on this page

ribo CLIribo mast

ribo mast

ribo mast is Tissue Mast from your terminal: who's on call, the pages that are open, sending and closing pages, and the whole on-call setup as a mast.toml you can keep in git. It needs ribo 2026.10.0 or later; ribo upgrade gets it.

It uses the same login as every other ribo command. A session from ribo login can do everything here. An API token needs mast:read for the commands that only look, and mast:write for the ones that change something:

ribo token create on-call --scope mast:read --scope mast:write

When a token is short of a scope, the error says which one the command needed.

The account has to be connected to a phone first, once, in the Mast app. Until it is, ribo mast commands fail with a 404, because there's no Mast setup on the account to read.


Who's on call

ribo mast status
On call
  primary   ann@example.com  until 2026-10-27 14:00 UTC
  database  bo@example.com  until 2026-10-26 09:00 UTC

Open pages
  #41    open         API answering 502  (api-health)  2026-10-25 14:02 UTC (12 minutes ago)

ribo mast pages lists recent pages, newest first. --open keeps only the ones nobody has resolved, and --days goes back further than the default 7, up to 90.

Every command that lists or shows something takes --json and prints what the API returned, for scripts.


Sending, acknowledging and resolving

ribo mast send deploys "Deploy of api finished" --body "v2026.10.4, 41 seconds"
ribo mast send alerts "Disk 95% full on db1" --priority page --dedupe disk-db1
ribo mast send alerts "Disk back to 60%" --dedupe disk-db1 --resolve

The first argument is a channel's name, and ribo sends through your login. It can also be the channel's key (mk_…) or its URL, which sends straight to the channel the way curl would and needs no login at all. ribo never prints a key it was given.

Flag What it does
--body The text under the title
--priority quiet, normal, loud or page. The channel's ceiling still caps it
--dedupe (or --alias) Sends with the same value fold into one page while it's open
--resolve Closes the open page with that --dedupe value instead of opening one
--url A link to open from the page

To acknowledge or resolve a page by hand, give its number or its id:

ribo mast ack 41
ribo mast resolve '#41'

A number is looked up among the last 90 days of pages.


Channels, vitals, rotations and policies one at a time

Each of these has list, show, create, update and delete. list is what runs when you give no subcommand, so ribo mast channels lists the channels.

ribo mast channels create deploys --default-priority normal --quiet-start 22:00 --quiet-end 07:00 --quiet-tz America/Chicago
ribo mast channels update deploys --policy default
ribo mast channels show deploys
ribo mast channels delete old-alerts

show prints the channel's URL, since that's what you came for. list and --json on the list leave the keys out.

A vital is a channel that expects a heartbeat, so it takes the channel flags plus its own:

ribo mast vitals create nightly-backup --period-secs 86400 --grace-secs 5400 --vital-priority loud

Rotations take a handoff (daily, weekly, biweekly or custom), a day and time, a time zone, and the people in order, by email:

ribo mast rotations create primary --handoff weekly --handoff-day monday --handoff-time 09:00 \
  --tz America/Chicago --order ann@example.com,bo@example.com
ribo mast rotations update night --handoff custom --length-hours 12

An override puts someone on call in a rotation's place for a while. A date on its own covers that whole day; a time narrows it:

ribo mast overrides add primary bo@example.com 2026-11-02 2026-11-04
ribo mast overrides add primary cy@example.com 2026-11-05T18:00 2026-11-06T09:00 --covers ann@example.com
ribo mast overrides list primary
ribo mast overrides rm primary ov_…

A policy is a list of steps, each AFTER,TARGET[,PRIORITY]. AFTER is seconds, or a number with m, h or d. TARGET is everyone, oncall:<rotation>, next:<rotation>, user:<email> or channel:<name>:

ribo mast policies create default \
  --step 0,oncall:primary \
  --step 10m,next:primary,loud \
  --step 30m,everyone \
  --repeat-every-secs 1800 --repeat-count 2

next:primary pages whoever takes over at the next handoff. An oncall or next step with no rotation pages everyone.

Giving any --step to update replaces all of them.

update changes only the flags you pass, and --rename gives the thing a new name. A renamed channel keeps its URL. delete asks first; --yes skips the question, and a session with no terminal refuses without it.

A channel's ceiling, max_priority, is set to loud at most from ribo. Raising a channel to page is something only the owner's phone can do, in the app.


The whole setup as a file

ribo mast export prints everything as a mast.toml:

ribo mast export > mast.toml
[[rotation]]
name = "primary"
handoff = "weekly"
handoff_day = "monday"
handoff_time = "09:00"
tz = "America/Chicago"
order = ["ann@example.com", "bo@example.com"]

[[policy]]
name = "default"
repeat_every_secs = 1800
repeat_count = 2

[[policy.step]]
after_secs = 0
target = "user"
user = "ann@example.com"
priority = "page"

[[policy.step]]
after_secs = 600
target = "everyone"
priority = "loud"

[[channel]]
name = "alerts"
policy = "default"
default_priority = "loud"
quiet_start = "22:00"
quiet_end = "07:00"
quiet_tz = "America/Chicago"

[[vital]]
name = "nightly-backup"
period_secs = 86400
grace_secs = 5400

The field names are the API's. People are emails, and policies and rotations are referred to by name. Fields left at Mast's defaults are left out of the export, and a field you leave out of the file is left alone on every apply, so the phone's own changes to it stick.

Channel keys are never in the file. The file can go in git. A channel created from it gets a new key, and an existing one keeps the key it has; ribo mast channels show <name> prints the URL when a sender needs it.

ribo mast apply makes Mast match the file. It reads ./mast.toml unless you give it a path, checks the whole file, prints a plan and asks before writing anything:

ribo mast apply
  unchanged  rotation "primary"
  update     policy "default"
               repeat_count: 2 → 3
  create     channel "deploys"
  unchanged  channel "alerts"
  kept       vital "old-cron"  (not in the file)

1 create, 1 update, 2 unchanged, 1 kept
Apply these changes? [y/N]

Things are matched by name. Anything in Mast that the file doesn't mention is left alone and listed as kept; --prune deletes those instead, and the plan says so before it asks. --dry-run prints the plan and stops, and --yes applies without asking, for CI.

The check runs before the first write. A key ribo doesn't know is refused with its line number, and so is a policy or rotation the file refers to that's in neither the file nor the account, an email nobody on the account has, or a handoff that isn't one of the four. Writes go in the order rotations, policies, channels, vitals, so a channel can name a policy the same file creates. A policy step can only page a channel that's already in Mast, so a new channel and a step that pages it take two applies.

Run ribo mast export straight after an apply and you get the same file back, and applying an unedited export changes nothing.


Importing from Opsgenie

ribo mast import opsgenie reads your Opsgenie account and plans the import into Mast. It's the same import as the one in the web app, it needs the account's owner or an admin, and it never writes anything without --apply. See Moving from Opsgenie for what comes across and what doesn't.

export OPSGENIE_API_KEY=…            # or leave it unset and ribo asks for it
ribo mast import opsgenie             # print the plan
ribo mast import opsgenie --apply     # write it

The key is read from OPSGENIE_API_KEY or typed at a prompt that doesn't echo. There's no flag for it, so it stays out of your shell history. ribo reads Opsgenie from your machine, sends Mast what it read, and forgets the key; it's never printed or put in an error. Use --region eu if your Opsgenie account is on the EU instance.

After --apply, ribo lists every channel the import made with its Mast URL and the Opsgenie-shaped address (https://mast.tissue.dev/opsgenie) and key to give the tools that used to send to Opsgenie. --json prints the server's plan or result as it came.

From ribo, --apply imports everything the plan lists and sends no invites. To leave items out, or to invite the Opsgenie people who aren't on the account yet, use the importer in the web app; running either one again later is safe, since it matches by name.