---
title: "Get enumeration job status"
method: GET
path: "/email/jobs/{job_id}"
---

# Get enumeration job status

`GET /email/jobs/{job_id}`

Poll a single job -- owner-scoped exactly like every other read here.

`enumerate_email` now enqueues every job as a one-job batch (see its
docstring), so this poll also drives that batch's finalization/billing:
it looks up the owning batch (also owner-scoped, so this adds no new IDOR
surface), fetches its results, and syncs/finalizes through the same
`_sync_batch_task` helper `get_batch_results` uses below -- no separate
copy of the billable-status decision or the CAS. A job
with no batch (none found, or the batch row aged past its purge window)
just skips straight to returning the job, same as before this change.

## Path parameters

- `job_id` string, uuid, required

## Response `200`

Successful Response

- JobResponse — Single job status + result (`GET /email/jobs/{id}`).
  - `job_id` string, uuid, required
  - `status` 'queued' | 'processing' | 'completed' | 'failed', required — Job lifecycle status.
  - `kind` 'find' | 'verify', nullable
  - `result` union
    - EnumerationOutput — Full output of an email enumeration run.
      - `first_name` string, required
      - `last_name` string, required
      - `domain` string, required
      - `status` 'found' | 'not_found' | 'catch_all' | 'catch_all_pattern' | 'undeliverable' | 'error' | 'timeout' | 'greylisted' | 'rate_limited', required — Outcome status for an enumeration attempt.
      - `result` EnumerationMatch — Details about a matched email pattern.
        - `email` string, required
        - `pattern` string, required
        - `tier` integer, required
        - `smtp_code` integer, required
        - `valid` boolean, required
      - `is_catch_all` boolean, required
      - `mx_host` string, nullable
      - `provider` string, nullable
      - `attempts` integer, required
      - `duration_ms` integer, required
      - `error` string, nullable
    - ValidationOutput — Result payload for a verify job, written by the Rust worker into `mirrorball.job.result` and decoded when the job's `kind = 'validate'`.
      - `email` string, required
      - `status` 'safe' | 'risky' | 'invalid' | 'unknown', required — Deliverability verdict for a verify (validate) job. 1:1 with the Rust worker's `Reachable`: safe = deliverable, risky = catch-all or full-inbox, invalid = undeliverable/refused, unknown = greylisted/inconclusive.
      - `is_deliverable` boolean, required
      - `is_catch_all` boolean, required
      - `mx_host` string, nullable
      - `provider` string, nullable
      - `smtp_code` integer, nullable
      - `attempts` integer, required
      - `duration_ms` integer, required
      - `error` string, nullable
      - `reason` 'deliverable' | 'catch_all' | 'full_inbox' | 'greylisted' | 'mailbox_disabled' | 'no_mailbox' | 'no_smtp_connection' | 'smtp_error' | 'timed_out' — Structured cause behind a verify job's `ValidationStatus`. 1:1 with the Rust worker's `ValidationReason`. Absent on job rows written before the worker emitted it, hence optional on `ValidationOutput`.
  - `error` string, nullable

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/discolike/apis/discolike-api.md) · [All operations](https://skmtc.net/discolike/apis/discolike-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/discolike/discolike-api/revisions/eaeae50f8275/schema)
