For Zoho services only


I'm currently part of a wider delivery team at Ascent Business Solutions, recognised as a leading Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions support organisations with everything from targeted technical fixes through to full Zoho CRM implementations and long-term platform adoption. Working as a team rather than a one-person consultancy allows projects to move forward consistently, with access to the right skills at each stage.

The team I manage specialises in API integrations between Zoho and third-party finance and commerce platforms such as Xero, Shopify, WooCommerce, and eBay. Much of our work involves solving integration challenges that fall outside standard documentation, supporting new ideas, new sectors, and evolving business models.

Success is measured through practical outcomes and return on investment, ranging from scaling small operations into high-turnover businesses to delivering rapid gains through online payments, automation, and streamlined digital workflows.

If you are looking for structured Zoho expertise backed by an established consultancy, you can contact Ascent Business Solutions on 0121 392 8140 (UK), email info@ascentbusiness.co.uk, or visit https://www.ascentbusiness.co.uk.
Zoho People: Get Performance Records over API

Zoho People: Get Performance Records over API

What?
Took us a while to find this and perhaps others would have a quicker way but here's the instructions on getting the records from the performance module in Zoho People API.

Why?
Cos it took us a while. The online forums seem to go back over a decade and the documentation seems to have gaps; or simply modules are so custom/bespoke the documentation has started to genericize.

My client has seen the Goals or Objectives report under "Organization Reports" but would like some additional fields/columns added to it. Turns out, it's a system report you can't change. Enter Zoho Analytics... well almost, Analytics will sync with Zoho People but I wasn't able to select the performance modules (Client Review and Placement Tech Survey having been disabled)...

How?
So this might seem obvious but after trying to scan the meta data (snippets at the bottom of this article), the simplest way was right in front of us. We're going to use the interface to find the form where staff have been entering employees goals, then we're going to query it and push it to Zoho Analytics.

