# Dispatching a job: the tool sequence for AI assistants

> assign_job (runs the compliance gate) → propose_send_work_order → approve_send_work_order (two legs, no confirm) → the subcontractor accepts from the email, which dispatches the job → record_completion (or their portal) → a pending payable is raised by the job.completed event. An unsigned agreement refuses assign_job with code sma_unsigned; clear it with create_contractor_agreement → propose_send_contractor_agreement → confirm_pending_operation → approve_send_contractor_agreement.

URL: https://biloh.com.au/docs/reference/dispatching-a-job-for-agents
Category: Reference | Audience: agent | Updated: 2026-11-08

**At a glance**

- **Who it's for:** AI assistants working in a Biloh business
- **What it does:** [Subcontractors who work from their phone](https://biloh.com.au/capabilities/subcontractor-dispatch) · [Insurance and licences checked before every job](https://biloh.com.au/capabilities/contractor-compliance)
- **Tools it uses:** [`suggest_contractors_for_job`](https://biloh.com.au/connect#suggest_contractors_for_job) · [`assign_job`](https://biloh.com.au/connect#assign_job) · [`create_contractor_agreement`](https://biloh.com.au/connect#create_contractor_agreement) · [`propose_send_contractor_agreement`](https://biloh.com.au/connect#propose_send_contractor_agreement) · [`confirm_pending_operation`](https://biloh.com.au/connect#confirm_pending_operation) · [`approve_send_contractor_agreement`](https://biloh.com.au/connect#approve_send_contractor_agreement) · [`record_contractor_agreement_response`](https://biloh.com.au/connect#record_contractor_agreement_response) · [`propose_send_work_order`](https://biloh.com.au/connect#propose_send_work_order) · [`approve_send_work_order`](https://biloh.com.au/connect#approve_send_work_order) · [`record_work_order_acceptance`](https://biloh.com.au/connect#record_work_order_acceptance) · [`dispatch_job`](https://biloh.com.au/connect#dispatch_job) · [`record_completion`](https://biloh.com.au/connect#record_completion) · [`get_job`](https://biloh.com.au/connect#get_job) · [`get_payable`](https://biloh.com.au/connect#get_payable) · [`propose_release_payable`](https://biloh.com.au/connect#propose_release_payable) · [`approve_release_payable`](https://biloh.com.au/connect#approve_release_payable)
- **What a person approves:** Your business records are stored in Sydney, Australia.
- **Checked against the code:** 2026-11-08

This is the sequence an assistant follows when the person it works for says "give this job to
that subcontractor". Every message the assistant sends the subcontractor is staged first and sent only after
the person says yes. Money is always integer cents: $240.00 is `24000`.

## The sequence

| Step | Tool | What it does |
| --- | --- | --- |
| 1 (optional) | `suggest_contractors_for_job` | Who passes the gate for this job (`candidates`) and who fails (`blocked`, each with its reason) |
| 2 | `assign_job` | Runs the compliance gate, then sets the job's subcontractor |
| 3 | `propose_send_work_order` | Stages the work order; returns `operation_id`. Nothing is sent |
| 4 | `approve_send_work_order` | Sends it, with `confirmed: true` after the person says yes |
| 5 | (the subcontractor) | Accepts from **Review Work Order** in the email; the job becomes `dispatched` |
| 6 | `record_completion` | Marks it complete when they did the work but did not press **Mark Complete** |
| 7 | `get_payable` | The subcontractor's bill, raised automatically by the completion |

## Step 1 — who can take it?

`suggest_contractors_for_job({ job_id })` is read-only. Each entry in `candidates` and `blocked`
carries `display_name`, `is_internal` (true for the business's own crew) and `prospective_rate` —
what that subcontractor would be paid on this job, in cents, with `derivable: false` and a
`reason` when it cannot be worked out. `client_rate_cents` comes back beside them.

## Step 2 — assign_job and the compliance gate

Input: `{ job_id, contractor_id }` (or `team_member_id` for the business's own team, which skips the
gate). `contractor_rate_cents` is optional and only for a deliberate change to the subcontractor's
standing terms.

On success it returns `job`, `warnings` (soft, never blocking — e.g. insurance expiring soon),
`rate_decision` (what a reassignment did to the visit's pay) and `first_attach_rate` (the same for
a first assignment). Assigning does not send anything: `work_order_sent_at` stays null.

A hard-gate failure writes nothing and returns an error whose text begins
*Compliance gate blocked assignment:* and carries a `code`. The common ones:

| `code` | Meaning | What clears it |
| --- | --- | --- |
| `sma_unsigned` | Agreement not signed | Send the agreement (below) |
| `no_insurance` | No verified insurance document | Collect and verify a certificate |
| `insurance_expired` | Insurance past its expiry date | A current certificate |
| `insurance_below_site_minimum` | Cover below what the site requires | Higher cover, or another subcontractor |
| `no_capability` | Not set up for this service | Add the service to the subcontractor |
| `missing_credential` | A licence or card the service requires | Collect and verify it |
| `on_leave` / `inactive` | Not available | Change availability, or pick someone else |

Reassigning a job that is already `completed`, `approved`, `invoiced` or `paid` is refused with
`terminal_reassignment_requires_confirmation`, an `assessment` and a `confirm_token`. Show the
assessment to the person and wait: only they can decide to move work the client has been billed for.

## Clearing sma_unsigned

1. `create_contractor_agreement` — a draft agreement for the subcontractor.
2. `propose_send_contractor_agreement({ agreement_id })` — a preview PDF (`pdf_url`) and a
   `readiness` block. Read its findings to the person before going on.
3. `confirm_pending_operation` — stages the send. Nothing is emailed.
4. `approve_send_contractor_agreement` — sends it. The subcontractor signs from the link.

If they agreed by phone, email or in person, record it with `record_contractor_agreement_response`
instead; it marks the agreement signed. Then call `assign_job` again.

## Steps 3 and 4 — the work order, in two legs

`propose_send_work_order` takes exactly one of `job_id`, `job_ids` (one subcontractor, up to 100),
`contract_service_line_id` (a whole recurring series) or `contract_service_line_ids` (several
series at one site). It runs the same gate over every member; one failure rejects the whole set
with a `failures` array and creates nothing. It returns `operation_id`, `recipients`,
`recipient_count`, an `earnings_total_cents` forecast and, for a visit on a recurring line, a
`scope_advisory` to read before approving.

Then `approve_send_work_order({ operation_id, confirmed: true })` — only after the person says yes.
It returns `work_order_id`, `accept_url`, `recipients` and `recipient_details` (one row per address,
each with its own `message_id` and `status`). Do not call `confirm_pending_operation` for a work
order.

## Step 5 — acceptance dispatches

The approve leg does not change the job's status. The subcontractor's acceptance does: a single
job goes `scheduled` → `dispatched`, and a series acceptance dispatches every scheduled visit on
the line. An acceptance that arrived by phone or email is recorded with
`record_work_order_acceptance`. `dispatch_job` dispatches without a work order and returns
`dispatched_without_work_order: true` to say so.

## Steps 6 and 7 — completion and the payable

`record_completion({ job_id })` needs the job `dispatched` (or `backfill: true` for a missed or held
one, with `completed_on` set to the real day). It checks the photos and notes the completion
rules require, then emits `job.completed`. That event raises a pending payable for the
subcontractor and, when the client is invoiced job by job, a draft invoice; the response's
`auto_invoice` says which. Read the bill with `get_payable`.

The payable becomes releasable when the client's invoice for the job is paid.
`propose_release_payable` then `approve_release_payable` record the date the business paid the
subcontractor at its bank.

**Biloh never moves money: its tools record payments that already happened, and a pay-now link opens the business's own Stripe checkout for its client.**

## Related

- [Dispatch a job to a subcontractor (how-to)](/docs/how-to/dispatch-a-job-to-a-subcontractor)
- [Why a contractor can't be assigned to a job](/docs/concepts/why-a-contractor-cant-be-assigned)
- [Write safety: expected_tenant](/docs/reference/write-safety-expected-tenant)
