> This page is for CLI.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.deel.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.deel.com/_mcp/server.

# Scripting and CI

> Patterns for running the Deel CLI in shell scripts and CI pipelines

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.

```bash
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.

```bash
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.

```bash
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](/cli/usage/idempotency-and-retries).

## Build bodies with jq

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

```bash
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](/cli/usage/async-jobs) for a complete loop.

```bash
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

> **Warning**
>
> 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.

```yaml
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

#### [Output and filtering](/cli/usage/output)

Shape command output for the next step in a pipeline

#### [Errors and exit codes](/cli/usage/errors)

The complete list of error codes