Find the form link name:
Putting this here as it should be what we first did! This is with reference to the Zoho People version 5:
  1. Login to Zoho People as an administrator (don't have to be super admin)
  2. Go to Settings (cog icon in the top right)
  3. Click on "Performance"
  4. Click on "Extend Service"
  5. Click on the form you want the data from, in our case "Objectives" (renamed from "Goals")
  6. Note the label name in the top right:
In our case, the form link name is P_Goals which doesn't feature under the developer space > Zoho People API...

The Zoho People function to retrieve this data:
copyraw
/* *******************************************************************************
	Function:       String fn_PushPerformanceToAnalytics(int p_RecordID)
	Label:          Fn - Push Performance Data to Analytics
	Trigger:        Workflow when an objective is created or edited.
	Purpose:		Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns.
	Inputs:         int p_RecordID
	Outputs:        -

	Date Created:   2025-09-18 (Joel Lipman)
					- Initial release
	Date Modified:	???
					- ???
	******************************************************************************* */
v_CountTotal = 0;
m_OutputAll = Map();
v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getRecords";
r_GoalsData = invokeurl
[
	url :v_Endpoint_Goals
	type :GET
	connection:"people_cf"
];
m_GoalsResponse = ifnull(r_GoalsData.get("response"), Map());
l_GoalRecords = ifnull(m_GoalsResponse.get("result"), List());
for each m_GoalRecord in l_GoalRecords
{
	v_CountTotal = v_CountTotal + 1;
	l_GoalKeys = m_GoalRecord.keys();
	if(l_GoalKeys.size()>0)
	{
		v_GoalKey = l_GoalKeys.get(0);
		//info "Record ID: " + v_GoalKey;
		//
		m_ThisGoal = m_GoalRecord.get(v_GoalKey).get(0).toMap();
		//info "Goal Record Data: " + m_ThisGoal;
		if(!isNull(m_ThisGoal.get("Zoho_ID")))
		{
			m_OutputRecord = Map();
			m_OutputRecord.put("Record Index", v_CountTotal);
			for each v_Field in m_ThisGoal.keys()
            {
				m_OutputRecord.put(v_Field, m_ThisGoal.get(v_Field));
				//info "Field: " + v_Field + " || Value: " + m_ThisGoal.get(v_Field);
            }
			m_OutputAll.put(v_GoalKey, m_OutputRecord);
		}
	}
	// 
	// for testing I'm doing 3 records at a time
	if(v_CountTotal > 3)
	{
		break;
	}
}
info m_OutputAll;
return "";
  1.  /* ******************************************************************************* 
  2.      Function:       String fn_PushPerformanceToAnalytics(int p_RecordID) 
  3.      Label:          Fn - Push Performance Data to Analytics 
  4.      Trigger:        Workflow when an objective is created or edited. 
  5.      Purpose:        Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns. 
  6.      Inputs:         int p_RecordID 
  7.      Outputs:        - 
  8.   
  9.      Date Created:   2025-09-18 (Joel Lipman) 
  10.                      - Initial release 
  11.      Date Modified:    ??? 
  12.                      - ??? 
  13.      ******************************************************************************* */ 
  14.  v_CountTotal = 0
  15.  m_OutputAll = Map()
  16.  v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getRecords"
  17.  r_GoalsData = invokeurl 
  18.  [ 
  19.      url :v_Endpoint_Goals 
  20.      type :GET 
  21.      connection:"people_cf" 
  22.  ]
  23.  m_GoalsResponse = ifnull(r_GoalsData.get("response"), Map())
  24.  l_GoalRecords = ifnull(m_GoalsResponse.get("result"), List())
  25.  for each m_GoalRecord in l_GoalRecords 
  26.  { 
  27.      v_CountTotal = v_CountTotal + 1
  28.      l_GoalKeys = m_GoalRecord.keys()
  29.      if(l_GoalKeys.size()>0) 
  30.      { 
  31.          v_GoalKey = l_GoalKeys.get(0)
  32.          //info "Record ID: " + v_GoalKey; 
  33.          // 
  34.          m_ThisGoal = m_GoalRecord.get(v_GoalKey).get(0).toMap()
  35.          //info "Goal Record Data: " + m_ThisGoal; 
  36.          if(!isNull(m_ThisGoal.get("Zoho_ID"))) 
  37.          { 
  38.              m_OutputRecord = Map()
  39.              m_OutputRecord.put("Record Index", v_CountTotal)
  40.              for each v_Field in m_ThisGoal.keys() 
  41.              { 
  42.                  m_OutputRecord.put(v_Field, m_ThisGoal.get(v_Field))
  43.                  //info "Field: " + v_Field + " || Value: " + m_ThisGoal.get(v_Field)
  44.              } 
  45.              m_OutputAll.put(v_GoalKey, m_OutputRecord)
  46.          } 
  47.      } 
  48.      // 
  49.      // for testing I'm doing 3 records at a time 
  50.      if(v_CountTotal > 3) 
  51.      { 
  52.          break; 
  53.      } 
  54.  } 
  55.  info m_OutputAll; 
  56.  return ""
This should give you all the fields that will be pulled from "Goals" and some records with data.

Create the Zoho Analytics table
  1. Create a blank Excel file / spreadsheet / csv
  2. Copy the names of fields you want to store (I'm taking all of it), and paste them into the header of the spreadsheet with each one occupying one cell.
  3. Enter a description in the 2nd row to convince Analytics there is some data in the table we are going to import. You should end up with something like:
  4. I'm going to save this file as Zoho_Analytics_Performance_Goals.xlsx
  5. Login to Zoho Analytics
  6. Select "Data Sources" then Add New Data Source
  7. Select "Files" (has the icon of MS Excel on it)
  8. Give it a table name. I've gone with Performance Objectives / Goals
  9. File Type I said was Excel and then clicked on "Choose File" (with data location set to "Local Drive")
  10. Click on Next have a quick glance of the preview and then click on Next
  11. First row contains column names = Yes, leave everything else as plain text except for Description which you should change to a Multi Line Text.
  12. Click on Create. You should end up with something like
A few more things to configure
  1. Get the ZohoAnalytics Org ID:
    1. Login to ZohoAnalytics and ensure you are at the "Home" level
    2. Click on the cog icon in the top-right for "Settings"
    3. You should be in the "Organization Settings"
    4. Your Org ID is in the URL as the last number after the slash /org-details/###### (eg. "123456789")
    5. Note this down for later use
  2. Create a connection to Zoho Analytics:
    1. Above the function in Zoho People > Connections > Create Connection
    2. Select Zoho Analytics and give the required scopes (I've cheated and gone for ZohoAnalytics.fullaccess.all)
    3. Note the connection link name (mine is called "zanalytics")

The Zoho People function modified to pull the Goals data and push it into Zoho Analytics:
copyraw
/* *******************************************************************************
Function:       String fn_PushPerformanceToAnalytics(int p_RecordID)
Label:          Fn - Push Performance Data to Analytics
Trigger:        Workflow when an objective is created or edited.
Purpose:		Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns.
Inputs:         int p_RecordID
Outputs:        -

Date Created:   2025-09-18 (Joel Lipman)
				- Initial release
Date Modified:	???
				- ???
******************************************************************************* */
//
// init
v_CountTotal = 0;
// 
// the zoho analytics org ID noted earlier 
v_AnalyticsOrgID = "123456879"; 
//
// query goals endpoint
v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getRecords";
r_GoalsData = invokeurl
[
	url :v_Endpoint_Goals
	type :GET
	connection:"zpeople"
];
//
// parse the response loop through each record returned
m_GoalsResponse = ifnull(r_GoalsData.get("response"),Map());
l_GoalRecords = ifnull(m_GoalsResponse.get("result"),List());
for each  m_GoalRecord in l_GoalRecords
{
	//
	// start an increment for counting the records processed
	v_CountTotal = v_CountTotal + 1;
	//
	// for the response structure, we need to extrat the first key as the Zoho ID
	l_GoalKeys = m_GoalRecord.keys();
	if(l_GoalKeys.size() > 0)
	{
		//
		// messy but this is the key to the record in the list - happens to be the Zoho ID as well
		v_GoalKey = l_GoalKeys.get(0);
		//info "Record ID: " + v_GoalKey;
		//
		m_ThisGoal = m_GoalRecord.get(v_GoalKey).get(0).toMap();
		//info "Goal Record Data: " + m_ThisGoal;
		//
		if(!isNull(m_ThisGoal.get("Zoho_ID")))
		{
			m_OutputRecord = Map();
			//
			// obtain each field in this record and create our own map...
			// in this case, my table exactly matches the keys so this isn't really necessary but just in case I wanted to filter some fields out...
			for each  v_Field in m_ThisGoal.keys()
			{
				m_OutputRecord.put(v_Field,m_ThisGoal.get(v_Field).toString());
				//info "Field: " + v_Field + " || Value: " + m_ThisGoal.get(v_Field);
			}
			// 
			// Add this row to Analytics 
			m_Header = Map(); 
			m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID); 
			m_Columns = Map(); 
			m_Columns.put("columns", m_OutputRecord); 
			m_Params = Map(); 
			m_Params.put("CONFIG", m_Columns.toString()); 
			info m_OutputRecord;
			// 
			// in analytics, browse to the target table and note the URL IDs after workspace and view 
			v_WorkspaceID = "1234567000008912345"; 
			v_TableID = "9876543000002198765"; 
			v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/"+v_WorkspaceID+"/views/"+v_TableID+"/rows"; 
			info v_Endpoint2;
			//
			r_AddRow = invokeUrl 
			[ 
				 url: v_Endpoint2 
				 type: POST 
				 parameters: m_Params
				 headers: m_Header 
				 connection: "zanalytics" 
			]; 
			info r_AddRow; 			
		}
	}
	//
	// used while debugging / remove once in production
	if(v_CountTotal >= 3)
	{
		break;
	}
}
return "Processed " + v_CountTotal + " record(s)";
  1.  /* ******************************************************************************* 
  2.  Function:       String fn_PushPerformanceToAnalytics(int p_RecordID) 
  3.  Label:          Fn - Push Performance Data to Analytics 
  4.  Trigger:        Workflow when an objective is created or edited. 
  5.  Purpose:        Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns. 
  6.  Inputs:         int p_RecordID 
  7.  Outputs:        - 
  8.   
  9.  Date Created:   2025-09-18 (Joel Lipman) 
  10.                  - Initial release 
  11.  Date Modified:    ??? 
  12.                  - ??? 
  13.  ******************************************************************************* */ 
  14.  // 
  15.  // init 
  16.  v_CountTotal = 0
  17.  // 
  18.  // the zoho analytics org ID noted earlier 
  19.  v_AnalyticsOrgID = "123456879"
  20.  // 
  21.  // query goals endpoint 
  22.  v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getRecords"
  23.  r_GoalsData = invokeurl 
  24.  [ 
  25.      url :v_Endpoint_Goals 
  26.      type :GET 
  27.      connection:"zpeople" 
  28.  ]
  29.  // 
  30.  // parse the response loop through each record returned 
  31.  m_GoalsResponse = ifnull(r_GoalsData.get("response"),Map())
  32.  l_GoalRecords = ifnull(m_GoalsResponse.get("result"),List())
  33.  for each  m_GoalRecord in l_GoalRecords 
  34.  { 
  35.      // 
  36.      // start an increment for counting the records processed 
  37.      v_CountTotal = v_CountTotal + 1
  38.      // 
  39.      // for the response structure, we need to extrat the first key as the Zoho ID 
  40.      l_GoalKeys = m_GoalRecord.keys()
  41.      if(l_GoalKeys.size() > 0) 
  42.      { 
  43.          // 
  44.          // messy but this is the key to the record in the list - happens to be the Zoho ID as well 
  45.          v_GoalKey = l_GoalKeys.get(0)
  46.          //info "Record ID: " + v_GoalKey; 
  47.          // 
  48.          m_ThisGoal = m_GoalRecord.get(v_GoalKey).get(0).toMap()
  49.          //info "Goal Record Data: " + m_ThisGoal; 
  50.          // 
  51.          if(!isNull(m_ThisGoal.get("Zoho_ID"))) 
  52.          { 
  53.              m_OutputRecord = Map()
  54.              // 
  55.              // obtain each field in this record and create our own map... 
  56.              // in this case, my table exactly matches the keys so this isn't really necessary but just in case I wanted to filter some fields out... 
  57.              for each  v_Field in m_ThisGoal.keys() 
  58.              { 
  59.                  m_OutputRecord.put(v_Field,m_ThisGoal.get(v_Field).toString())
  60.                  //info "Field: " + v_Field + " || Value: " + m_ThisGoal.get(v_Field)
  61.              } 
  62.              // 
  63.              // Add this row to Analytics 
  64.              m_Header = Map()
  65.              m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID)
  66.              m_Columns = Map()
  67.              m_Columns.put("columns", m_OutputRecord)
  68.              m_Params = Map()
  69.              m_Params.put("CONFIG", m_Columns.toString())
  70.              info m_OutputRecord; 
  71.              // 
  72.              // in analytics, browse to the target table and note the URL IDs after workspace and view 
  73.              v_WorkspaceID = "1234567000008912345"
  74.              v_TableID = "9876543000002198765"
  75.              v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/"+v_WorkspaceID+"/views/"+v_TableID+"/rows"
  76.              info v_Endpoint2; 
  77.              // 
  78.              r_AddRow = invokeUrl 
  79.              [ 
  80.                   url: v_Endpoint2 
  81.                   type: POST 
  82.                   parameters: m_Params 
  83.                   headers: m_Header 
  84.                   connection: "zanalytics" 
  85.              ]
  86.              info r_AddRow; 
  87.          } 
  88.      } 
  89.      // 
  90.      // used while debugging / remove once in production 
  91.      if(v_CountTotal >= 3) 
  92.      { 
  93.          break; 
  94.      } 
  95.  } 
  96.  return "Processed " + v_CountTotal + record(s)"

Important Note: The above has a caveat in that it pushes the data from Zoho People to Zoho Analytics. It doesn't check to see if the data is already there, it doesn't update, it just keeps adding rows. Modify the function above (the one hosted in ZohoPeople) to account for this. It's the end of the day and I will do that tomorrow but there is only so much fun one can have in a day.

The next day: Add to a workflow when objective / goal is submitted
Here's the code that I added to the workflow for when a performance goal is created or modified (this will upsert it based on the Goal Record ID):
copyraw
//
/* *******************************************************************************
Function:       String fn_PushObjectivesToAnalytics(int p_RecordID)
Label:          Fn - Push Objective Data to Analytics
Trigger:        Workflow when an objective is created or edited.
Purpose:		Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns.
Inputs:         int p_RecordID
Outputs:        -

Date Created:   2025-09-23 (Joel Lipman)
- Initial release
Date Modified:	???
- ???
******************************************************************************* */
// 
// the zoho analytics org ID noted earlier 
v_AnalyticsOrgID = "123456879"; 
//
// query goals endpoint
v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getDataByID?recordId=" + p_RecordID;
r_GoalsData = invokeurl
	[
	url :v_Endpoint_Goals
	type :GET
	connection:"zpeople"
];
//info r_GoalsData;
//
// parse the response loop through each record returned
m_GoalsResponse = ifnull(r_GoalsData.get("response"),Map());
l_GoalResults = ifnull(m_GoalsResponse.get("result"),List());
for each  m_GoalRecord in l_GoalResults
{
	//
	// check this is a valid record and not an error from the search
	if(!isNull(m_GoalRecord.get("Progress")))
	{
		m_OutputRecord = Map();
		//
		// obtain each field in this record and create our own map...
		m_OutputRecord.put("Progress.displayValue",m_GoalRecord.get("Progress.displayValue"));
		m_OutputRecord.put("Description",m_GoalRecord.get("Description"));
		m_OutputRecord.put("AssignedTo.ID",m_GoalRecord.get("AssignedTo.ID"));
		m_OutputRecord.put("Priority",m_GoalRecord.get("Priority"));
		m_OutputRecord.put("Priority.id",m_GoalRecord.get("Priority.id"));
		m_OutputRecord.put("ApprovalStatus",m_GoalRecord.get("ApprovalStatus"));
		m_OutputRecord.put("AssignedBy.ID",m_GoalRecord.get("AssignedBy.ID"));
		m_OutputRecord.put("StartDate",m_GoalRecord.get("StartDate"));
		//m_OutputRecord.put("IsArchived",m_GoalRecord.get("IsArchived"));
		m_OutputRecord.put("GoalName",m_GoalRecord.get("GoalName"));
		m_OutputRecord.put("componentValueDisp",m_GoalRecord.get("componentValueDisp"));
		m_OutputRecord.put("DueDate",m_GoalRecord.get("DueDate"));
		//
		// primary key
		m_OutputRecord.put("ObjectiveRecordID",p_RecordID);
		// 
		// Add this row to Analytics 
		m_Header = Map();
		m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID);
		m_Columns = Map();
		m_Columns.put("columns",m_OutputRecord);
		m_Columns.put("criteria","\"Performance Objectives / Goals\".\"ObjectiveRecordID\"='" + p_RecordID + "'");
		m_Columns.put("addIfNotExist",true);
		m_Params = Map();
		m_Params.put("CONFIG",m_Columns);
		//info m_Columns;
		// 
		// in analytics, browse to the target table and note the URL IDs after workspace and view 
		v_WorkspaceID = "1234567000008912345"; 
		v_TableID = "9876543000002198765"; 
		v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/" + v_WorkspaceID + "/views/" + v_TableID + "/rows";
		//info v_Endpoint2;
		//
		r_AddRow = invokeurl
		[
			url :v_Endpoint2
			type :PUT
			parameters:m_Params
			headers:m_Header
			connection:"zanalytics"
		];
		info r_AddRow;
	}
}
  1.  // 
  2.  /* ******************************************************************************* 
  3.  Function:       String fn_PushObjectivesToAnalytics(int p_RecordID) 
  4.  Label:          Fn - Push Objective Data to Analytics 
  5.  Trigger:        Workflow when an objective is created or edited. 
  6.  Purpose:        Pushes the data and columns we need to Zoho Analytics to produce an Objectives report with the requested columns. 
  7.  Inputs:         int p_RecordID 
  8.  Outputs:        - 
  9.   
  10.  Date Created:   2025-09-23 (Joel Lipman) 
  11.  - Initial release 
  12.  Date Modified:    ??? 
  13.  - ??? 
  14.  ******************************************************************************* */ 
  15.  // 
  16.  // the zoho analytics org ID noted earlier 
  17.  v_AnalyticsOrgID = "123456879"
  18.  // 
  19.  // query goals endpoint 
  20.  v_Endpoint_Goals = "https://people.zoho.com/api/forms/P_Goals/getDataByID?recordId=" + p_RecordID; 
  21.  r_GoalsData = invokeurl 
  22.      [ 
  23.      url :v_Endpoint_Goals 
  24.      type :GET 
  25.      connection:"zpeople" 
  26.  ]
  27.  //info r_GoalsData; 
  28.  // 
  29.  // parse the response loop through each record returned 
  30.  m_GoalsResponse = ifnull(r_GoalsData.get("response"),Map())
  31.  l_GoalResults = ifnull(m_GoalsResponse.get("result"),List())
  32.  for each  m_GoalRecord in l_GoalResults 
  33.  { 
  34.      // 
  35.      // check this is a valid record and not an error from the search 
  36.      if(!isNull(m_GoalRecord.get("Progress"))) 
  37.      { 
  38.          m_OutputRecord = Map()
  39.          // 
  40.          // obtain each field in this record and create our own map... 
  41.          m_OutputRecord.put("Progress.displayValue",m_GoalRecord.get("Progress.displayValue"))
  42.          m_OutputRecord.put("Description",m_GoalRecord.get("Description"))
  43.          m_OutputRecord.put("AssignedTo.ID",m_GoalRecord.get("AssignedTo.ID"))
  44.          m_OutputRecord.put("Priority",m_GoalRecord.get("Priority"))
  45.          m_OutputRecord.put("Priority.id",m_GoalRecord.get("Priority.id"))
  46.          m_OutputRecord.put("ApprovalStatus",m_GoalRecord.get("ApprovalStatus"))
  47.          m_OutputRecord.put("AssignedBy.ID",m_GoalRecord.get("AssignedBy.ID"))
  48.          m_OutputRecord.put("StartDate",m_GoalRecord.get("StartDate"))
  49.          //m_OutputRecord.put("IsArchived",m_GoalRecord.get("IsArchived"))
  50.          m_OutputRecord.put("GoalName",m_GoalRecord.get("GoalName"))
  51.          m_OutputRecord.put("componentValueDisp",m_GoalRecord.get("componentValueDisp"))
  52.          m_OutputRecord.put("DueDate",m_GoalRecord.get("DueDate"))
  53.          // 
  54.          // primary key 
  55.          m_OutputRecord.put("ObjectiveRecordID",p_RecordID)
  56.          // 
  57.          // Add this row to Analytics 
  58.          m_Header = Map()
  59.          m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID)
  60.          m_Columns = Map()
  61.          m_Columns.put("columns",m_OutputRecord)
  62.          m_Columns.put("criteria","\"Performance Objectives / Goals\".\"ObjectiveRecordID\"='" + p_RecordID + "'")
  63.          m_Columns.put("addIfNotExist",true)
  64.          m_Params = Map()
  65.          m_Params.put("CONFIG",m_Columns)
  66.          //info m_Columns; 
  67.          // 
  68.          // in analytics, browse to the target table and note the URL IDs after workspace and view 
  69.          v_WorkspaceID = "1234567000008912345"
  70.          v_TableID = "9876543000002198765"
  71.          v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/" + v_WorkspaceID + "/views/" + v_TableID + "/rows"
  72.          //info v_Endpoint2; 
  73.          // 
  74.          r_AddRow = invokeurl 
  75.          [ 
  76.              url :v_Endpoint2 
  77.              type :PUT 
  78.              parameters:m_Params 
  79.              headers:m_Header 
  80.              connection:"zanalytics" 
  81.          ]
  82.          info r_AddRow; 
  83.      } 
  84.  } 

