Jobs: forms and resources
Import mapped rows into a job table
/a/Job_addTableRow
Append mapped import rows to one job table, or replace that table’s existing rows.
When to use this endpoint
Use after choosing a table form and mapping import columns to its zero-based field positions. It is the CSV-import workflow, not a table-form schema editor.
Use Job_tableRowAppend when rows are already in destination-column order; use Job_setTableCells for a small set of addressed cells.
Authentication and permissions
Authenticate with signed API-key fields or a logged-in CRM session. The handler performs no additional page or role-permission check.
Job and form IDs refer to the authenticated account. Cross-account writes occur only through the stored outsourcing relationship on the routes that explicitly mirror changes.
The dispatcher also applies account/user and IP restrictions. A CRM session carrying a CSRF token must supply it through X-FMCSRF or _csrf; missing/incorrect tokens can be rejected before this handler runs.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | Integer string | Yes | Positive physical job ID, not a mobile app ID. |
table_id | Integer string | Yes | Positive destination table-form ID; the handler does not check that this form exists or belongs to the job’s main form. |
map | JSON array string | Yes | One mapping per source column position, for example [{"from":"Description","to":"f0"}]. The source position is the mapping’s array index; from is not read. Entries without to are skipped; all f characters are removed from to before integer conversion. |
rows | JSON array string | Yes | Positional source rows, for example [["Inspection","Passed"]]. Values are copied from each mapping’s source index, with earlier destination gaps filled with empty strings. No decoded-shape or size validation is performed. |
replace_existing | Integer string | No | Defaults to 0. Any nonzero integer clears the selected table before appending; other tables remain stored. |
Example request
$result = FM_api('Job_addTableRow', [
'job_id'=>'1042', 'table_id'=>'302',
'map'=>json_encode([['from'=>'Description','to'=>'f0'], ['from'=>'Result','to'=>'f1']]),
'rows'=>json_encode([['Inspection','Passed']]),
'replace_existing'=>'0',
]);
Example response
{"ok":1}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error":"missing or invalid parameter: job_id"} | Job ID is nonpositive. |
{"error":"missing or invalid parameter: table_id"} | Table ID is nonpositive. |
{"error":"missing or invalid parameter: map"} | map is absent. |
{"error":"missing or invalid parameter: rows"} | rows is absent. |
{"error":"no such job exists"} | No active job is read. |
{"error":"Unable to save job"} | The primary database save failed. Successful-save history and subsequent follow-ups are not run. |
{"ok":1,"warning":"Job saved; follow-up incomplete. Inspect saved state before retrying.","warnings":["Job_updateCosts: Unable to save job"],"saved":1} | The primary save succeeded, but a recalculation, asset rebuild, notification or outsourced mirror failed. warnings identifies the failed follow-up; any normal response fields remain present. A benign no-change result from a follow-up is not a failure. Read saved state before retrying. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Normalizes missing or invalid stored table collections to an empty collection before adding rows. Unselected tables are preserved.
- Default mode appends in request order and does not deduplicate. Replaying a successful import duplicates rows. Replace mode discards every existing row in the selected table.
- Malformed mapping/row JSON can produce PHP warnings or runtime failures rather than a structured validation error. Supply valid arrays; an ok response does not establish that every source column was mapped.
- Table answers are serialized with table IDs as top-level object keys. There are no paging, filtering, row-count or batch-size controls.
Side effects
- Writes subform_values, last_edited and edited_uuid; invalidates the meetings cache. The primary write is checked before follow-ups.
- Calls Job_updateCosts, which may enrich resource answers, update financial totals and invoke Job_updatePrices; follow-up failures are returned as saved-state warnings. This route adds no direct import history or asset-link rebuild and does not mirror outsourced answers.
See it in Fieldmotion
CRM1
Open a job’s Tables page and use Import. pageJobTables.js supplies the column map and batches rows; Append is the default and Replace Existing sends replace_existing only for the initial replacement batch. Filter Network for Job_addTableRow.
CRM2
Open the job Form and import a table in pageJobForm.js. The importer also exposes Append / Replace Existing and sends Job_addTableRow; inspect job_id, table_id, map, rows and the replacement flag.
Related endpoints
Job_tableRowAppendappends rows already in destination field order.Job_setTableCellspatches selected row/field positions while retaining unspecified cells.Job_updateCostsrefreshes cost/resource snapshots and invokes the price follow-up.