When to use this endpoint

Choose an existing invoice and link the job instead of generating another header. A job can have only one type-3 relation under this guard, while one invoice can have multiple jobs.

Authentication and permissions

Requires authenticated tenant context through signed API-key or CRM session. crm_access bit 64 returns no permission to access api before the handler’s login/parameter flow.

No customer-match, assignment or completion permission is checked. Dispatcher account/user/IP restrictions and session CSRF still apply.

The dispatcher also applies user/account and IP restrictions. CRM sessions carrying a CSRF token must supply X-FMCSRF or _csrf; missing or incorrect tokens can be rejected before the handler runs.

Parameters

ParameterTypeRequiredDescription
job_idInteger stringYesPositive physical active job ID.
invoice_idInteger stringYesPositive active invoice ID. Status must be <=1; statuses greater than 1 are rejected.

Example request

$result = FM_api('Job_invoiceLink', [
	'job_id' => '1042',
	'invoice_id' => '501',
]);

Example response

{"ok":1}

Errors and unusual responses

ResponseCondition
{"error":"not logged in"}No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs.
{"error":"no permission to access api"}User crm_access bit 64 is set.
{"error":"missing or invalid parameter: job_id"}Missing/nonpositive job ID; invoice_id has the analogous error.
{"error":"invoice does not exist"}Invoice missing/deleted.
{"error":"jobs may only be linked to open invoices"}Invoice status >1.
{"error":"job does not exist"}Job missing/deleted.
{"error":"this job is already linked to an invoice"}Any type-3 relation already exists for the job, even a stale relation to a deleted invoice or the same pair.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Only the type-3 relation is created; rows, customer ownership and job resources are not copied or changed.
  • The existing-link guard does not join active invoices or inspect the legacy invoice pointer. It is not a race-safe uniqueness constraint.
  • If invoice.email_sent is nonblank and parses to a positive epoch, propagates that value to the invoice’s directly linked jobs (including the new one). With no direct links the shared helper has a legacy fallback.
  • meta.jobs_linked increments its stored value rather than recounting relations. No pagination, list filtering, customer equality check or invoice-row ownership change applies.

Side effects

  • Inserts manymany type 3 with Unix last_edited; adds invoice history job link and updates invoice metadata/count and last_edited. It does not set invoice.edited_uuid in this handler.
  • Can update linked job invoice_sent, last_edited and edited_uuid=Invoice_setJobsInvoiceSent and invalidate the meetings cache. Does not queue mail or add explicit job-link history.

See it in Fieldmotion

CRM1

On the job invoice screen choose Link, or select jobs and Link to Invoice. pageJobInvoice.js / jobsLinkToInvoice.js submit job_id and invoice_id; filter Job_invoiceLink.

CRM2

Use the Job-to-Invoice link widget or bulk Link to Invoice. widgetTableJobToInvoiceLink.js / jobsLinkToInvoice.js post the same IDs and refresh linked invoice data.

Related endpoints