Jobs: reports, email and links
Save a public job or quote signature
/a/Job_signatureCustomerSave
Consume a pending public signing token and write its signature value to the selected job or quote.
When to use this endpoint
The public signature-pad page posts this after drawing. Keep pid, md5 and object type from the received signing link; do not substitute IDs or treat it as a general authenticated form editor.
Authentication and permissions
Public bearer-token route, exempt from CRM-session CSRF. No logged-in user, API key or prior visit to the view page is required; a pending token within the supplied tenant is sufficient.
The handler routes queries explicitly using pid. It does not bind obj_type to the request’s stored type or validate recipient identity.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
pid | Integer string | Yes | Positive tenant ID from the request link. |
md5 | String | Yes | Exactly 32 lowercase hexadecimal characters identifying an unexpired pending request of the selected document type. |
v | String | Yes | JSON-encoded nonempty stroke list, e.g. [{"lx":1,"ly":1,"mx":10,"my":10}]. Every stroke needs finite numeric lx, ly, mx and my coordinates, and at least one segment must move. Stored unchanged after validation. |
obj_type | Integer string | No | Default 0 saves a job; 3 saves a quote. Must match the stored token type (Job=0, Quote=1 internally); other public document types are rejected. |
Example request
$result = FM_api('Job_signatureCustomerSave', [
'pid' => '123',
'md5' => '0123456789abcdef0123456789abcdef',
'v' => json_encode([['lx'=>1, 'ly'=>1, 'mx'=>10, 'my'=>10]]),
]);
Example response
{"ok":1}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"missing or invalid parameter: v"} | v absent, blank, malformed, empty or without any drawn segment. |
{"error":"pid not valid - internal error"} | Tenant ID nonpositive. |
{"error":"signature request not found, or expired, or has been signed already"} | No matching unexpired pending token of the selected document type; consumed tokens cannot be reused. |
{"error":"form does not exist"} | Target job/quote is missing/deleted, or its current form is missing. |
{"error":"field is not a signature"} | A matching named field exists but is not signature type. |
{"error":"field does not exist"} | No matching named signature field. |
{"error":"Unable to save job delivery changes"} | A checked database write, audit or transaction failed; local transactional writes roll back. Cross-tenant files/commits are not distributed-atomic. |
{"error":"missing or invalid parameter: obj_type"} | Public document type is not 0 (Job) or 3 (Quote). A supported but token-mismatched type returns the unavailable-request response instead. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Job mode checks an active job, resolves the current form and exact sig_field name, and stores v in form_values[name]. Existing other answers are preserved; malformed stored answers become an empty array first.
- Quote mode requires an active quote, resolves the named signature field’s stable meta.id and writes form_fields[id]. Public obj_type=3 must match the token’s stored Quote request type.
- Sets object meta.signed_by_request to Unix seconds. Request completion metadata records formatted time, epoch time_e and remote IP; status becomes 1, so an ordinary subsequent call is rejected.
- If quote-set-won-when-customer-signs is enabled, quote statuses -1,0,4,5,6 change to Won (1); won_date is populated if null. With features2.workflow enabled it records the transition transactionally with the status write, and new-CRM tenants receive the status notification.
- If request metadata has nonzero send_report (or legacy send_quote), emails the signed document to the original recipient after commit. Also notifies the requesting user when user_id is positive. Unconfirmed follow-ups retain ok and add saved=1, warning and warnings; check mail history before retrying.
- Checks expiry, document binding and nonblank stroke structure before saving. Locks the pending request and active target; signature, optional Quote Won/Workflow transition and token consumption commit together. No paging applies.
Side effects
- Writes job answers/meta/last_edited/edited_uuid=Job_signatureCustomerSave with job-cache invalidation, or quote form_fields/meta/last_edited.
- Marks the request consumed with time/IP metadata; may change quote status and enqueue Workflow work.
- Attempts requesting-user notification and optional signed-document mail; does not mark the job complete or use Job_emailReport’s sent-marker path.
See it in Fieldmotion
CRM1
Follow a signature-request email, draw and Save. application-sign.js posts the compatibility route Appointment_signatureCustomerSave with pid, md5, obj_type and JSON strokes; it checks a minimum length in the browser only.
CRM2
Uses the same public page/script after the signature-request email. No separate CRM2 save route is used.
Related endpoints
Job_signatureCustomerViewsupplies the public pad and document link.Job_signatureRequestcreates a job signing request.Job_getAsPdfretrieves report bytes without consuming a signature request.