{ "kind": "open-flow-workflow", "version": 1, "name": "Airtable Contacts to Google Sheets", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_fb78eddfc609d08b": { "name": "Run contact sync", "kind": "start", "inputDefs": [] }, "n_4a612d9d09d5a555": { "inputs": { "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4a612d9d09d5a555", "output": "state" } ] }, "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_076c3aa01d97cc06", "output": "records" } ] }, "offset": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_076c3aa01d97cc06", "output": "offset" } ] } }, "name": "Collect every Airtable page", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records returned by Airtable." }, "nullable": true, "handle": "records" }, { "jsonSchema": { "type": [ "string", "null" ], "description": "Pagination cursor for the next page of records, or null when unavailable." }, "nullable": true, "handle": "offset" } ], "outputs": [ { "jsonSchema": { "type": "object", "properties": { "records": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "records" ] }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "Opaque pagination cursor returned by a previous Airtable response." }, "nullable": true, "handle": "offset" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "hasRecords" } ], "name": "Collect every Airtable page", "moduleId": "n_6e708ae1aa2dbf66", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b069f8e637a95628": { "inputs": {}, "name": "More source pages", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_4a612d9d09d5a555", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_076c3aa01d97cc06": { "inputs": { "baseId": { "kind": "value", "value": "YOUR_AIRTABLE_BASE_ID" }, "tableIdOrName": { "kind": "value", "value": "TableName" }, "filterByFormula": { "kind": "value", "value": "{LastUpdated} >= DATEADD(TODAY(), -1, 'days')" }, "pageSize": { "kind": "value", "value": 100 }, "offset": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4a612d9d09d5a555", "output": "offset" } ] } }, "name": "Read recently updated contacts", "kind": "task", "task": { "inputs": [ { "description": "Base ID in the format appXXXXXXXXXXXXXX.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Base ID in the format appXXXXXXXXXXXXXX." }, "nullable": false, "handle": "baseId" }, { "description": "Table ID or table name accepted by the Airtable path parameter.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Table ID or table name accepted by the Airtable path parameter." }, "nullable": false, "handle": "tableIdOrName" }, { "description": "View name or view ID used by Airtable to filter and sort results.", "jsonSchema": { "type": "string", "minLength": 1, "description": "View name or view ID used by Airtable to filter and sort results." }, "nullable": true, "handle": "view", "value": null }, { "description": "Field names or field IDs to include in the Airtable response.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "An array item." }, "minItems": 1, "description": "Field names or field IDs to include in the Airtable response." }, "nullable": true, "handle": "fields", "value": null }, { "description": "Sort rules applied by Airtable in order.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "field": { "type": "string", "minLength": 1, "description": "Field name or field ID used by Airtable for sorting." }, "direction": { "type": "string", "enum": [ "asc", "desc" ], "description": "Sort direction accepted by Airtable." } }, "required": [ "field" ], "additionalProperties": false, "description": "Sort rule accepted by the Airtable list records endpoint." }, "minItems": 1, "description": "Sort rules applied by Airtable in order." }, "nullable": true, "handle": "sort", "value": null }, { "description": "Formula string evaluated by Airtable to filter matching records.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Formula string evaluated by Airtable to filter matching records." }, "nullable": true, "handle": "filterByFormula", "value": null }, { "description": "Maximum total number of records to return before Airtable stops pagination.", "jsonSchema": { "type": "integer", "minimum": 1, "description": "Maximum total number of records to return before Airtable stops pagination." }, "nullable": true, "handle": "maxRecords", "value": null }, { "description": "Number of records to return per page.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Number of records to return per page." }, "nullable": true, "handle": "pageSize", "value": null }, { "description": "Opaque pagination cursor returned by a previous Airtable response.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Opaque pagination cursor returned by a previous Airtable response." }, "nullable": true, "handle": "offset", "value": null }, { "description": "Cell format accepted by Airtable for read operations.", "jsonSchema": { "type": "string", "enum": [ "json", "string" ], "description": "Cell format accepted by Airtable for read operations." }, "nullable": true, "handle": "cellFormat", "value": null }, { "description": "Timezone string sent to Airtable when cellFormat is string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Timezone string sent to Airtable when cellFormat is string." }, "nullable": true, "handle": "timeZone", "value": null }, { "description": "User locale sent to Airtable when cellFormat is string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "User locale sent to Airtable when cellFormat is string." }, "nullable": true, "handle": "userLocale", "value": null }, { "description": "Whether Airtable should return field IDs instead of field names in record objects.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should return field IDs instead of field names in record objects." }, "nullable": true, "handle": "returnFieldsByFieldId", "value": null }, { "description": "Whether Airtable should return date dependency metadata for linked record fields.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should return date dependency metadata for linked record fields." }, "nullable": true, "handle": "includeDateDependencyMetadata", "value": null }, { "description": "Record metadata fields to include in Airtable list records responses.", "jsonSchema": { "type": "array", "items": { "const": "commentCount", "description": "An array item." }, "minItems": 1, "description": "Record metadata fields to include in Airtable list records responses." }, "nullable": true, "handle": "recordMetadata", "value": null } ], "outputs": [ { "description": "Records returned by Airtable.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records returned by Airtable." }, "nullable": false, "handle": "records" }, { "description": "Pagination cursor for the next page of records, or null when unavailable.", "jsonSchema": { "type": [ "string", "null" ], "description": "Pagination cursor for the next page of records, or null when unavailable." }, "nullable": true, "handle": "offset" } ], "name": "Read recently updated contacts", "executor": { "kind": "connector", "action": "airtable.list_records" } } }, "n_b50472b48bdded68": { "inputs": {}, "name": "No contacts selected", "kind": "condition", "matchMode": "first", "cases": [ { "output": "empty", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_4a612d9d09d5a555", "output": "hasRecords" } }, "operator": "==", "right": { "kind": "value", "value": false } } ] } ] } ] }, "n_5440c5af875ad6aa": { "inputs": { "spreadsheetId": { "kind": "value", "value": "YOUR_GOOGLE_SHEET_ID" }, "range": { "kind": "value", "value": "A1:E1000" }, "majorDimension": { "kind": "value", "value": "ROWS" }, "valueRenderOption": { "kind": "value", "value": "UNFORMATTED_VALUE" } }, "name": "Read the existing sheet window", "kind": "task", "task": { "inputs": [ { "description": "The spreadsheet ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": true, "handle": "spreadsheetId", "value": null }, { "description": "The A1 notation of the range to retrieve.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to retrieve." }, "nullable": false, "handle": "range" }, { "description": "The major dimension for the returned values.", "jsonSchema": { "type": "string", "enum": [ "DIMENSION_UNSPECIFIED", "ROWS", "COLUMNS" ], "description": "The major dimension for the returned values." }, "nullable": true, "handle": "majorDimension", "value": null }, { "description": "How values should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" ], "description": "How values should be rendered in the output." }, "nullable": true, "handle": "valueRenderOption", "value": null }, { "description": "How dates and times should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "SERIAL_NUMBER", "FORMATTED_STRING" ], "description": "How dates and times should be rendered in the output." }, "nullable": true, "handle": "dateTimeRenderOption", "value": null } ], "outputs": [ { "description": "The A1 notation of the range.", "jsonSchema": { "type": "string", "description": "The A1 notation of the range." }, "nullable": false, "handle": "range" }, { "description": "The major dimension of the values (ROWS or COLUMNS).", "jsonSchema": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "nullable": false, "handle": "majorDimension" }, { "description": "The data values in the range.", "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" } ], "name": "Read the existing sheet window", "executor": { "kind": "connector", "action": "googlesheets.values_get" } } }, "n_8754ced7a4b20630": { "inputs": { "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4a612d9d09d5a555", "output": "state", "field": "records" } ] }, "values": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5440c5af875ad6aa", "output": "values" } ] }, "range": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5440c5af875ad6aa", "output": "range" } ] }, "majorDimension": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5440c5af875ad6aa", "output": "majorDimension" } ] } }, "name": "Match contacts by Email", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "records" }, { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" }, { "jsonSchema": { "type": "string", "description": "The A1 notation of the range." }, "nullable": false, "handle": "range" }, { "jsonSchema": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "nullable": false, "handle": "majorDimension" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "range": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to write to." }, "values": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data to write into the range." }, "majorDimension": { "type": "string", "enum": [ "ROWS", "COLUMNS" ], "description": "The major dimension of the values array." } }, "required": [ "range", "values" ], "additionalProperties": false, "description": "A batch values update item." }, "minItems": 1, "description": "The list of value ranges to update." }, "nullable": false, "handle": "data" }, { "jsonSchema": { "type": "object", "properties": { "tableRange": { "type": "string", "minLength": 1 } }, "required": [ "tableRange" ] }, "nullable": false, "handle": "plan" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "hasUpdates" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "newRows" } ], "name": "Match contacts by Email", "moduleId": "n_3ca2d7700acc6ee5", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_df35a6e283cf1622": { "inputs": { "plan": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8754ced7a4b20630", "output": "plan" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_df35a6e283cf1622", "output": "state" } ] }, "updatedRange": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c164ffb5ddbeb7c8", "output": "updatedRange" } ] }, "updatedRows": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c164ffb5ddbeb7c8", "output": "updatedRows" } ] }, "updatedCells": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c164ffb5ddbeb7c8", "output": "updatedCells" } ] } }, "name": "Collect new contact receipts", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "plan" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "string", "description": "The A1 notation of the range that was updated." }, "nullable": true, "handle": "updatedRange" }, { "jsonSchema": { "type": "integer", "description": "The number of rows that were updated." }, "nullable": true, "handle": "updatedRows" }, { "jsonSchema": { "type": "integer", "description": "The number of cells that were updated." }, "nullable": true, "handle": "updatedCells" } ], "outputs": [ { "jsonSchema": { "type": "object", "properties": { "inserted": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "inserted" ] }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data to append." }, "nullable": false, "handle": "values" } ], "name": "Collect new contact receipts", "moduleId": "n_c8997f2ec980da52", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_61cc23f827ddff27": { "inputs": {}, "name": "More new contacts", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_df35a6e283cf1622", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_c164ffb5ddbeb7c8": { "inputs": { "spreadsheetId": { "kind": "value", "value": "YOUR_GOOGLE_SHEET_ID" }, "range": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8754ced7a4b20630", "output": "plan", "field": "tableRange" } ] }, "values": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_df35a6e283cf1622", "output": "values" } ] }, "valueInputOption": { "kind": "value", "value": "RAW" } }, "name": "Append one new contact as RAW", "kind": "task", "task": { "inputs": [ { "description": "The spreadsheet ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": true, "handle": "spreadsheetId", "value": null }, { "description": "The A1 notation of the range to append values after.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to append values after." }, "nullable": false, "handle": "range" }, { "description": "The data to append.", "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data to append." }, "nullable": false, "handle": "values" }, { "description": "The major dimension of the values array.", "jsonSchema": { "type": "string", "enum": [ "ROWS", "COLUMNS" ], "description": "The major dimension of the values array." }, "nullable": true, "handle": "majorDimension", "value": null }, { "description": "How the data should be inserted.", "jsonSchema": { "type": "string", "enum": [ "OVERWRITE", "INSERT_ROWS" ], "description": "How the data should be inserted." }, "nullable": true, "handle": "insertDataOption", "value": null }, { "description": "How the input data should be interpreted.", "jsonSchema": { "type": "string", "enum": [ "RAW", "USER_ENTERED" ], "description": "How the input data should be interpreted." }, "nullable": true, "handle": "valueInputOption", "value": null }, { "description": "Whether to include the appended values in the response.", "jsonSchema": { "type": "boolean", "description": "Whether to include the appended values in the response." }, "nullable": true, "handle": "includeValuesInResponse", "value": null }, { "description": "How values in the response should be rendered.", "jsonSchema": { "type": "string", "enum": [ "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" ], "description": "How values in the response should be rendered." }, "nullable": true, "handle": "responseValueRenderOption", "value": null }, { "description": "How dates in the response should be rendered.", "jsonSchema": { "type": "string", "enum": [ "SERIAL_NUMBER", "FORMATTED_STRING" ], "description": "How dates in the response should be rendered." }, "nullable": true, "handle": "responseDateTimeRenderOption", "value": null } ], "outputs": [ { "description": "The unique ID of the spreadsheet.", "jsonSchema": { "type": "string", "description": "The unique ID of the spreadsheet." }, "nullable": false, "handle": "spreadsheetId" }, { "description": "The A1 notation of the range that was updated.", "jsonSchema": { "type": "string", "description": "The A1 notation of the range that was updated." }, "nullable": false, "handle": "updatedRange" }, { "description": "The number of rows that were updated.", "jsonSchema": { "type": "integer", "description": "The number of rows that were updated." }, "nullable": false, "handle": "updatedRows" }, { "description": "The number of columns that were updated.", "jsonSchema": { "type": "integer", "description": "The number of columns that were updated." }, "nullable": false, "handle": "updatedColumns" }, { "description": "The number of cells that were updated.", "jsonSchema": { "type": "integer", "description": "The number of cells that were updated." }, "nullable": false, "handle": "updatedCells" }, { "description": "The values of the cells after the update.", "jsonSchema": { "type": "object", "properties": { "range": { "type": "string", "description": "The A1 notation of the range." }, "majorDimension": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "values": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." } }, "required": [ "range", "majorDimension", "values" ], "additionalProperties": false, "description": "The values of the cells after the update." }, "nullable": true, "handle": "updatedData" }, { "description": "The A1 notation of the table range that data was appended to.", "jsonSchema": { "type": "string", "description": "The A1 notation of the table range that data was appended to." }, "nullable": false, "handle": "tableRange" } ], "name": "Append one new contact as RAW", "executor": { "kind": "connector", "action": "googlesheets.spreadsheets_values_append" } } }, "n_2b06fedfaa7757aa": { "inputs": {}, "name": "Existing cells need updates", "kind": "condition", "matchMode": "first", "cases": [ { "output": "updates", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_8754ced7a4b20630", "output": "hasUpdates" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_aff05f19ba6cd288": { "inputs": { "spreadsheetId": { "kind": "value", "value": "YOUR_GOOGLE_SHEET_ID" }, "data": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8754ced7a4b20630", "output": "data" } ] }, "valueInputOption": { "kind": "value", "value": "RAW" } }, "name": "Update existing contact cells", "kind": "task", "task": { "inputs": [ { "description": "The spreadsheet ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": true, "handle": "spreadsheetId", "value": null }, { "description": "The list of value ranges to update.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "range": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to write to." }, "values": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data to write into the range." }, "majorDimension": { "type": "string", "enum": [ "ROWS", "COLUMNS" ], "description": "The major dimension of the values array." } }, "required": [ "range", "values" ], "additionalProperties": false, "description": "A batch values update item." }, "minItems": 1, "description": "The list of value ranges to update." }, "nullable": false, "handle": "data" }, { "description": "How the input data should be interpreted.", "jsonSchema": { "type": "string", "enum": [ "INPUT_VALUE_OPTION_UNSPECIFIED", "RAW", "USER_ENTERED" ], "description": "How the input data should be interpreted." }, "nullable": true, "handle": "valueInputOption", "value": null }, { "description": "Whether to include the written values in the response.", "jsonSchema": { "type": "boolean", "description": "Whether to include the written values in the response." }, "nullable": true, "handle": "includeValuesInResponse", "value": null }, { "description": "How values in the response should be rendered.", "jsonSchema": { "type": "string", "enum": [ "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" ], "description": "How values in the response should be rendered." }, "nullable": true, "handle": "responseValueRenderOption", "value": null }, { "description": "How dates in the response should be rendered.", "jsonSchema": { "type": "string", "enum": [ "SERIAL_NUMBER", "FORMATTED_STRING" ], "description": "How dates in the response should be rendered." }, "nullable": true, "handle": "responseDateTimeRenderOption", "value": null } ], "outputs": [ { "description": "The unique ID of the spreadsheet.", "jsonSchema": { "type": "string", "description": "The unique ID of the spreadsheet." }, "nullable": false, "handle": "spreadsheetId" }, { "description": "The total number of rows updated across all ranges.", "jsonSchema": { "type": "integer", "description": "The total number of rows updated across all ranges." }, "nullable": false, "handle": "totalUpdatedRows" }, { "description": "The total number of columns updated across all ranges.", "jsonSchema": { "type": "integer", "description": "The total number of columns updated across all ranges." }, "nullable": false, "handle": "totalUpdatedColumns" }, { "description": "The total number of cells updated across all ranges.", "jsonSchema": { "type": "integer", "description": "The total number of cells updated across all ranges." }, "nullable": false, "handle": "totalUpdatedCells" }, { "description": "The per-range update responses.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "updatedRange": { "type": "string", "description": "The A1 notation of the range that was updated." }, "updatedRows": { "type": "integer", "description": "The number of rows that were updated." }, "updatedColumns": { "type": "integer", "description": "The number of columns that were updated." }, "updatedCells": { "type": "integer", "description": "The number of cells that were updated." }, "updatedData": { "type": "object", "properties": { "range": { "type": "string", "description": "The A1 notation of the range." }, "majorDimension": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "values": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." } }, "required": [ "range", "majorDimension", "values" ], "additionalProperties": false, "description": "The values of the cells after the update." } }, "required": [ "updatedRange", "updatedRows", "updatedColumns", "updatedCells" ], "additionalProperties": false, "description": "The batch update item schema." }, "description": "The per-range update responses." }, "nullable": false, "handle": "responses" } ], "name": "Update existing contact cells", "executor": { "kind": "connector", "action": "googlesheets.update_values_batch" } } }, "n_59d010f03b2f4980": { "inputs": { "plan": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8754ced7a4b20630", "output": "plan" } ] }, "inserted": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_df35a6e283cf1622", "output": "state", "field": "inserted" } ] }, "totalUpdatedRows": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_aff05f19ba6cd288", "output": "totalUpdatedRows" } ] }, "totalUpdatedCells": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_aff05f19ba6cd288", "output": "totalUpdatedCells" } ] }, "responses": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_aff05f19ba6cd288", "output": "responses" } ] } }, "name": "Confirm the complete sheet write", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "plan" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "inserted" }, { "jsonSchema": { "type": "integer", "description": "The total number of rows updated across all ranges." }, "nullable": true, "handle": "totalUpdatedRows" }, { "jsonSchema": { "type": "integer", "description": "The total number of cells updated across all ranges." }, "nullable": true, "handle": "totalUpdatedCells" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "updatedRange": { "type": "string", "description": "The A1 notation of the range that was updated." }, "updatedRows": { "type": "integer", "description": "The number of rows that were updated." }, "updatedColumns": { "type": "integer", "description": "The number of columns that were updated." }, "updatedCells": { "type": "integer", "description": "The number of cells that were updated." }, "updatedData": { "type": "object", "properties": { "range": { "type": "string", "description": "The A1 notation of the range." }, "majorDimension": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "values": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." } }, "required": [ "range", "majorDimension", "values" ], "additionalProperties": false, "description": "The values of the cells after the update." } }, "required": [ "updatedRange", "updatedRows", "updatedColumns", "updatedCells" ], "additionalProperties": false, "description": "The batch update item schema." }, "description": "The per-range update responses." }, "nullable": true, "handle": "responses" } ], "outputs": [ { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUpdated" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsInserted" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUnchanged" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "totalRowsProcessed" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "updated" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "inserted" } ], "name": "Confirm the complete sheet write", "moduleId": "n_975069faab48a518", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_5a29f68fb949ff8c": { "inputs": {}, "name": "No recently updated contacts", "kind": "task", "task": { "inputs": [], "outputs": [ { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUpdated" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsInserted" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUnchanged" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "totalRowsProcessed" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "updated" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "inserted" } ], "name": "No recently updated contacts", "moduleId": "n_12fd07102d2ff7f2", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_f6e9dfd36f694b3a": { "name": "Contact sync receipt", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUpdated" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsInserted" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "rowsUnchanged" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "totalRowsProcessed" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "updated" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "inserted" } ], "outputMappings": { "rowsUpdated": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "rowsUpdated" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "rowsUpdated" } ] }, "rowsInserted": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "rowsInserted" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "rowsInserted" } ] }, "rowsUnchanged": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "rowsUnchanged" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "rowsUnchanged" } ] }, "totalRowsProcessed": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "totalRowsProcessed" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "totalRowsProcessed" } ] }, "updated": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "updated" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "updated" } ] }, "inserted": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59d010f03b2f4980", "output": "inserted" }, { "kind": "node", "nodeId": "n_5a29f68fb949ff8c", "output": "inserted" } ] } } } }, "edges": [ { "source": "n_fb78eddfc609d08b", "target": "n_4a612d9d09d5a555" }, { "source": "n_4a612d9d09d5a555", "target": "n_b069f8e637a95628" }, { "source": "n_b069f8e637a95628", "target": "n_076c3aa01d97cc06", "sourceHandle": "more" }, { "source": "n_076c3aa01d97cc06", "target": "n_4a612d9d09d5a555" }, { "source": "n_b069f8e637a95628", "target": "n_b50472b48bdded68", "sourceHandle": "otherwise" }, { "source": "n_b50472b48bdded68", "target": "n_5a29f68fb949ff8c", "sourceHandle": "empty" }, { "source": "n_b50472b48bdded68", "target": "n_5440c5af875ad6aa", "sourceHandle": "otherwise" }, { "source": "n_5440c5af875ad6aa", "target": "n_8754ced7a4b20630" }, { "source": "n_8754ced7a4b20630", "target": "n_df35a6e283cf1622" }, { "source": "n_df35a6e283cf1622", "target": "n_61cc23f827ddff27" }, { "source": "n_61cc23f827ddff27", "target": "n_c164ffb5ddbeb7c8", "sourceHandle": "more" }, { "source": "n_c164ffb5ddbeb7c8", "target": "n_df35a6e283cf1622" }, { "source": "n_61cc23f827ddff27", "target": "n_2b06fedfaa7757aa", "sourceHandle": "otherwise" }, { "source": "n_2b06fedfaa7757aa", "target": "n_aff05f19ba6cd288", "sourceHandle": "updates" }, { "source": "n_2b06fedfaa7757aa", "target": "n_59d010f03b2f4980", "sourceHandle": "otherwise" }, { "source": "n_aff05f19ba6cd288", "target": "n_59d010f03b2f4980" }, { "source": "n_59d010f03b2f4980", "target": "n_f6e9dfd36f694b3a" }, { "source": "n_5a29f68fb949ff8c", "target": "n_f6e9dfd36f694b3a" } ] } }, "modules": { "n_6e708ae1aa2dbf66": { "name": "Collect every Airtable page", "imports": [], "source": "export default function collectAirtableRows({previousState,records,offset}) {\n const state=previousState?{records:[...previousState.records],cursors:[...previousState.cursors]}:{records:[],cursors:[]};\n if(previousState){if(!Array.isArray(records))throw new Error('Airtable returned no record array.');for(const record of records){if(!record||typeof record.id!=='string'||!record.id||!record.fields||typeof record.fields!=='object'||Array.isArray(record.fields))throw new Error('Invalid Airtable record.');state.records.push(record);}if(offset!==undefined&&offset!==null&&(typeof offset!=='string'||!offset))throw new Error('Invalid Airtable cursor.');if(offset){if(state.cursors.includes(offset))throw new Error('Airtable repeated its pagination cursor.');state.cursors.push(offset);}}\n const more=!previousState||!!offset;return {state,offset:more?(offset??null):null,more,hasRecords:state.records.length>0};\n}\n" }, "n_3ca2d7700acc6ee5": { "name": "Match contacts by Email", "imports": [], "source": "export default function planSheetSync({records,values,range,majorDimension}) {\n if(!Array.isArray(records)||!records.length)throw new Error('At least one selected Airtable record is required.');\n if(majorDimension!=='ROWS'||!Array.isArray(values)||!Array.isArray(values[0]))throw new Error('A sheet header row is required.');\n const headers=values[0];if(!headers.length||headers.length>5||headers.some(h=>typeof h!=='string'||!h)||new Set(headers).size!==headers.length||!headers.includes('Email'))throw new Error('The A1:E1 headers must be unique text field names including Email, with no gaps.');\n if(typeof range!=='string'||range.lastIndexOf('!')<1||range.slice(range.lastIndexOf('!')+1)!=='A1:E1000')throw new Error('The sheet response must identify the requested A1:E1000 lookup window.');\n const prefix=range.slice(0,range.lastIndexOf('!')+1),keyIndex=headers.indexOf('Email'),existing=new Map(),sourceKeys=new Set(),data=[],newRows=[],matched=[],unchanged=[];\n if(values.length>1000)throw new Error('The sheet response exceeds the configured lookup window.');\n for(let i=1;i5)throw new Error('Invalid sheet row.');const key=row[keyIndex];if(key!==null&&key!==undefined&&key!==''){if(typeof key!=='string')throw new Error('Existing Email keys must be text.');if(existing.has(key))throw new Error('Duplicate Email keys exist in the target lookup window.');existing.set(key,i+1);}}\n for(const record of records){const fields=record?.fields,key=fields?.Email;if(!record?.id||!fields||typeof fields!=='object'||Array.isArray(fields)||typeof key!=='string'||!key.trim())throw new Error('Every selected record needs a nonempty text Email key.');if(sourceKeys.has(key))throw new Error('Duplicate Email keys exist in the source batch.');sourceKeys.add(key);\n for(const h of headers){const v=fields[h];if(v!==undefined&&v!==null&&!['string','number','boolean'].includes(typeof v))throw new Error('Mapped Airtable cells must be text,numbers,booleans or null.');if(typeof v==='number'&&!Number.isFinite(v))throw new Error('Mapped numbers must be finite.');}\n const rowNumber=existing.get(key);if(rowNumber){const before=data.length;headers.forEach((h,j)=>{if(h!=='Email'&&fields[h]!==undefined&&fields[h]!==null)data.push({range:prefix+String.fromCharCode(65+j)+rowNumber,majorDimension:'ROWS',values:[[fields[h]]]});});(data.length>before?matched:unchanged).push({recordId:record.id,email:key,row:rowNumber});}\n else newRows.push({recordId:record.id,email:key,values:headers.map(h=>fields[h]===undefined||fields[h]===null?'':String(fields[h]))});\n }\n return {data:data.length?data:[{range:prefix+'A1',values:[['UNUSED']],majorDimension:'ROWS'}],newRows,hasUpdates:data.length>0,plan:{matched,unchanged,count:records.length,ranges:data.map(d=>d.range),cells:data.length,newRows,tableRange:range,width:headers.length}};\n}\n" }, "n_c8997f2ec980da52": { "name": "Collect new contact receipts", "imports": [], "source": "export default function collectContactAppends({plan,previousState,updatedRange,updatedRows,updatedCells}) {\n const state=previousState?{index:previousState.index,inserted:[...previousState.inserted]}:{index:0,inserted:[]};\n if(previousState){const expected=plan.newRows[state.index],prefix=plan.tableRange.slice(0,plan.tableRange.lastIndexOf('!')+1),suffix=typeof updatedRange==='string'&&updatedRange.startsWith(prefix)?updatedRange.slice(prefix.length):'',match=suffix.match(/^A([0-9]+)(?::([A-E])([0-9]+))?$/);if(updatedRows!==1||updatedCells!==plan.width||!match||match[1]!==(match[3]??match[1])||(match[2]??'A')!==String.fromCharCode(64+plan.width))throw new Error('Append receipt does not confirm the new contact row. Inspect the sheet before retrying.');state.inserted.push({recordId:expected.recordId,email:expected.email,range:updatedRange,row:Number(match[1])});state.index++;}\n const next=plan.newRows[state.index];return {state,more:!!next,values:next?[next.values]:[]};\n}\n" }, "n_975069faab48a518": { "name": "Confirm the complete sheet write", "imports": [], "source": "export default function confirmSheetSync({plan,inserted,totalUpdatedRows,totalUpdatedCells,responses}) {\n if(inserted.length!==plan.newRows.length)throw new Error('All new contact appends must complete before updating existing rows.');\n if(plan.cells){if(totalUpdatedRows!==plan.matched.length||totalUpdatedCells!==plan.cells||!Array.isArray(responses)||responses.length!==plan.cells)throw new Error('The update did not confirm every planned cell. Inspect the sheet before retrying.');\n const canonical=r=>String(r).replace(/([A-Z]+\\d+):\\1$/,'$1');for(let i=0;i