And of course the deleting workflow
There may be some users who want to tidy up the system, as innocently as deleting test records... or just being a pain:
copyraw
/* *******************************************************************************
Function:       String fn_DeleteObjectiveFromAnalytics(int p_RecordID)
Label:          Fn - Delete Objective Data from Analytics
Trigger:        Workflow when an objective is deleted.
Purpose:		Removes the objective row from Analytics based on ObjectiveRecordID.
Inputs:         int p_RecordID
Outputs:        -

Date Created:   2025-09-23 (Joel Lipman)
- Initial release
Date Modified:	???
- ???
******************************************************************************* */
//
// the zoho analytics org ID noted earlier 
v_AnalyticsOrgID = "123456879"; 
// 
// Build the request for Analytics 
m_Header = Map();
m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID);
m_Columns = Map();
m_Columns.put("criteria","\"Performance Objectives / Goals\".\"ObjectiveRecordID\"='" + p_RecordID + "'");
m_Params = Map();
m_Params.put("CONFIG",m_Columns);
//info m_Columns;
// 
// in analytics, browse to the target table and note the URL IDs after workspace and view 
v_WorkspaceID = "1234567000008912345"; 
v_TableID = "9876543000002198765"; 
v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/" + v_WorkspaceID + "/views/" + v_TableID + "/rows";
//info v_Endpoint2;
//
r_DelRow = invokeurl
[
	url :v_Endpoint2
	type :DELETE
	parameters:m_Params
	headers:m_Header
	connection:"ab_analytics"
];
info r_DelRow;
  1.  /* ******************************************************************************* 
  2.  Function:       String fn_DeleteObjectiveFromAnalytics(int p_RecordID) 
  3.  Label:          Fn - Delete Objective Data from Analytics 
  4.  Trigger:        Workflow when an objective is deleted. 
  5.  Purpose:        Removes the objective row from Analytics based on ObjectiveRecordID. 
  6.  Inputs:         int p_RecordID 
  7.  Outputs:        - 
  8.   
  9.  Date Created:   2025-09-23 (Joel Lipman) 
  10.  - Initial release 
  11.  Date Modified:    ??? 
  12.  - ??? 
  13.  ******************************************************************************* */ 
  14.  // 
  15.  // the zoho analytics org ID noted earlier 
  16.  v_AnalyticsOrgID = "123456879"
  17.  // 
  18.  // Build the request for Analytics 
  19.  m_Header = Map()
  20.  m_Header.put("ZANALYTICS-ORGID",v_AnalyticsOrgID)
  21.  m_Columns = Map()
  22.  m_Columns.put("criteria","\"Performance Objectives / Goals\".\"ObjectiveRecordID\"='" + p_RecordID + "'")
  23.  m_Params = Map()
  24.  m_Params.put("CONFIG",m_Columns)
  25.  //info m_Columns; 
  26.  // 
  27.  // in analytics, browse to the target table and note the URL IDs after workspace and view 
  28.  v_WorkspaceID = "1234567000008912345"
  29.  v_TableID = "9876543000002198765"
  30.  v_Endpoint2 = "https://analyticsapi.zoho.com/restapi/v2/workspaces/" + v_WorkspaceID + "/views/" + v_TableID + "/rows"
  31.  //info v_Endpoint2; 
  32.  // 
  33.  r_DelRow = invokeurl 
  34.  [ 
  35.      url :v_Endpoint2 
  36.      type :DELETE 
  37.      parameters:m_Params 
  38.      headers:m_Header 
  39.      connection:"ab_analytics" 
  40.  ]
  41.  info r_DelRow; 


