> ## Documentation Index
> Fetch the complete documentation index at: https://staplehire.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI errors

> Understand Staplehire CLI exit codes and structured error JSON — authentication, validation, not found, conflict, permission, upstream, usage, and job timeout — and how to recover.

Staplehire CLI failures print one structured JSON object to **stderr** and exit with a **stable, non-zero code**, so humans, scripts, and agents can recover predictably. Branch on the **exit code** first, then on `error.code`.

```json theme={null}
{
  "error": {
    "name": "ValidationError",
    "code": "candidate_email_invalid",
    "message": "Invalid email address",
    "hint": "Use a valid email format",
    "field": "email",
    "requestId": "req_…",
    "status": 400
  }
}
```

<Warning>
  Never grep `error.message` — the wording can change. Parse `error.code` (stable) and read `error.hint` / `error.field` to fix the request. Log `error.requestId` when contacting support.
</Warning>

## First step when a command fails

```bash theme={null}
staplehire doctor -q
```

Then capture the command's stderr and inspect the error:

```bash theme={null}
staplehire candidates create <roleId> --email not-an-email --stage Sourced 2> error.json
jq '.error' error.json
```

## Exit codes

| Code | HTTP  | Meaning              | Typical fix                                                     |
| ---- | ----- | -------------------- | --------------------------------------------------------------- |
| `0`  | —     | Success              | Parse stdout                                                    |
| `1`  | other | Generic / internal   | Retry; inspect `error.requestId` and escalate                   |
| `2`  | 401   | Authentication       | Run `staplehire login` or set `STAPLEHIRE_KEY`                  |
| `3`  | 400   | Validation           | Fix the field named in `error.field` / `error.hint`             |
| `4`  | 404   | Not found            | Re-list the parent resource and use a real ID                   |
| `5`  | 409   | Conflict             | Reuse the existing resource or change unique fields             |
| `6`  | 403   | Permission           | Check the key's organization and scope                          |
| `7`  | 502   | Upstream             | Retry with backoff                                              |
| `8`  | —     | CLI usage / bad args | Run `staplehire <command> --help` or `staplehire commands`      |
| `9`  | —     | `jobs poll` timeout  | Re-run `jobs poll` with a higher `--timeout`, or use `jobs get` |

## Common `error.code` values

| Code                        | Resolution                                                                                                                 |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `candidate_email_invalid`   | Fix the email syntax                                                                                                       |
| `candidate_already_exists`  | The email exists on this role — [find and move](/docs/stages-cli#recover-from-a-duplicate-candidate-409) the existing candidate |
| `stage_name_already_exists` | Reuse the stage or pick a new name                                                                                         |
| `stage_not_found`           | Run `staplehire stages list <roleId>`                                                                                      |
| `role_not_found`            | Confirm the role created successfully (`roles list`)                                                                       |
| `interview_design_required` | Run `designs create` before sending an interview                                                                           |
| `job_failed`                | Read `error.message`; fix inputs or retry with `--force`                                                                   |
| `api_key_invalid`           | Create a new key — see [Authentication](/docs/authentication)                                                                   |
| `api_key_revoked`           | Issue a new key in Settings → Developers                                                                                   |
| `org_not_provisioned`       | Contact support                                                                                                            |

## Error types

### AuthenticationError (exit 2)

The CLI could not resolve or validate an API key.

```bash theme={null}
staplehire login          # local
# or, for agents and CI:
export STAPLEHIRE_KEY=sh_live_xxx
staplehire doctor -q
```

### ValidationError (exit 3)

A required value is missing or invalid. Fix the field named by `.error.field`, then rerun.

### NotFoundError (exit 4)

A role, candidate, design, stage, or job ID does not exist or isn't visible to your organization.

```bash theme={null}
staplehire roles list
staplehire candidates list --role-id <roleId>
staplehire designs list <roleId>
staplehire jobs get <jobId>
```

### ConflictError (exit 5)

A resource already exists — commonly a duplicate candidate email on the same role.

```bash theme={null}
staplehire candidates list --role-id <roleId> --email alex@example.com
```

### Poll timeout (exit 9)

`jobs poll` stopped waiting. It does **not** mean the job failed.

```bash theme={null}
staplehire jobs poll <jobId> --interval 5000 --timeout 300000
staplehire jobs get <jobId>
```

## Capture and branch in a shell script

```bash theme={null}
if ! out=$(staplehire candidates create "$ROLE_ID" --email "$EMAIL" --stage Sourced 2> err.json); then
  code=$?
  err=$(jq -r '.error.code // "unknown"' err.json)
  echo "failed (exit $code): $err" >&2
  case "$err" in
    candidate_already_exists) echo "→ move the existing candidate instead" >&2 ;;
    stage_not_found)          staplehire stages list "$ROLE_ID" ;;
  esac
  exit "$code"
fi
echo "$out" | jq -r '.candidate.id'
```

<Note>
  **Agent loop rule:** if the same `error.code` repeats with the same payload, change the inputs or stop — do not retry blindly.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="Where does error JSON print?">
    To stderr. Successful data prints to stdout, so you can pipe stdout cleanly while still capturing errors separately.
  </Accordion>

  <Accordion title="What should I send to support?">
    The `error.requestId`, the exact command, and the timestamp.
  </Accordion>

  <Accordion title="Why did `jobs poll` exit with code 9?">
    The `--timeout` elapsed. The job may still be running — inspect it with `jobs get` or poll again.
  </Accordion>
</AccordionGroup>

Related: [Authentication](/docs/authentication) · [Poll jobs](/docs/poll-agent-jobs) · [Command reference](/docs/commands)
