Skip to navigation

Agent workflows

This page describes how an agent should use the CLI: discover before calling, build bodies from skeletons, read results as data, classify errors, retry safely, follow jobs, and stay within guardrails. The instruction block encodes these patterns in a form agents follow.

Discover before calling

The recommended order is: list, read the contract, generate the body, call, read the result.

deel --help # 1. what exists
deel adjustments create --help # 2. which variants
deel adjustments create --invoice --help --json # 3. the exact contract
deel adjustments create --invoice --generate-input # 4. a body to fill in
deel adjustments create --invoice --input file://body.json --fields adjustment_id,status # 5. call and read

Steps 1 to 4 run offline and do not need a token or network access. The contract from step 3 tells the agent how to build the body:

body.kindThe agent should
nonePass parameters as flags only
jsonFill the skeleton object and pass it with --input
json-arrayBuild a JSON array, one item per record, and pass it as a file or inline JSON
multipartPass text fields with --input and files with --file field=@path

Build request bodies from skeletons

--generate-input prints every field with a typed placeholder. An agent should write the skeleton to a file, replace the values it knows, remove optional fields it does not need, and pass the file back.

deel adjustments create --invoice --generate-input > body.json
# ... the agent edits body.json ...
deel adjustments create --invoice --input file://body.json

Values are validated before the request is sent. A wrong enumeration value or a non-numeric amount fails locally with input.enum or input.type, without a round trip to the API.

Read results as data

Ask for only what the next step needs.

deel adjustments create --invoice --input file://body.json --jq '.adjustment_id' --raw # one bare value
deel jobs list --fields job_id,status # a projected envelope
deel jobs status --job_id="$JOB_ID" --jq '.items[] | select(.status == "FAILED")' # only the failures

--quiet prints identifiers only, one per line, which is the form to pipe into the next command.

Classify errors

On failure the exit status is 1 and stderr carries a JSON envelope. The code prefix tells the agent what kind of problem it is and whether it can fix it alone.

error.codeMeaningAgent action
input.*, usage.*The command line or body is wrongFix locally using --help --json, then retry
auth.*No token, invalid token, or no keychainStop and report; a person must provide or fix the token
http.400, API validation codesThe API rejected the body; message lists the fieldsFix the listed fields, then retry
http.401, http.403The token lacks accessStop and report; a scope or policy decision is needed
http.404The referenced record does not existRe-check identifiers; do not retry blindly
http.429, http.5xxRate limited or upstream failure after the CLI’s own retriesWait, then retry the same command
network.*Transport, TLS, or timeoutReport the environment problem
{ "error": { "code": "input.enum", "message": "--type must be one of: BONUS, COMMISSION, DEDUCTION, EXPENSE, OTHER, OVERTIME, TIME_OFF, VAT" } }

Every API error includes request_id. An agent should quote it when it reports a failure so that support can locate the request. See Errors and exit codes for the full list.

Retry safely

Every mutating command carries an idempotency key derived from its content. Re-running the identical command after a timeout or an ambiguous failure sends the same key, and the API returns the original result instead of creating a second record. An agent does not need to check whether a write completed successfully before retrying it.

The CLI already retries 429 and 5xx responses up to three times. An agent that still sees http.429 should wait longer before its own retry rather than loop immediately. See Idempotency and retries.

Bulk operations and jobs

Bulk commands return 202 with a job_id. The agent should capture the id, poll until a terminal status, and then inspect failed items.

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")'

Batches are limited to 50 items per request; the CLI rejects longer arrays with usage.max_items before sending. An agent should split larger sets and use external_id on each item to correlate results with its own records. See Asynchronous jobs.

Guardrails

  • Least privilege. Give the agent a token with only the scopes its tasks need. A 403 is a signal to ask a person, not to try another route.
  • Demo by default. Keep DEEL_ENV=demo in the agent’s environment. Production runs are an explicit change made by a person.
  • Review before bulk writes. Have the agent show the batch file before running create-bulk.
  • No token handling. The agent runs deel commands; it never reads, prints, or moves the token. deel auth status masks it to its last four characters.
  • Keep logging on. The local log is the record of what the agent did.

Auditing an agent session

The CLI writes one newline-delimited JSON (NDJSON) line per request to its local log: timestamp, command, method, URL path, status, duration, request id, and idempotency key, with the token masked and the body omitted. To review what an agent did:

# macOS
jq -r '[.ts, .command, .method, .status, .request_id] | @tsv' ~/Library/Logs/deel/deel.log
# Linux
jq -r '[.ts, .command, .method, .status, .request_id] | @tsv' ~/.local/state/deel/logs/deel.log

Set DEEL_LOG_DIR in hosted environments to a directory you collect, or upload the log as a CI artifact. Response bodies are never logged unless --log-bodies is passed, so the log can be kept without special handling.

Next steps