Error(s) Encountered
  • {"status":"failure","summary":"LESS_THAN_MIN_OCCURANCE","data":{"errorCode":8504,"errorMessage":"The parameter CONFIG is not proper(Has not been sent or is less than required count)"}} Crazy! sometimes the invokeUrl wants its parameters changed using .toString()... sometimes it doesn't, as it did in this case.
  • EXTRA_KEY_FOUND_IN_JSON {"errorCode":8542,"errorMessage":"Unknown JSON attribute(s) found in the parameter CONFIG."}: This was because I was trying to include the parameter addIfNotExist and I was using POST as the method in the invokeURL instead of PUT.
  • INVALID_UPDATE_CRITERIA_CONFIGURATION","data":{"errorCode":8130,"errorMessage":"Invalid criteria configuration. Kindly provide valid criteria or use 'updateAllRows' attribute as 'true' to update all the rows." Need to include the criteria along with the columns and the "addIfNotExist" attribute

Additional / Optional stuff to do but please don't waste your time.

Out of sheer boredom: fn_GetMetaData_FormsFields
copyraw
v_NewLine = hexToText("0A");
l_CsvRows = List();
v_FormsEndpoint = "https://people.zoho.com/api/forms";
r_Forms = invokeurl
[
url :v_FormsEndpoint
type :GET
connection:"my_people_connection"
];
m_Response = ifnull(r_Forms.get("response"),Map());
l_Forms = ifnull(m_Response.get("result"),List());
for each  m_Form in l_Forms
{
if(!isNull(m_Form.get("formLinkName")))
{
	v_FormName = m_Form.get("displayName");
	v_FieldsEndpoint = "https://people.zoho.com/people/api/forms/" + m_Form.get("formLinkName") + "/components";
	r_Fields = invokeurl
	[
		url :v_FieldsEndpoint
		type :GET
		connection:"my_people_connection"
	];
	m_FieldsResponse = ifnull(r_Fields.get("response"),Map());
	l_Fields = ifnull(m_FieldsResponse.get("result"),List());
	for each  m_Field in l_Fields
	{
		if(!isNull(m_Field.get("comptype")))
		{
			l_CsvRow = List();
			l_CsvRow.add(v_FormName);
			l_CsvRow.add(m_Form.get("formLinkName"));
			l_CsvRow.add(m_Field.get("displayname"));
			l_CsvRow.add(m_Field.get("labelname"));
			l_CsvRow.add(m_Field.get("comptype"));
			l_CsvRows.add(l_CsvRow.toString());
		}
	}
}
}
v_Filename = "My_Form_Fields_MetaData.csv";
f_MyCSV = l_CsvRows.toString(v_NewLine).toFile(v_Filename);
f_MyCSV.setParamName("attachment");
sendmail
[
from :zoho.adminuserid
to :"<my_email>"
subject :"My Form Fields"
message :"See attached"
Attachments :file:f_MyCSV
]
return "";
  1.  v_NewLine = hexToText("0A")
  2.  l_CsvRows = List()
  3.  v_FormsEndpoint = "https://people.zoho.com/api/forms"
  4.  r_Forms = invokeurl 
  5.  [ 
  6.  url :v_FormsEndpoint 
  7.  type :GET 
  8.  connection:"my_people_connection" 
  9.  ]
  10.  m_Response = ifnull(r_Forms.get("response"),Map())
  11.  l_Forms = ifnull(m_Response.get("result"),List())
  12.  for each  m_Form in l_Forms 
  13.  { 
  14.  if(!isNull(m_Form.get("formLinkName"))) 
  15.  { 
  16.      v_FormName = m_Form.get("displayName")
  17.      v_FieldsEndpoint = "https://people.zoho.com/people/api/forms/" + m_Form.get("formLinkName") + "/components"
  18.      r_Fields = invokeurl 
  19.      [ 
  20.          url :v_FieldsEndpoint 
  21.          type :GET 
  22.          connection:"my_people_connection" 
  23.      ]
  24.      m_FieldsResponse = ifnull(r_Fields.get("response"),Map())
  25.      l_Fields = ifnull(m_FieldsResponse.get("result"),List())
  26.      for each  m_Field in l_Fields 
  27.      { 
  28.          if(!isNull(m_Field.get("comptype"))) 
  29.          { 
  30.              l_CsvRow = List()
  31.              l_CsvRow.add(v_FormName)
  32.              l_CsvRow.add(m_Form.get("formLinkName"))
  33.              l_CsvRow.add(m_Field.get("displayname"))
  34.              l_CsvRow.add(m_Field.get("labelname"))
  35.              l_CsvRow.add(m_Field.get("comptype"))
  36.              l_CsvRows.add(l_CsvRow.toString())
  37.          } 
  38.      } 
  39.  } 
  40.  } 
  41.  v_Filename = "My_Form_Fields_MetaData.csv"
  42.  f_MyCSV = l_CsvRows.toString(v_NewLine).toFile(v_Filename)
  43.  f_MyCSV.setParamName("attachment")
  44.  sendmail 
  45.  [ 
  46.  from :zoho.adminuserid 
  47.  to :"<my_email>" 
  48.  subject :"My Form Fields" 
  49.  message :"See attached" 
  50.  Attachments :file:f_MyCSV 
  51.  ] 
  52.  return ""


