A command-line client for selected endpoints from the Deel Public API.
deel auth status --json # verify your token and identity
deel adjustments create --invoice --generate-input # explore required fields
deel jobs list --jq '.[] | {job_id, status}' # filter recent async jobs- Requirements
- Install
- Verifying downloads
- Quickstart
- Authentication
- Discovering commands
- Request input (
--input,--file,--form) - Output (
--json,--fields,--jq,--quiet) - Idempotency
- Environments
- Corporate proxies & custom CAs
- Exit codes
- Logging
- Configuration reference
- Building single-file binaries
- Project layout
- Local development
- Node 22.18+ to run from source (executes the TypeScript directly via type-stripping — no build step for local use).
- Optional: Bun is bundled as a devDependency and used to compile the single-file binaries; you don't need it on your PATH.
npm (Node 22.18+ / MCP agents):
npm install -g @deel-org/cli # installs the binary for your platform
npx @deel-org/cli --help # or run without installingDirect binary download (no Node required — Linux / macOS / Alpine):
curl -fsSL https://cli.deel.com/install.sh | shThe installer always verifies the SHA256 checksum and, when cosign is installed, also verifies the release signature against the release's public key (baked into the installer at release time). Set
DEEL_REQUIRE_COSIGN=1to abort if cosign is not available.
From source:
npm install # citty + jq-wasm (runtime), typescript + bun (dev)
node src/main.ts --help # or: npm run deel -- --helpOnce built (see Building single-file binaries) the binary is invoked as deel.
Every GitHub Release ships SHA256SUMS, SHA256SUMS.sigstore, and cosign.pub. Use these to confirm the binary was produced by the official release pipeline and has not been tampered with.
1. Install cosign (one-time):
# macOS
brew install cosign
# Linux
curl -sLO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign2. Download the release artifacts:
VERSION=v1.0.0 # replace with the version you downloaded
BASE=https://github.com/letsdeel/deel-cli/releases/download/$VERSION
curl -sLO $BASE/SHA256SUMS
curl -sLO $BASE/SHA256SUMS.sigstore
curl -sLO $BASE/cosign.pub3. Verify the signature:
cosign verify-blob \
--key cosign.pub \
--bundle SHA256SUMS.sigstore \
--insecure-ignore-tlog \
SHA256SUMSExpected output: Verified OK
--insecure-ignore-tlogis required because the release pipeline intentionally skips uploading to the Sigstore public transparency log. The integrity guarantee comes from thecosign.pubkey, not the log.
4. Verify the binary checksum:
# download the binary for your platform first, e.g.:
# curl -sLO $BASE/deel-linux-x64
sha256sum --check SHA256SUMS --ignore-missingExpected output: deel-<platform>: OK
Set a Personal Access Token and validate it:
export DEEL_TOKEN="<your-pat>"
export DEEL_ENV=demo # use staging while exploring
deel auth status # verify the token
deel adjustments create --invoice --generate-input # print the required fieldsUse --env demo for staging or --env prod (default) for production.
Mutating commands hit the real API. Prefer staging while exploring — every
createhas real effects.
A Personal Access Token (PAT) is required for every command that hits the API. It is never logged (the local log redacts Authorization to Bearer ***).
Resolution precedence (first match wins):
| Source | How |
|---|---|
--token-stdin |
echo "$PAT" | deel <cmd> --token-stdin |
DEEL_TOKEN |
the token value directly (handy for CI) |
| OS keychain | stored by deel auth login, used automatically otherwise |
--token-stdin and DEEL_TOKEN are pass-through and never written to disk. For a long-lived token, prefer the OS keychain over exporting DEEL_TOKEN.
deel auth login # prompts for the PAT (hidden input), validates, stores in the OS keychain
echo "$PAT" | deel auth login # non-interactive (CI): read the PAT from stdin
deel auth status # validates; reports the source (stdin / env / keychain)
deel auth logout # removes the stored tokenKeychain backends: macOS Keychain (security) and Linux libsecret (secret-tool); tokens are stored per environment. Where no keychain is available, use --token-stdin or DEEL_TOKEN.
Help is available at three levels, plus a machine-readable form for agents:
deel --help # all commands + global options
deel adjustments --help # subcommands in a group
deel adjustments create --help # params + request/response contract
deel adjustments create --help --json # machine-readable contract
deel adjustments create --invoice --generate-input # JSON body skeleton to fill in--help for an action shows what generic --help cannot: the request-body fields (which arrive via --input, not as flags) and the response fields.
Body content is supplied with --input, which accepts four interchangeable forms:
# key=value shorthand (scalar coercion + enum validation against the spec)
deel adjustments create --invoice \
--input type=BONUS,amount=500,contract_id=C123,description="Q1 bonus",date_submitted=2025-01-01
# inline JSON
deel adjustments create --invoice \
--input '{"type":"BONUS","amount":500,"contract_id":"C123","description":"Q1 bonus","date_submitted":"2025-01-01"}'
# a file
deel adjustments create --invoice --input file://payload.json
# stdin
echo '{"type":"BONUS","amount":500,...}' | deel adjustments create --invoice --input -The shorthand grammar supports nested objects, lists, and space-separated scalar lists, with backslash escaping:
--input 'tags=approved urgent,meta={source=api,ref=123}'Scalar values are coerced to the type declared in the spec (numbers, booleans, enums) and enum values are validated before the request is sent.
Array query params are repeatable — pass the flag once per value:
deel <command> --some_ids id1 --some_ids id2 # -> ?some_ids=id1&some_ids=id2File uploads. Endpoints that accept multipart/form-data take --file:
deel <command> --input field=value --file attachment=@/path/to/file.pdf--file is repeatable across fields and within a single field (multiple files under the same part). Some endpoints accept both JSON and multipart— those default to JSON; add --form to switch.
Output is the JSON envelope by default. Use --no-json on a TTY to get a human-readable view instead. The envelope is:
{ "data": { ... }, "meta": { "request_id": "..." } }| Flag | Effect |
|---|---|
--no-json |
Human-readable view on a TTY (default is always JSON) |
--fields a,b.c |
Project only these fields (dotted paths; maps over arrays) |
--jq '<filter>' |
Transform the data payload with an embedded jq (no external binary) |
--raw |
With --jq, print scalar string results unquoted (like jq -r) |
--quiet |
Print ids only (id / job_id / oid) |
deel jobs list --fields job_id,status
deel jobs list --jq '.[] | select(.status == "SUCCEEDED") | .job_id' --raw
deel jobs list --quietEvery mutating request (POST/PUT/PATCH/DELETE) carries an Idempotency-Key. By default it is a deterministic UUIDv5 derived from a canonicalized form of the request (method + path + query + body), so re-running the identical command sends the identical key and the server can dedupe a retry. Override it with --idempotency-key <value> when you want a fresh attempt.
--env |
Base URL |
|---|---|
prod (default) |
https://api.letsdeel.com/rest |
demo |
https://api-staging.letsdeel.com/rest |
Every API call — including deel job status/deel job list and the generated
deel jobs status/deel jobs list — goes through this same /rest-prefixed
base URL. There is no separate, unprefixed origin for any endpoint.
The CLI trusts your OS certificate store by default, so a corporate TLS-inspection proxy or an internal CA works with no configuration as long as its root is installed on the machine. At startup it merges bundled Mozilla + NODE_EXTRA_CA_CERTS + OS system store via tls.setDefaultCACertificates() (Node 22.15+ and Bun), so the packaged binary behaves the same as the dev path.
If a root isn't in the OS store, add it explicitly (no verification-disabling):
deel auth status --ca-cert /path/to/root.pem # extra CA (repeatable)
export NODE_EXTRA_CA_CERTS=/path/to/root.pem # or via envControl the trust sources with DEEL_CERT_STORE (default bundled,system; set to bundled to skip the OS store). Proxies are honored via HTTPS_PROXY / HTTP_PROXY / NO_PROXY. Verification is never disabled by default — this tool carries tokens and PII.
The exit status is deliberately binary:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | error |
All failure detail lives in the error envelope printed to stderr — never encoded in the exit code:
{ "error": { "code": "auth.missing", "message": "No token found", "next": "Set DEEL_TOKEN or pass --token-stdin", "request_id": "..." } }code distinguishes failures programmatically (e.g. auth.missing, input.enum, network.timeout, http.<status>, or the API's own error code); scripts should branch on code, not on the exit status.
Each HTTP exchange is written as NDJSON to a size-capped, rotating local log (separate from any network telemetry). By default only metadata is logged — command, method, URL path (query string omitted), status, request id, duration, and request headers (the Authorization header masked to Bearer ***). Response bodies are not written by default.
Opt in to full response bodies with --log-bodies per command, or DEEL_LOG_BODIES=1 globally. When bodies are logged, keys whose names look sensitive (token, password, email, iban, tax_id, and similar) are masked — but a response body may still contain personal data, so enable this only when you need it. Disable logging entirely per-command with --no-log, or globally with DEEL_LOG=off.
Location: ~/Library/Logs/deel/deel.log (macOS),
%LOCALAPPDATA%\deel\logs (Windows), $XDG_STATE_HOME/deel/logs (Linux).
| Variable | Purpose |
|---|---|
DEEL_TOKEN |
PAT value |
DEEL_ENV |
Default environment (prod / demo) |
DEEL_LOG |
off to disable local logging |
DEEL_LOG_BODIES |
1 to include response bodies in the local log (off by default) |
DEEL_LOG_DIR |
Override the log directory |
DEEL_LOG_MAX_SIZE |
Rotate threshold in bytes (default 10 MiB) |
DEEL_LOG_MAX_FILES |
Rotated files to keep (default 5) |
DEEL_CERT_STORE |
Trust sources, comma-separated (default bundled,system) |
DEEL_CA_CERT |
Extra CA PEM path (same as --ca-cert) |
NODE_EXTRA_CA_CERTS |
Extra CA PEM path honored by the runtime |
HTTPS_PROXY / HTTP_PROXY / NO_PROXY |
Proxy configuration |
npm run build # -> dist/deel (standalone, --minify --bytecode)
./dist/deel --version
npm run build:all # -> dist/deel-{darwin,linux}-{arm64,x64}, deel-windows-x64.exe + SHA256SUMSbun build --compile bundles all sources and dependencies into one self-contained executable. Cross-compilation to every target runs from a single Linux runner (scripts/build-all.sh).
.github/
workflows/pr.yaml # PR gate: validate-pr, check, build-smoke (public-safe, SHA-pinned actions)
pull_request_template.md, dependabot.yml
.husky/ # git hooks: pre-commit (dev guard, doctor gate, lockfile/version), pre-push (npm run check)
packaging/
install.sh # POSIX-sh installer shipped on every GitHub Release
scripts/
build-all.sh # cross-compile all binary targets + checksums
pack-npm.ts # assemble the npm launcher + platform packages from dist/
check-manifest.ts # assert manifest ↔ command map consistency
check-lockfile.ts # assert package-lock.json carries every bun platform binary
check-version.ts # assert package.json ↔ src/version.ts, clean semver
check-shell.sh # sh -n / bash -n + shellcheck for installer, build scripts, hooks
check-pr.ts # PR requirements (used by CI and by the /ship skill)
check-doctor.ts # pre-commit doctor gate; doctor-hashes.json is its ledger
src/
main.ts # entry point + top-level error handling
cli.ts # command-tree wiring + routing
help.ts # help / usage / contract / skeleton rendering
version.ts # CLI_VERSION (kept in sync with package.json by check-version)
types.ts # shared descriptor/manifest types
manifest.generated.ts # command descriptors (generated by the deel-cli-extractor skill — never hand-edited)
commands.map.ts # command ↔ API endpoint mapping
commands/ # static commands (auth, job)
core/ # http, auth, keychain, config, tls, input, output, logging, …
test/ # automated tests (node --test), one file per module
npm ci # install dependencies and the git hooks
npm test # run the automated test suite (node --test)
npm run typecheck # type-check with tsc (no emit)
npm run check # everything CI runs: typecheck, tests + coverage gate, manifest / lockfile / version / shell checks
npm run doctor # npm run check + build smoke, then clears the pre-commit gate for your changed filesThe git hooks keep dev (the release line) clean: pre-commit blocks files that haven't passed
npm run doctor since they last changed, and pre-push runs npm run check. See
CONTRIBUTING.md for the full workflow, the PR requirements and what CI checks.