---
title: Jobs
description: What a job id is, how to run one with endpointId "job:<id>", how looot picks a provider, and what each refusal means.
sidebar:
  icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="looot-icon"><circle class="li-i" cx="12" cy="12" r="8.5"/><circle class="li-a" cx="12" cy="12" r="3.5"/></svg>'
---

A job is the task you want done, not the provider that does it. `people.email.find` is a job.
`people.email.verify` is a job. Dozens of providers can each carry an endpoint that does the same
job, and looot ranks them against each other so you do not have to.

You see a job id in two places on a search row: `capability` is the resolved job id (the same
value as `job.id`), and `sourceCapability` is the raw slug the endpoint was registered under. Most
of the time they match. When a provider's endpoint was filed under an older slug that the catalog
now treats as an alias, `capability` shows the job it belongs to and `sourceCapability` shows what
it was actually registered as.

## Run a job by its id

Send `job:<job id>` as the `endpointId`:

<CodeGroup>

```bash CLI
looot run job:people.email.verify \
  --input '{"email":"jane.doe@example.com"}' \
  --wait
```

```json MCP
{
  "endpointId": "job:people.email.verify",
  "input": { "email": "jane.doe@example.com" },
  "idempotencyKey": "<new unique key>",
  "wait": 20
}
```

</CodeGroup>

`looot run job:<id>` works on CLI 1.1.0 today, since the CLI sends whatever you type as the
positional argument. `--fallback` and `--prefer` are not in CLI 1.1.0 yet, so if you want either
of those on a job run, use MCP or REST until the next CLI release.

An alias resolves to its canonical job: `job:companies.find` runs `company.enrich`.

## How the pick works

looot orders the job's live providers (in `prefer` order, `balanced` by default) and picks the
first one that:

1. is not in `fallback.exclude`,
2. can run for your workspace right now,
3. has a price,
4. your workspace's route policy allows, and
5. accepts your input under its own schema (see [Job inputs](/concepts/job-inputs)).

The run that follows is then an ordinary run on that one endpoint: the same validation, quote,
hold and settlement a direct run on that endpoint gets. Sending `fallback` on a job run does not
change the pick; it lets the walk continue to the job's other providers, in the same order, if the
picked one misses.

A job run does not turn fallback on by default. If you want looot to try the next provider on a
miss, add `fallback` yourself. See [Fallback](/concepts/fallback).

The run record carries `requestedJob`:

```json
{
  "id": "job:people.email.verify",
  "job": "people.email.verify",
  "prefer": "balanced",
  "pickedEndpointId": "icypeas-email-verify",
  "reason": "first of 6 people.email.verify endpoints in rankJob order for prefer \"balanced\"",
  "skipped": []
}
```

`skipped` lists the rows ranked above the pick that were passed over, each with a reason such as
`unavailable`, `unpriced`, `blocked_by_policy`, or `needs_identity` with "needs `<field>`".

A [`runs_list`](/mcp-tools/runs-list) row for a job run also carries `job` (the id you sent, e.g. `"job:web.scrape.markdown"`)
next to `endpointId` (the provider that was picked).

## Refusals

A job run that cannot be run is a failed run at $0, refused before any hold. `error.code` is
[`validation_error`](/errors/rest-errors#validation_error); the exact reason is in `result.code`:

| `result.code` | Meaning |
| --- | --- |
| [`unknown_job`](/errors/job-refusals#unknown_job) | No job has that id. The message suggests up to 3 close job ids. |
| [`no_supply_for_job`](/errors/job-refusals#no_supply_for_job) | No endpoint in the catalog does this job. |
| [`needs_input`](/errors/job-refusals#needs_input) | No provider of the job accepts the input you sent. `result.needs` lists each provider with the fields it needs; `error.details.fields` names each missing field. |
| [`no_runnable_provider`](/errors/job-refusals#no_runnable_provider) | The job exists, but nothing can run for this workspace right now (no key, drained, unpriced, or the route policy refuses every candidate). |
| [`invalid_input`](/errors/job-refusals#invalid_input) | A top-level `email`, `url`, `domain` or `phone` value is broken. See [Job inputs](/concepts/job-inputs). |

`no_supply_for_job` and `no_runnable_provider` are `whoseError: "gateway"`; `needs_input`,
`unknown_job` and `invalid_input` are `whoseError: "customer"`. `no_runnable_provider` is the one
retryable case: its hint is "connect your own key, or retry later with a new idempotencyKey".

## What is not there yet

Signed-in `looot inspect job:<id>` is not supported yet. Inspect an endpoint id instead, for
example one you got back from a search row or a previous run's `requestedJob.pickedEndpointId`.

<Related />