Out of sheer boredom: fn_GetMetaData_ViewsColumns
copyraw
v_NewLine = hexToText("0A");
l_CsvRows = List();
v_FormsEndpoint = "https://people.zoho.com/api/views";
r_Forms = invokeurl
[
url :v_FormsEndpoint
type :GET
connection:"my_people_connection"
];
m_Response = ifnull(r_Forms.get("response"),Map());
l_Forms = ifnull(m_Response.get("result"),List());
for each  m_Form in l_Forms
{
	l_CsvRows.add(m_Form.keys());
}
v_Filename = "My_Form_Fields_ViewColumns.csv";
f_MyCSV = l_CsvRows.toString(v_NewLine).toFile(v_Filename);
f_MyCSV.setParamName("attachment");
sendmail
[
from :zoho.adminuserid
to :"<my_email>"
subject :"My View Columns"
message :"See attached"
Attachments :file:f_MyCSV
]
return "";
  1.  v_NewLine = hexToText("0A")
  2.  l_CsvRows = List()
  3.  v_FormsEndpoint = "https://people.zoho.com/api/views"
  4.  r_Forms = invokeurl 
  5.  [ 
  6.  url :v_FormsEndpoint 
  7.  type :GET 
  8.  connection:"my_people_connection" 
  9.  ]
  10.  m_Response = ifnull(r_Forms.get("response"),Map())
  11.  l_Forms = ifnull(m_Response.get("result"),List())
  12.  for each  m_Form in l_Forms 
  13.  { 
  14.      l_CsvRows.add(m_Form.keys())
  15.  } 
  16.  v_Filename = "My_Form_Fields_ViewColumns.csv"
  17.  f_MyCSV = l_CsvRows.toString(v_NewLine).toFile(v_Filename)
  18.  f_MyCSV.setParamName("attachment")
  19.  sendmail 
  20.  [ 
  21.  from :zoho.adminuserid 
  22.  to :"<my_email>" 
  23.  subject :"My View Columns" 
  24.  message :"See attached" 
  25.  Attachments :file:f_MyCSV 
  26.  ] 
  27.  return ""
Category: Zoho People :: Article: 433

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com