Skip to navigation

Scripting and CI

This page collects the patterns that make unattended runs reliable: non-interactive authentication, a pinned version, error handling, and job polling. The CLI prints a JSON envelope, reports failures in a stable error envelope, and sends an idempotency key with every write.

Non-interactive authentication

Inject the token as a secret and pass it through the environment or stdin. Never write it to the workspace.

export DEEL_TOKEN="$DEEL_PAT" # from your CI secret store
export DEEL_ENV=demo # or prod, explicitly
deel auth status --fields valid >/dev/null

deel auth status is a preflight check: it fails with auth.missing or auth.invalid before the script does any work.

Pin the CLI version

Beta releases can change commands and output. Pin the version in automation and upgrade deliberately.

curl -fsSL https://cli.deel.com/v0.1.1/install.sh | sh
# or, with npm
npm install -g @deel-org/cli@0.1.1

Fail fast and read error codes

Use set -e semantics for the exit status and jq for the reason.

set -euo pipefail
if ! deel adjustments create --invoice --input file://bonus.json >result.json 2>error.json; then
jq -r '"deel failed: \(.error.code): \(.error.message) (request \(.error.request_id // "n/a"))"' error.json >&2
exit 1
fi
ADJUSTMENT_ID=$(jq -r '.data.adjustment_id' result.json)

Retryable conditions (429, 5xx) are already retried by the CLI up to three times. A script that sees http.429 after that should back off for longer rather than loop immediately.

Retry safety

Every mutating command carries a deterministic idempotency key derived from its content. Re-running a failed script step sends the same key and cannot create a duplicate record. If a step must create a second identical record on purpose, pass a new --idempotency-key. See Idempotency and retries.

Build bodies with jq

Generate request bodies from your own data instead of templating strings.

jq -n --arg contract "$CONTRACT_ID" --arg amount "$AMOUNT" --arg date "$(date +%F)" \
'{type:"BONUS", amount:($amount|tonumber), contract_id:$contract, description:"Monthly bonus", date_submitted:$date}' \
| deel adjustments create --invoice --input -

For bulk operations, map a CSV or database export into the array shape the operation expects and pass it with --input file://. Bulk adjustment operations accept up to 50 items per request; split larger sets into batches.

Follow asynchronous jobs

Bulk commands return a job_id. Poll until the job reaches a terminal status and inspect failed items. See Asynchronous jobs for a complete loop.

JOB_ID=$(deel adjustments create-bulk --invoice --input file://batch.json --quiet)
until deel job status "$JOB_ID" --jq '.status' --raw | grep -Eq 'SUCCEEDED|FAILED'; do sleep 5; done
deel jobs status --job_id="$JOB_ID" --jq '.items[] | select(.status == "FAILED")'

GitHub Actions example

This job writes real payroll data in production. Review the batch file before wiring this into a real pipeline, and treat DEEL_ENV: prod as a deliberate choice, not a default to copy unexamined.

jobs:
submit-adjustments:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g @deel-org/cli@0.1.1
- name: Submit payroll adjustments
env:
DEEL_TOKEN: ${{ secrets.DEEL_PAT }}
DEEL_ENV: prod
run: |
deel auth status --fields valid
deel adjustments create-bulk --payroll --input file://payroll/adjustments.json --fields job_id

Runners set CI=true, so the CLI treats the session as non-interactive and prints JSON regardless of flags.

Logging in CI

The local log is written to the runner’s home directory and discarded with the job. Disable it with DEEL_LOG=off if the runner’s filesystem is shared or persisted, or keep it and upload it as an artifact for auditing; tokens are masked and bodies are omitted by default.

Next steps