Sayr
CLI

Configuration reference

Every Sayr CLI setting in one place, including the config file, profiles, environment variables, precedence, defaults, shared flags, and error codes

This page is the complete reference for how the CLI is configured. For a guided introduction, start with Authentication & API keys and Organizations, labels & categories.

Config file

The CLI stores its settings in a JSON file in your home directory:

~/.sayr/config.json

The file is created the first time something is saved (by login, config set-org, or config set-base-url), with owner-only permissions (0600) because it holds your token. A missing file is treated as empty. It looks like this:

{
	"token": "api_xxxxxxxx",
	"baseUrl": "https://api.sayr.io",
	"defaultOrg": "platform"
}
FieldSet byWhat it's for
tokensayr loginThe personal access token, sent with every request
baseUrlsayr login (always), sayr config set-base-urlThe API server the CLI talks to
defaultOrgsayr config set-orgThe organization to use when a command isn't given --org

sayr logout rewrites the file as empty, which removes all three fields. Every field is optional.

Profiles

One config file holds one login. To keep more than one, for example production and a local dev backend, use a profile. A profile is a separate config file, ~/.sayr/config.<profile>.json, chosen with the SAYR_PROFILE environment variable:

SAYR_PROFILE=staging sayr login --token api_xxxxxxxx --base-url https://api.staging.example.com
SAYR_PROFILE=staging sayr task list --org platform

Profile names may contain only letters, digits, ., _, and -. Anything else is an error. An unset or empty SAYR_PROFILE means the default ~/.sayr/config.json.

sayr-local

The package ships a second command, sayr-local. It's the same CLI with SAYR_PROFILE=local forced on, so it reads and writes ~/.sayr/config.local.json. That's the easy way to keep a local sayr backend beside your normal production login:

sayr-local login --token api_xxxxxxxx --base-url http://localhost:5468
sayr-local task list --org platform

sayr and sayr-local never interact. sayr-local sets the profile even if SAYR_PROFILE is already set in your shell.

Environment variables

These are all the environment variables the CLI reads.

VariableEffect
SAYR_TOKENThe token to use for this command, instead of the one in the config file. Handy in CI. login ignores it, and config get doesn't reflect it
SAYR_BASE_URLThe API base URL for this command, instead of the stored one. login ignores it
SAYR_PROFILESelects the config profile (see above). sayr-local sets it to local
SAYR_DEBUGAny non-empty value (even 0) makes error output also print the error code and HTTP status, for example (NETWORK_ERROR, status 0)
SAYR_NO_INTERACTIVESet to 1 to turn guided mode off, so the CLI never asks for missing input. See below for the exact values

SAYR_NO_INTERACTIVE leaves guided mode on when it's unset, empty, or set to 0, false, no, or off (in any case, ignoring surrounding spaces). Any other value turns it off, and 1 is the one to use. It doesn't affect login's token prompt or the confirmation that releases publish and releases delete ask for. Those follow --token, --yes, --json, and whether there's a terminal.

There is no environment variable for the organization or the output format.

Precedence

When the same setting can come from more than one place, the first match wins:

Setting1st2nd3rd
TokenSAYR_TOKENtoken in the config fileNone: the command fails with NOT_AUTHENTICATED
Base URLSAYR_BASE_URLbaseUrl in the config fileThe default, https://api.sayr.io
OrganizationThe --org flagdefaultOrg in the config fileAt a terminal, guided mode uses your only organization or asks which one. Otherwise the command fails with MISSING_ORG
Config fileSAYR_PROFILE (config.<profile>.json)~/.sayr/config.json

The organization step applies to every command that takes --org, including read-only ones such as task list. When the organization comes from the picker, the CLI suggests sayr config set-org so you aren't asked again.

login is the one exception: it uses only its own --token and --base-url flags (or a prompt for the token, and the default URL), and never the environment or a previously stored token or URL. It does keep the stored defaultOrg.

Defaults

SettingDefault
API base URLhttps://api.sayr.io
API path/api/public/v1/me, appended to the base URL
OrganizationNone. Pass --org or set a default. At a terminal the CLI asks instead of failing
Guided modeOn at a terminal. Off with --json, without a terminal, or when SAYR_NO_INTERACTIVE is on
task listOpen tasks only, --sort mostPopular, page 1, 30 per page (the maximum)
comment list10 per page (maximum 30). comment replies is 20 (maximum 50)
releases comment list10 per page (maximum 50)
labels create --visibilitypublic
comment create --visibilitypublic. The same for releases comment create and releases status-update create
releases create --statusplanned
releases status-update create --healthon_track

The CLI always calls <base URL>/api/public/v1/me/... with an Authorization: Bearer <token> header, and it trims trailing slashes from the base URL. Set the base URL to the API server's address only, such as http://localhost:5468, without a path.

The config commands

sayr config get
sayr config set-org platform
sayr config set-base-url http://localhost:5468
CommandWhat it does
sayr config getShows the config file path, the base URL, the default organization, and the token
sayr config set-org <org>Saves the default organization (a slug or an id)
sayr config set-base-url <url>Saves the API base URL

