CLI
salesos — sign in, call any endpoint, deploy and test metadata, listen to live events, work with functions, records, bulk jobs and developer orgs from the terminal.
The salesos command line is built on the TypeScript SDK, so
it follows the same retry, idempotency and pagination rules. It needs Node 18+.
The CLI is not on npm yet. Build it from the SalesOS repository: build
packages/sdk-js, then cd packages/cli && npm install && npm run build && npm link.
Sign in
Three ways, stored per profile:
For OAuth, register a public app under Settings → OAuth apps with the
redirect URI http://127.0.0.1:8976/callback and the scopes you will ask for.
SalesOS matches redirect URIs exactly, port included, so pass --port if you
registered a different one. The CLI opens your browser (--no-browser just
prints the URL), waits for the redirect on 127.0.0.1, checks state and the
issuer, and exchanges the code. Ask for offline_access to get a refresh
token; the CLI refreshes before expiry and saves every rotated refresh token.
Without --scope it asks for openid profile email offline_access plus
WRITE_ on every core resource, so the app must allow all of those.
Session sign-in handles two-factor authentication: when your organization asks
for a code, the CLI prompts for a TOTP or backup code (or takes --code /
SALESOS_2FA_CODE). If your account has not enrolled yet, enroll in the web app
first. A session renews itself (POST /v1/auth/refresh) whenever a command runs
in the last 20 minutes of its hour, and saves the rotated token, so it lasts as
long as you keep using it; after an hour without a command, log in again. Two
salesos processes sharing a profile pick up each other's rotations.
Profiles and config
--profile <name> (or -p, or SALESOS_PROFILE) picks a profile; the first
one you log in to becomes the default. Credentials live in
~/.config/salesos/config.json ($XDG_CONFIG_HOME is honoured,
SALESOS_CONFIG overrides the path). The file is written atomically with mode
600 in a 700 directory, and a looser file is tightened when it is read.
The CLI never prints keys or tokens.
For CI, skip the config: SALESOS_API_KEY (or --api-key KEY) and
SALESOS_TOKEN (a bearer token) take precedence over the profile.
--base-url / SALESOS_BASE_URL point at another deployment.
Records
Objects: leads, contacts, accounts (companies), opportunities
(deals), tasks, quotes, orders, activities, cases, and
custom:<objectId> for custom object records. --filter key=value passes any
filter the endpoint accepts (see Requests); --all
walks every page and --max N stops early. --data takes JSON, @file or
- for stdin.
Any endpoint
The status and request id go to stderr, the body to stdout (--out writes it
to a file). POST/PATCH get an automatic Idempotency-Key; pass
--idempotency-key to choose it.
Output
Lists print as tables and single records as key/value blocks. Add --json
to any command for machine-readable output:
Exit codes: 0 success, 1 the API or the job failed, 2 usage error.
--verbose logs retries.
Platform
webhooks create writes the signing secret, which SalesOS shows only once, to
webhook-<id>.secret (mode 600, or --secret-out <file>) instead of
printing it.
Verify a delivery locally
Exits 0 when the signature is valid and 1 when it isn't. The body is read
byte for byte, so save it exactly as received. --tolerance <seconds> changes
the 300-second window and --ignore-time skips it when you replay an old
capture.
Bulk data
Jobs run on the Bulk API. The CLI polls and prints progress
to stderr. An import with failed rows writes them to
bulk-<jobId>-failures.csv (or --failures-out) and exits 1. --no-wait
returns as soon as the job is queued.
Developer orgs
These need a session profile (login --email), because the API refuses keys and
OAuth tokens on these routes. open and leave switch the profile's session.
See Developer orgs for limits.
Metadata
push deploys to the active profile; deploy --target deploys the same source
to another profile, so -p devorg … deploy --target production promotes what you
built in a developer org. Both print progress and a result per component and exit
1 when the deploy fails. diff compares JSON by value (key order and spacing
don't count) and prints changed lines. See Metadata as code
for the format and deploy rules.
--types takes any type the server lists in GET /v1/metadata/types: the CLI
knows every folder (objects, fields, validation-rules, workflows,
actions, hooks, functions, layouts, pages, app-pages, flows,
approval-processes, labels, translations, profiles, reports,
dashboards, email-templates, list-views, assignment-rules,
credentials, events, after-save). A push or deploy runs the package's
tests first (--run-tests SPECIFIED, the default); a failing test fails it.
Test
test sends the project as a check-only deploy with tests: the server
validates the package against the org and runs every test file in it —
functions and flows with every outside call mocked, validation rules and
formulas exactly as a save evaluates them. Nothing is written. It prints one row per test (status, kind,
target/name, time, the reason a test failed) and exits 0 when everything
passed, 1 when a test failed or the package doesn't validate, 2 on a usage
error. --json prints { deployId, status, ok, runTests, total, passed, failed, durationMs, tests[], problems[] }. The test file format is in
Testing.
Listen to live events
listen streams the org's catalog events over
POST /v1/webhooks/listen (Server-Sent Events) and POSTs each one to
--forward-to exactly as a webhook would arrive: the same envelope body, byte for
byte, and the same X-SalesOS-Event, X-SalesOS-Event-Id and
X-SalesOS-Signature: t=…,v1=… headers. The signature uses a secret the CLI
generates for this session and prints once — verify with it locally the way you
will verify production deliveries. It never leaves your machine. Without
--forward-to, events are printed as JSON lines.
Listening is equivalent to owning a webhook for the whole org: org owner/admin,
and WRITE_WEBHOOKS for keys and tokens. A stream ends after 15 minutes and the
CLI reconnects (with refreshed credentials); events published during the second
it takes are not replayed, and a terminal that falls far behind is told how many
events it dropped. Up to 5 listen sessions per org. trigger sends only to
listen sessions — never to your real webhooks.
Functions
init writes src/functions/<apiName>/ in your project. deploy sends just that
folder through the metadata deploy, so the rules match (compiled on the server,
inactive unless --activate, missing secrets reported). logs --follow polls the
invocation log and prints each run with its captured stdout/stderr.
Python SDK
salesos — typed resources, retries, idempotency, pagination, OAuth helpers and webhook verification for Python 3.9+, sync and asyncio.
Metadata as code
Keep objects, fields, rules, workflows, actions, hooks, functions, pages, flows, profiles, reports and more in source control, test them, and deploy them from a developer org to production with the CLI or the metadata API.