config get prints the path of the config file it read, the base URL a real request would use (so a SAYR_BASE_URL override shows up), the stored default organization or (none), and the stored token cut down to its first eight characters, or (not set):

Config file: /Users/you/.sayr/config.json
baseUrl:    https://api.sayr.io
defaultOrg: platform
token:      api_xxxx…

With --json it prints { "baseUrl", "defaultOrg", "hasToken" }. It never includes any part of the token. config get reports what's stored in the file, so it doesn't show a SAYR_TOKEN from the environment. set-org and set-base-url save exactly what you type and don't check it against the API.

Shared flags by command

--help works on every command, and -V / --version on the top-level sayr command.

Command--org--json--yes--page / --limitOther flags
login--token, --base-url
logout
whoamiyes
config getyes
config set-org, config set-base-url
orgs listyes
categories listyesyes
labels listyesyes
labels createyesyes--color, --visibility
task createyesyes--description, --status, --priority, --category <category>, --release
task listyesyesyes-q / --query, --category <category>, --release, --include-closed, --sort
task viewyesyes
task updateyesyes--title, --description, --status, --priority, --category <category>, --release, --no-release, --visible
task label, task assignyesyes--set <ids> (label names or ids on task label, user ids on task assign)
comment listyesyesyes
comment repliesyesyes
comment createyesyes--visibility
comment updateyes--visibility
comment deleteyes
releases listyesyes--status
releases viewyesyes
releases createyesyes--slug, --description, --status, --target-date, --color, --icon
releases updateyesyes--name, --slug, --description, --status, --target-date, --released-at, --color, --icon, --lead
releases publish, releases deleteyesyesyes
releases labelyesyes--add <labels>, --remove <labels> (label names or ids)
releases status-update list, deleteyesyes
releases status-update create, updateyesyes--health, --visibility
releases comment listyesyesyes--status-update
releases comment replies, deleteyes
releases comment createyesyes--reply-to, --status-update, --visibility
releases comment updateyes--visibility
releases pr list, link, unlinkyesyes

The commands with no --org either don't work inside an organization (login, whoami, config, orgs list) or address a comment by its own id, which already belongs to one organization. --json also turns guided mode off for that command.

task create [title] and releases create [name] take their title or name as an optional argument, which lets a terminal ask for it. Their own --help shows <title> and <name> because scripts must pass one, and without a terminal (or with --json) a missing one fails with error: missing required argument. --category takes a category name or id, and --set on task label and --add / --remove on releases label take label names or ids. See Names instead of ids.

Error codes

Errors print as ✗ <message> on standard error, and the command exits with code 1. Add SAYR_DEBUG=1 to also print the code and HTTP status in parentheses.

Codes the CLI raises

CodeStatusWhat it means
NOT_AUTHENTICATED401No token was found in SAYR_TOKEN or the config file. Run sayr login --token <api-key>, or set SAYR_TOKEN. Check you're using the profile you meant to
MISSING_ORG400The command needs an organization and none was given. Pass --org <slug>, or run sayr config set-org <slug>. At a terminal, guided mode uses your only organization or asks which one, so you only see this error there if you belong to no organization
INVALID_ARGUMENT400A flag value isn't one of the allowed values, a date can't be read, a pull request URL isn't a GitHub pull request URL, or a category or label name matches nothing or several items. The message lists what's accepted, or asks for the id. It's caught before any write request is made
CONFIRMATION_REQUIRED400releases publish or releases delete needs a yes, but can't ask (--json, or no terminal). Re-run with --yes
NETWORK_ERROR0The request never got an answer: the base URL is wrong, the server is down, you're offline, or something blocked it. The message includes the URL. Check it with sayr config get
INVALID_RESPONSEThe response's statusSomething answered, but not with the Sayr API (the message says "non-JSON response"). Usually the base URL points at a web page or a proxy error page
MISSING_PAGINATION500A list endpoint replied without paging information, which the CLI expects. It's unexpected: update the CLI, and report it if it keeps happening
NOT_FOUND404releases pr unlink found no linked pull request matching the URL. sayr releases pr list <release> shows what is linked
REQUEST_FAILEDThe response's statusThe API returned an error that didn't name itself. The CLI prints the API's message, or "Request failed"

A few errors have no code and print just the message, such as an invalid SAYR_PROFILE name or an invalid --visibility on labels create. Usage errors from the argument parser, such as a missing argument or an unknown command or option, print as error: ... instead.

Errors from the API

When Sayr's API refuses a request, the CLI prints the message the API sent. With SAYR_DEBUG set, the code in parentheses is the API's own short error name. The common ones:

HTTP statusAPI errorWhat it means
401Invalid API keyThe key is invalid, disabled, or expired. Create a new key, or regenerate it, under Settings > API keys
403You don't have permission to ...The key is missing a scope, or your role in the organization is missing the permission. For release commands the message says which. See Authentication & API keys
404Organization not foundThe --org value isn't an organization's slug or id. Use sayr orgs list to check. The short ID (like SAY) isn't accepted
404Task not found, Release not found, and similarThe task, release, or comment doesn't exist in that organization. Tasks are referred to by number (123), not SAY-123
429Rate limit exceededThe key has hit its rate limit. Wait and try again

On this page